pax_global_header00006660000000000000000000000064140363551520014516gustar00rootroot0000000000000052 comment=489df9f1b64ac27566b5a4f9c276ecc8c990f23d gitbatch-0.6.1/000077500000000000000000000000001403635515200133075ustar00rootroot00000000000000gitbatch-0.6.1/.gitignore000066400000000000000000000001041403635515200152720ustar00rootroot00000000000000exec.go.test build.sh test.go .vscode build/ coverage.txt .DS_Store gitbatch-0.6.1/.travis.yml000066400000000000000000000003211403635515200154140ustar00rootroot00000000000000language: go go: - "1.16" - tip before_install: - go get -t -v ./... script: - go test ./... -coverprofile=coverage.txt -covermode=atomic after_success: - bash <(curl -s https://codecov.io/bash) gitbatch-0.6.1/LICENSE000066400000000000000000000020671403635515200143210ustar00rootroot00000000000000MIT License Copyright (c) 2018 Ibrahim Serdar Acikgoz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gitbatch-0.6.1/README.md000066400000000000000000000040111403635515200145620ustar00rootroot00000000000000[![Build Status](https://travis-ci.com/isacikgoz/gitbatch.svg?branch=master)](https://travis-ci.com/isacikgoz/gitbatch) [![MIT License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](/LICENSE) [![Go Report Card](https://goreportcard.com/badge/github.com/isacikgoz/gitbatch)](https://goreportcard.com/report/github.com/isacikgoz/gitbatch) ## gitbatch Managing multiple git repositories is easier than ever. I (*was*) often end up working on many directories and manually pulling updates etc. To make this routine faster, I created a simple tool to handle this job. Although the focus is batch jobs, you can still do de facto micro management of your git repositories (e.g *add/reset, stash, commit etc.*) Check out the screencast of the app: [![asciicast](https://asciinema.org/a/lxoZT6Z8fSliIEebWSPVIY8ct.svg)](https://asciinema.org/a/lxoZT6Z8fSliIEebWSPVIY8ct) ## Installation To install with go, run the following command; ```bash go get -u github.com/isacikgoz/gitbatch/cmd/gitbatch ``` ### MacOS using homebrew ```bash brew install gitbatch ``` For other options see [installation page](https://github.com/isacikgoz/gitbatch/wiki/Installation) ## Use run the `gitbatch` command from the parent of your git repositories. For start-up options simply `gitbatch --help` For more information see the [wiki pages](https://github.com/isacikgoz/gitbatch/wiki) ## Further goals - improve testing - add push - full src-d/go-git integration (*having some performance issues in large repos*) - fetch, config, rev-list, add, reset, commit, status and diff commands are supported but not fully utilized, still using git occasionally - merge, stash are not supported yet by go-git ## Credits - [go-git](https://github.com/src-d/go-git) for git interface (partially) - [gocui](https://github.com/jroimartin/gocui) for user interface - [viper](https://github.com/spf13/viper) for configuration management - [color](https://github.com/fatih/color) for colored text - [kingpin](https://github.com/alecthomas/kingpin) for command-line flag&options gitbatch-0.6.1/cmd/000077500000000000000000000000001403635515200140525ustar00rootroot00000000000000gitbatch-0.6.1/cmd/gitbatch/000077500000000000000000000000001403635515200156375ustar00rootroot00000000000000gitbatch-0.6.1/cmd/gitbatch/main.go000066400000000000000000000022621403635515200171140ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/alecthomas/kingpin" "github.com/isacikgoz/gitbatch/internal/app" ) func main() { kingpin.Version("gitbatch version 0.6.1") dirs := kingpin.Flag("directory", "Directory(s) to roam for git repositories.").Short('d').Strings() mode := kingpin.Flag("mode", "Application start mode, more sensible with quick run.").Short('m').String() recursionDepth := kingpin.Flag("recursive-depth", "Find directories recursively.").Default("0").Short('r').Int() logLevel := kingpin.Flag("log-level", "Logging level; trace,debug,info,warn,error").Default("error").Short('l').String() quick := kingpin.Flag("quick", "runs without gui and fetches/pull remote upstream.").Short('q').Bool() kingpin.Parse() if err := run(*dirs, *logLevel, *recursionDepth, *quick, *mode); err != nil { fmt.Fprintf(os.Stderr, "application quitted with an unhandled error: %v", err) os.Exit(1) } } func run(dirs []string, log string, depth int, quick bool, mode string) error { app, err := app.New(&app.Config{ Directories: dirs, LogLevel: log, Depth: depth, QuickMode: quick, Mode: mode, }) if err != nil { return err } return app.Run() } gitbatch-0.6.1/go.mod000066400000000000000000000012241403635515200144140ustar00rootroot00000000000000module github.com/isacikgoz/gitbatch go 1.16 require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/alecthomas/kingpin v2.2.6+incompatible github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/fatih/color v1.9.0 github.com/go-git/go-git/v5 v5.1.0 github.com/jroimartin/gocui v0.4.0 github.com/mattn/go-runewidth v0.0.4 // indirect github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e // indirect github.com/spf13/viper v1.3.2 github.com/stretchr/testify v1.7.0 golang.org/x/sync v0.0.0-20190423024810-112230192c58 ) gitbatch-0.6.1/go.sum000066400000000000000000000305721403635515200144510ustar00rootroot00000000000000github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc= github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk= github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8= github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e h1:Vbib8wJAaMEF9jusI/kMSYMr/LtRzM7+F9MJgt/nH8k= github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gitbatch-0.6.1/internal/000077500000000000000000000000001403635515200151235ustar00rootroot00000000000000gitbatch-0.6.1/internal/app/000077500000000000000000000000001403635515200157035ustar00rootroot00000000000000gitbatch-0.6.1/internal/app/builder.go000066400000000000000000000036051403635515200176640ustar00rootroot00000000000000package app import ( "fmt" "os" "github.com/isacikgoz/gitbatch/internal/gui" ) // The App struct is responsible to hold app-wide related entities. Currently // it has only the gui.Gui pointer for interface entity. type App struct { Config *Config } // Config is an assembler data to initiate a setup type Config struct { Directories []string LogLevel string Depth int QuickMode bool Mode string } // New will handle pre-required operations. It is designed to be a wrapper for // main method right now. func New(argConfig *Config) (*App, error) { // initiate the app and give it initial values app := &App{} if len(argConfig.Directories) <= 0 { d, _ := os.Getwd() argConfig.Directories = []string{d} } presetConfig, err := loadConfiguration() if err != nil { return nil, err } app.Config = overrideConfig(presetConfig, argConfig) return app, nil } // Run starts the application. func (a *App) Run() error { dirs := generateDirectories(a.Config.Directories, a.Config.Depth) if a.Config.QuickMode { return a.execQuickMode(dirs) } // create a gui.Gui struct and run the gui gui, err := gui.New(a.Config.Mode, dirs) if err != nil { return err } return gui.Run() } func overrideConfig(appConfig, setupConfig *Config) *Config { if len(setupConfig.Directories) > 0 { appConfig.Directories = setupConfig.Directories } if len(setupConfig.LogLevel) > 0 { appConfig.LogLevel = setupConfig.LogLevel } if setupConfig.Depth > 0 { appConfig.Depth = setupConfig.Depth } if setupConfig.QuickMode { appConfig.QuickMode = setupConfig.QuickMode } if len(setupConfig.Mode) > 0 { appConfig.Mode = setupConfig.Mode } return appConfig } func (a *App) execQuickMode(directories []string) error { if a.Config.Mode != "fetch" && a.Config.Mode != "pull" { return fmt.Errorf("unrecognized quick mode: " + a.Config.Mode) } return quick(directories, a.Config.Mode) } gitbatch-0.6.1/internal/app/builder_test.go000066400000000000000000000021231403635515200207150ustar00rootroot00000000000000package app import ( "os" "testing" "github.com/isacikgoz/gitbatch/internal/git" "github.com/stretchr/testify/require" ) func TestOverrideConfig(t *testing.T) { config1 := &Config{ Directories: []string{}, LogLevel: "info", Depth: 1, QuickMode: false, Mode: "fetch", } config2 := &Config{ Directories: []string{string(os.PathSeparator) + "tmp"}, LogLevel: "error", Depth: 1, QuickMode: true, Mode: "pull", } var tests = []struct { inp1 *Config inp2 *Config expected *Config }{ {config1, config2, config1}, } for _, test := range tests { output := overrideConfig(test.inp1, test.inp2) require.Equal(t, test.expected, output) require.Equal(t, test.inp2.Mode, output.Mode) } } func TestExecQuickMode(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) var tests = []struct { inp1 []string }{ {[]string{th.BasicRepoPath()}}, } a := App{ Config: &Config{ Mode: "fetch", }, } for _, test := range tests { err := a.execQuickMode(test.inp1) require.NoError(t, err) } } gitbatch-0.6.1/internal/app/config.go000066400000000000000000000054501403635515200175030ustar00rootroot00000000000000package app import ( "os" "path/filepath" "runtime" "github.com/spf13/viper" ) // config file stuff var ( configFileName = "config" configFileExt = ".yml" configType = "yaml" appName = "gitbatch" configurationDirectory = filepath.Join(osConfigDirectory(runtime.GOOS), appName) configFileAbsPath = filepath.Join(configurationDirectory, configFileName) ) // configuration items var ( modeKey = "mode" modeKeyDefault = "fetch" pathsKey = "paths" quickKey = "quick" quickKeyDefault = false recursionKey = "recursion" recursionKeyDefault = 1 ) // loadConfiguration returns a Config struct is filled func loadConfiguration() (*Config, error) { if err := initializeConfigurationManager(); err != nil { return nil, err } if err := setDefaults(); err != nil { return nil, err } if err := readConfiguration(); err != nil { return nil, err } var directories []string if len(viper.GetStringSlice(pathsKey)) <= 0 { d, _ := os.Getwd() directories = []string{d} } else { directories = viper.GetStringSlice(pathsKey) } config := &Config{ Directories: directories, Depth: viper.GetInt(recursionKey), QuickMode: viper.GetBool(quickKey), Mode: viper.GetString(modeKey), } return config, nil } // set default configuration parameters func setDefaults() error { viper.SetDefault(quickKey, quickKeyDefault) viper.SetDefault(recursionKey, recursionKeyDefault) viper.SetDefault(modeKey, modeKeyDefault) // viper.SetDefault(pathsKey, pathsKeyDefault) return nil } // read configuration from file func readConfiguration() error { err := viper.ReadInConfig() // Find and read the config file if err != nil { // Handle errors reading the config file // if file does not exist, simply create one if _, err := os.Stat(configFileAbsPath + configFileExt); os.IsNotExist(err) { if err = os.MkdirAll(configurationDirectory, 0755); err != nil { return err } f, err := os.Create(configFileAbsPath + configFileExt) if err != nil { return err } defer f.Close() } else { return err } // let's write defaults if err := viper.WriteConfig(); err != nil { return err } } return nil } // initialize the configuration manager func initializeConfigurationManager() error { // config viper viper.AddConfigPath(configurationDirectory) viper.SetConfigName(configFileName) viper.SetConfigType(configType) return nil } // returns OS dependent config directory func osConfigDirectory(osName string) (osConfigDirectory string) { switch osName { case "windows": osConfigDirectory = os.Getenv("APPDATA") case "darwin": osConfigDirectory = os.Getenv("HOME") + "/Library/Application Support" case "linux": osConfigDirectory = os.Getenv("HOME") + "/.config" } return osConfigDirectory } gitbatch-0.6.1/internal/app/config_test.go000066400000000000000000000013211403635515200205330ustar00rootroot00000000000000package app import ( "testing" "github.com/stretchr/testify/require" ) func TestLoadConfiguration(t *testing.T) { cfg, err := loadConfiguration() require.NoError(t, err) require.NotNil(t, cfg) } func TestReadConfiguration(t *testing.T) { err := readConfiguration() require.NoError(t, err) } func TestInitializeConfigurationManager(t *testing.T) { err := initializeConfigurationManager() require.NoError(t, err) } func TestOsConfigDirectory(t *testing.T) { var tests = []struct { input string expected string }{ {"linux", ".config"}, {"darwin", "Application Support"}, } for _, test := range tests { output := osConfigDirectory(test.input) require.Contains(t, output, test.expected) } } gitbatch-0.6.1/internal/app/files.go000066400000000000000000000042411403635515200173350ustar00rootroot00000000000000package app import ( "io/ioutil" "os" "path/filepath" ) // generateDirectories returns possible git repositories to pipe into git pkg // load function func generateDirectories(dirs []string, depth int) []string { gitDirs := make([]string, 0) for i := 0; i < depth; i++ { directories, repositories := walkRecursive(dirs, gitDirs) dirs = directories gitDirs = repositories } return gitDirs } // returns given values, first search directories and second stands for possible // git repositories. Call this func from a "for i := 0; i= len(search) { continue } // find possible repositories and remaining ones, b slice is possible ones a, b, err := separateDirectories(search[i]) if err != nil { continue } // since we started to search let's get rid of it and remove from search // array search[i] = search[len(search)-1] search = search[:len(search)-1] // lets append what we have found to continue recursion search = append(search, a...) appendant = append(appendant, b...) } return search, appendant } // separateDirectories is to find all the files in given path. This method // does not check if the given file is a valid git repositories func separateDirectories(directory string) ([]string, []string, error) { dirs := make([]string, 0) gitDirs := make([]string, 0) files, err := ioutil.ReadDir(directory) // can we read the directory? if err != nil { return nil, nil, nil } for _, f := range files { repo := directory + string(os.PathSeparator) + f.Name() file, err := os.Open(repo) // if we cannot open it, simply continue to iteration and don't consider if err != nil { file.Close() continue } dir, err := filepath.Abs(file.Name()) if err != nil { file.Close() continue } // with this approach, we ignore submodule or sub repositories in a git repository ff, err := os.Open(dir + string(os.PathSeparator) + ".git") if err != nil { dirs = append(dirs, dir) } else { gitDirs = append(gitDirs, dir) } ff.Close() file.Close() } return dirs, gitDirs, nil } gitbatch-0.6.1/internal/app/files_test.go000066400000000000000000000035251403635515200204000ustar00rootroot00000000000000package app import ( "path/filepath" "testing" "github.com/isacikgoz/gitbatch/internal/git" "github.com/stretchr/testify/require" ) func TestGenerateDirectories(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) var tests = []struct { inp1 []string inp2 int expected []string }{ {[]string{th.RepoPath}, 1, []string{th.BasicRepoPath(), th.DirtyRepoPath()}}, {[]string{th.RepoPath}, 2, []string{th.BasicRepoPath(), th.DirtyRepoPath()}}, // maybe move one repo to a sub folder } for _, test := range tests { output := generateDirectories(test.inp1, test.inp2) require.ElementsMatch(t, output, test.expected) } } func TestWalkRecursive(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) var tests = []struct { inp1 []string inp2 []string exp1 []string exp2 []string }{ { []string{th.RepoPath}, []string{""}, []string{filepath.Join(th.RepoPath, ".git"), filepath.Join(th.RepoPath, ".gitmodules"), th.NonRepoPath()}, []string{"", th.BasicRepoPath(), th.DirtyRepoPath()}, }, } for _, test := range tests { out1, out2 := walkRecursive(test.inp1, test.inp2) require.ElementsMatch(t, out1, test.exp1) require.ElementsMatch(t, out2, test.exp2) } } func TestSeparateDirectories(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) var tests = []struct { input string exp1 []string exp2 []string }{ { "", nil, nil, }, { th.RepoPath, []string{filepath.Join(th.RepoPath, ".git"), filepath.Join(th.RepoPath, ".gitmodules"), th.NonRepoPath()}, []string{th.BasicRepoPath(), th.DirtyRepoPath()}, }, } for _, test := range tests { out1, out2, err := separateDirectories(test.input) require.NoError(t, err) require.ElementsMatch(t, out1, test.exp1) require.ElementsMatch(t, out2, test.exp2) } } gitbatch-0.6.1/internal/app/quick.go000066400000000000000000000020011403635515200173370ustar00rootroot00000000000000package app import ( "fmt" "sync" "time" "github.com/isacikgoz/gitbatch/internal/command" "github.com/isacikgoz/gitbatch/internal/git" ) func quick(directories []string, mode string) error { var wg sync.WaitGroup start := time.Now() for _, dir := range directories { wg.Add(1) go func(d string, mode string) { defer wg.Done() if err := operate(d, mode); err != nil { fmt.Printf("could not perform %s on %s: %s", mode, d, err) } fmt.Printf("%s: successful\n", d) }(dir, mode) } wg.Wait() elapsed := time.Since(start) fmt.Printf("%d repositories finished in: %s\n", len(directories), elapsed) return nil } func operate(directory, mode string) error { r, err := git.FastInitializeRepo(directory) if err != nil { return err } switch mode { case "fetch": return command.Fetch(r, &command.FetchOptions{ RemoteName: "origin", Progress: true, }) case "pull": return command.Pull(r, &command.PullOptions{ RemoteName: "origin", Progress: true, }) } return nil } gitbatch-0.6.1/internal/app/quick_test.go000066400000000000000000000007411403635515200204070ustar00rootroot00000000000000package app import ( "testing" "github.com/isacikgoz/gitbatch/internal/git" "github.com/stretchr/testify/require" ) func TestQuick(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) var tests = []struct { inp1 []string inp2 string }{ { []string{th.DirtyRepoPath()}, "fetch", }, { []string{th.DirtyRepoPath()}, "pull", }, } for _, test := range tests { err := quick(test.inp1, test.inp2) require.NoError(t, err) } } gitbatch-0.6.1/internal/command/000077500000000000000000000000001403635515200165415ustar00rootroot00000000000000gitbatch-0.6.1/internal/command/add.go000066400000000000000000000031571403635515200176260ustar00rootroot00000000000000package command import ( "fmt" "github.com/isacikgoz/gitbatch/internal/git" ) // AddOptions defines the rules for "git add" command type AddOptions struct { // Update Update bool // Force Force bool // DryRun DryRun bool // Mode is the command mode CommandMode Mode } // Add is a wrapper function for "git add" command func Add(r *git.Repository, f *git.File, o *AddOptions) error { mode := o.CommandMode if o.Update || o.Force || o.DryRun { mode = ModeLegacy } switch mode { case ModeLegacy: err := addWithGit(r, f, o) return err case ModeNative: err := addWithGoGit(r, f) return err } return fmt.Errorf("unhandled add operation") } // AddAll function is the wrapper of "git add ." command func AddAll(r *git.Repository, o *AddOptions) error { args := make([]string, 0) args = append(args, "add") if o.DryRun { args = append(args, "--dry-run") } args = append(args, ".") _, err := Run(r.AbsPath, "git", args) if err != nil { return fmt.Errorf("could not run add function: %v", err) } return nil } func addWithGit(r *git.Repository, f *git.File, o *AddOptions) error { args := make([]string, 0) args = append(args, "add") args = append(args, f.Name) if o.Update { args = append(args, "--update") } if o.Force { args = append(args, "--force") } if o.DryRun { args = append(args, "--dry-run") } _, err := Run(r.AbsPath, "git", args) if err != nil { return fmt.Errorf("could not add %s: %v", f.AbsPath, err) } return nil } func addWithGoGit(r *git.Repository, f *git.File) error { w, err := r.Repo.Worktree() if err != nil { return err } _, err = w.Add(f.Name) return err } gitbatch-0.6.1/internal/command/add_test.go000066400000000000000000000024321403635515200206600ustar00rootroot00000000000000package command import ( "testing" "github.com/isacikgoz/gitbatch/internal/git" "github.com/stretchr/testify/require" ) var ( testAddopt1 = &AddOptions{} ) func TestAddAll(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) _, err := testFile(th.RepoPath, "file") require.NoError(t, err) var tests = []struct { inp1 *git.Repository inp2 *AddOptions }{ {th.Repository, testAddopt1}, } for _, test := range tests { err := AddAll(test.inp1, test.inp2) require.NoError(t, err) } } func TestAddWithGit(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) f, err := testFile(th.RepoPath, "file") require.NoError(t, err) var tests = []struct { inp1 *git.Repository inp2 *git.File inp3 *AddOptions }{ {th.Repository, f, testAddopt1}, } for _, test := range tests { err := addWithGit(test.inp1, test.inp2, test.inp3) require.NoError(t, err) } } func TestAddWithGoGit(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) f, err := testFile(th.RepoPath, "file") require.NoError(t, err) var tests = []struct { inp1 *git.Repository inp2 *git.File }{ {th.Repository, f}, } for _, test := range tests { err := addWithGoGit(test.inp1, test.inp2) require.NoError(t, err) } } gitbatch-0.6.1/internal/command/checkout.go000066400000000000000000000020751403635515200207010ustar00rootroot00000000000000package command import ( "os/exec" "github.com/isacikgoz/gitbatch/internal/git" ) // CheckoutOptions defines the rules of checkout command type CheckoutOptions struct { TargetRef string CreateIfAbsent bool CommandMode Mode } // Checkout is a wrapper function for "git checkout" command. func Checkout(r *git.Repository, o *CheckoutOptions) error { var branch *git.Branch for _, b := range r.Branches { if b.Name == o.TargetRef { branch = b break } } msg := "checkout in progress" if branch != nil { if err := r.Checkout(branch); err != nil { r.SetWorkStatus(git.Fail) msg = err.Error() } else { r.SetWorkStatus(git.Success) msg = "switched to " + o.TargetRef } } else if o.CreateIfAbsent { args := []string{"checkout", "-b", o.TargetRef} cmd := exec.Command("git", args...) cmd.Dir = r.AbsPath _, err := cmd.CombinedOutput() if err != nil { r.SetWorkStatus(git.Fail) msg = err.Error() } else { r.SetWorkStatus(git.Success) msg = "switched to " + o.TargetRef } } r.State.Message = msg return r.Refresh() } gitbatch-0.6.1/internal/command/checkout_test.go000066400000000000000000000012541403635515200217360ustar00rootroot00000000000000package command import ( "testing" "github.com/isacikgoz/gitbatch/internal/git" "github.com/stretchr/testify/require" ) func TestCheckout(t *testing.T) { opts1 := &CheckoutOptions{ TargetRef: "master", CreateIfAbsent: false, CommandMode: ModeLegacy, } opts2 := &CheckoutOptions{ TargetRef: "develop", CreateIfAbsent: true, CommandMode: ModeLegacy, } th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) var tests = []struct { inp1 *git.Repository inp2 *CheckoutOptions }{ {th.Repository, opts1}, {th.Repository, opts2}, } for _, test := range tests { err := Checkout(test.inp1, test.inp2) require.NoError(t, err) } } gitbatch-0.6.1/internal/command/cmd.go000066400000000000000000000037671403635515200176500ustar00rootroot00000000000000package command import ( "log" "os/exec" "strings" "syscall" ) // Mode indicates that whether command should run native code or use git // command to operate. type Mode uint8 const ( // ModeLegacy uses traditional git command line tool to operate ModeLegacy = iota // ModeNative uses native implementation of given git command ModeNative ) // Run runs the OS command and return its output. If the output // returns error it also encapsulates it as a golang.error which is a return code // of the command except zero func Run(d string, c string, args []string) (string, error) { cmd := exec.Command(c, args...) if d != "" { cmd.Dir = d } output, err := cmd.CombinedOutput() return trimTrailingNewline(string(output)), err } // Return returns if we supposed to get return value as an int of a command // this method can be used. It is practical when you use a command and process a // failover according to a specific return code func Return(d string, c string, args []string) (int, error) { cmd := exec.Command(c, args...) if d != "" { cmd.Dir = d } var err error // this time the execution is a little different if err := cmd.Start(); err != nil { return -1, err } if err := cmd.Wait(); err != nil { if exiterr, ok := err.(*exec.ExitError); ok { // The program has exited with an exit code != 0 // This works on both Unix and Windows. Although package // syscall is generally platform dependent, WaitStatus is // defined for both Unix and Windows and in both cases has // an ExitStatus() method with the same signature. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { statusCode := status.ExitStatus() return statusCode, err } } else { log.Fatalf("cmd.Wait: %v", err) } } return -1, err } // trimTrailingNewline removes the trailing new line form a string. this method // is used mostly on outputs of a command func trimTrailingNewline(s string) string { if strings.HasSuffix(s, "\n") || strings.HasSuffix(s, "\r") { return s[:len(s)-1] } return s } gitbatch-0.6.1/internal/command/cmd_test.go000066400000000000000000000033351403635515200206760ustar00rootroot00000000000000package command import ( "os" "testing" "github.com/isacikgoz/gitbatch/internal/git" ) func TestRun(t *testing.T) { wd, err := os.Getwd() if err != nil { t.Fatalf("Test Failed.") } var tests = []struct { inp1 string inp2 string inp3 []string }{ {wd, "git", []string{"status"}}, } for _, test := range tests { if output, err := Run(test.inp1, test.inp2, test.inp3); err != nil || len(output) <= 0 { t.Errorf("Test Failed. {%s, %s, %s} inputted, output: %s", test.inp1, test.inp2, test.inp3, output) } } } func TestReturn(t *testing.T) { wd, err := os.Getwd() if err != nil { t.Fatalf("Test Failed.") } var tests = []struct { inp1 string inp2 string inp3 []string expected int }{ {wd, "foo", []string{}, -1}, } for _, test := range tests { if output, _ := Return(test.inp1, test.inp2, test.inp3); output != test.expected { t.Errorf("Test Failed. {%s, %s, %s} inputted, output: %d, expected : %d", test.inp1, test.inp2, test.inp3, output, test.expected) } } } func TestTrimTrailingNewline(t *testing.T) { var tests = []struct { input string expected string }{ {"foo", "foo"}, {"foo\n", "foo"}, {"foo\r", "foo"}, } for _, test := range tests { if output := trimTrailingNewline(test.input); output != test.expected { t.Errorf("Test Failed. %s inputted, output: %s, expected: %s", test.input, output, test.expected) } } } func testFile(testRepoDir, name string) (*git.File, error) { _, err := os.Create(testRepoDir + string(os.PathSeparator) + name) if err != nil { return nil, err } f := &git.File{ Name: name, AbsPath: testRepoDir + string(os.PathSeparator) + name, X: git.StatusUntracked, Y: git.StatusUntracked, } return f, nil } gitbatch-0.6.1/internal/command/commit.go000066400000000000000000000035521403635515200203650ustar00rootroot00000000000000package command import ( "fmt" "time" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" giterr "github.com/isacikgoz/gitbatch/internal/errors" "github.com/isacikgoz/gitbatch/internal/git" ) // CommitOptions defines the rules for commit operation type CommitOptions struct { // CommitMsg CommitMsg string // User User string // Email Email string // Mode is the command mode CommandMode Mode } // Commit defines which commit command to use. func Commit(r *git.Repository, o *CommitOptions) (err error) { // here we configure commit operation switch o.CommandMode { case ModeLegacy: return commitWithGit(r, o) case ModeNative: return commitWithGoGit(r, o) } return fmt.Errorf("unhandled commit operation") } // commitWithGit is simply a bare git commit -m command which is flexible func commitWithGit(r *git.Repository, opt *CommitOptions) (err error) { args := make([]string, 0) args = append(args, "commit") args = append(args, "-m") // parse options to command line arguments if len(opt.CommitMsg) > 0 { args = append(args, opt.CommitMsg) } if out, err := Run(r.AbsPath, "git", args); err != nil { _ = r.Refresh() return giterr.ParseGitError(out, err) } // till this step everything should be ok return r.Refresh() } // commitWithGoGit is the primary commit method func commitWithGoGit(r *git.Repository, options *CommitOptions) (err error) { opt := &gogit.CommitOptions{ Author: &object.Signature{ Name: options.User, Email: options.Email, When: time.Now(), }, Committer: &object.Signature{ Name: options.User, Email: options.Email, When: time.Now(), }, } w, err := r.Repo.Worktree() if err != nil { return err } _, err = w.Commit(options.CommitMsg, opt) if err != nil { _ = r.Refresh() return err } // till this step everything should be ok return r.Refresh() } gitbatch-0.6.1/internal/command/commit_test.go000066400000000000000000000025261403635515200214240ustar00rootroot00000000000000package command import ( "testing" giterr "github.com/isacikgoz/gitbatch/internal/errors" "github.com/isacikgoz/gitbatch/internal/git" "github.com/stretchr/testify/require" ) func TestCommitWithGit(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) f, err := testFile(th.RepoPath, "file") require.NoError(t, err) err = addWithGit(th.Repository, f, testAddopt1) require.NoError(t, err) testCommitopt1 := &CommitOptions{ CommitMsg: "test", User: "foo", Email: "foo@bar.com", } var tests = []struct { inp1 *git.Repository inp2 *CommitOptions }{ {th.Repository, testCommitopt1}, } for _, test := range tests { err = commitWithGit(test.inp1, test.inp2) require.False(t, err != nil && err == giterr.ErrUserEmailNotSet) } } func TestCommitWithGoGit(t *testing.T) { th := git.InitTestRepositoryFromLocal(t) defer th.CleanUp(t) f, err := testFile(th.RepoPath, "file") require.NoError(t, err) err = addWithGit(th.Repository, f, testAddopt1) require.NoError(t, err) testCommitopt1 := &CommitOptions{ CommitMsg: "test", User: "foo", Email: "foo@bar.com", } var tests = []struct { inp1 *git.Repository inp2 *CommitOptions }{ {th.Repository, testCommitopt1}, } for _, test := range tests { err = commitWithGoGit(test.inp1, test.inp2) require.NoError(t, err) } } gitbatch-0.6.1/internal/command/config.go000066400000000000000000000051161403635515200203400ustar00rootroot00000000000000package command import ( "fmt" "github.com/isacikgoz/gitbatch/internal/git" ) // ConfigOptions defines the rules for commit operation type ConfigOptions struct { // Section Section string // Option Option string // Site should be Global or Local Site ConfigSite // Mode is the command mode CommandMode Mode } // ConfigSite defines a string type for the site. type ConfigSite string const ( // ConfigSiteLocal defines a local config. ConfigSiteLocal ConfigSite = "local" // ConfigSiteGlobal defines a global config. ConfigSiteGlobal ConfigSite = "global" ) // Config adds or reads config of a repository func Config(r *git.Repository, o *ConfigOptions) (value string, err error) { // here we configure config operation switch o.CommandMode { case ModeLegacy: return configWithGit(r, o) case ModeNative: return configWithGoGit(r, o) } return value, fmt.Errorf("unhandled config operation") } // configWithGit is simply a bare git config --site