├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── cross-compile.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── cli ├── add.go ├── auth_plan9.go ├── auth_plan9_test.go ├── auth_posix.go ├── blame.go ├── branch.go ├── checkout.go ├── clone.go ├── commit.go ├── config.go ├── diff.go ├── editor_plan9.go ├── editor_posix.go ├── fetch.go ├── git.go ├── grep.go ├── init.go ├── log.go ├── log_test.go ├── ls-files.go ├── ls-remote.go ├── mv.go ├── pull.go ├── push.go ├── receive-pack.go ├── remote.go ├── reset.go ├── rev-parse.go ├── rm.go ├── root.go ├── show.go ├── status.go ├── tag.go ├── testdata │ └── factotum.keys └── upload-pack.go ├── cmd ├── gig │ └── main.go ├── git-receive-pack │ └── main.go └── git-upload-pack │ └── main.go ├── go.mod ├── go.sum ├── script_test.go └── testdata ├── add.txt ├── blame.txt ├── branch.txt ├── checkout.txt ├── clone.txt ├── commit.txt ├── diff.txt ├── fetch.txt ├── grep.txt ├── init_cwd.txt ├── init_foo.txt ├── log.txt ├── ls-files.txt ├── ls-remote.txt ├── mv.txt ├── pull.txt ├── push-current-branch.txt ├── push.txt ├── remote.txt ├── reset.txt ├── rev-parse.txt ├── rm.txt ├── show.txt └── tag.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for Go 4 | - package-ecosystem: "gomod" 5 | # Look for `go.mod` file in the `root` directory 6 | directory: "/" 7 | schedule: 8 | interval: "monthly" 9 | -------------------------------------------------------------------------------- /.github/workflows/cross-compile.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Go Cross-compile 3 | 4 | jobs: 5 | test: 6 | strategy: 7 | matrix: 8 | go-version: ['1.19', '1.20'] 9 | platform: [ubuntu-latest] 10 | fail-fast: false 11 | runs-on: ${{ matrix.platform }} 12 | env: 13 | GO111MODULE: on 14 | 15 | steps: 16 | - name: Install Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: ${{ matrix.go-version }} 20 | 21 | - name: Checkout code 22 | uses: actions/checkout@v1 23 | 24 | - name: Cross-compile 25 | run: | 26 | GOOS=freebsd go build ./... 27 | GOOS=openbsd go build ./... 28 | GOOS=netbsd go build ./... 29 | GOOS=dragonfly go build ./... 30 | GOOS=solaris go build ./... 31 | GOOS=illumos go build ./... 32 | GOOS=plan9 go build ./... 33 | #GOOS=js GOARCH=wasm go build ./... # go-billy build fails 34 | shell: bash 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | 4 | jobs: 5 | test: 6 | strategy: 7 | matrix: 8 | go-version: ['1.19', '1.20'] 9 | platform: [ubuntu-latest, macos-latest, windows-latest] 10 | fail-fast: false 11 | runs-on: ${{ matrix.platform }} 12 | env: 13 | GO111MODULE: on 14 | 15 | steps: 16 | - name: Install Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: ${{ matrix.go-version }} 20 | 21 | - name: Go environment 22 | run: | 23 | go version 24 | go env 25 | echo PATH is $PATH 26 | shell: bash 27 | 28 | - name: Checkout code 29 | uses: actions/checkout@v1 30 | 31 | - name: Run tests 32 | run: | 33 | go test -race -v ./... 34 | shell: bash 35 | 36 | - name: Check gofmt 37 | run: | 38 | gofmt -l . 39 | test `gofmt -l . | wc -l` = 0 40 | shell: bash 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gig 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2019-2020 Fazlul Shahriar. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gig 2 | 3 | Gig is a git client written in pure Go (using 4 | [go-git](https://github.com/go-git/go-git)). The main motivation was to 5 | create a git client for platforms that are not supported by the official 6 | git client (mainly Plan 9). Gig tries to be compatible with git CLI, 7 | so anyone familiar with the official git client will already know how 8 | to use gig. 9 | 10 | ## Install 11 | 12 | ``` 13 | GO111MODULE=on go get github.com/fhs/gig/cmd/git-upload-pack 14 | GO111MODULE=on go get github.com/fhs/gig/cmd/git-receive-pack 15 | GO111MODULE=on go get github.com/fhs/gig/cmd/gig 16 | ``` 17 | 18 | ## Testing on Plan 9 19 | 20 | To run tests on Plan 9, you need to run this first: 21 | ``` 22 | go mod edit -replace 'github.com/rogpeppe/go-internal@v1.6.2=github.com/fhs/go-internal@v1.6.3-0.20201122174144-815d671f4ff9' 23 | ``` 24 | This is due to https://github.com/rogpeppe/go-internal/pull/115. 25 | 26 | ## See also 27 | * https://github.com/oridb/git9 28 | * https://github.com/driusan/dgit 29 | * [Wrapping Git in rc shell](https://blog.gopheracademy.com/advent-2014/wrapping-git/) 30 | * [Git port to 9legacy by Kyohei Kadota](https://9fans.topicbox.com/groups/9fans/Te3752ec266e3a002-M7286f7236d8aab10096f7946/9fans-git-client) 31 | -------------------------------------------------------------------------------- /cli/add.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func init() { 14 | cmd := &cobra.Command{ 15 | Use: "add file ...", 16 | Short: "Add file contents to the index", 17 | Long: ``, 18 | RunE: addCmd, 19 | } 20 | rootCmd.AddCommand(cmd) 21 | } 22 | 23 | func addCmd(_ *cobra.Command, args []string) error { 24 | if len(args) == 0 { 25 | fmt.Printf("Nothing specified, nothing added.") 26 | return nil 27 | } 28 | 29 | root, r, err := openRepo() 30 | if err != nil { 31 | return err 32 | } 33 | w, err := r.Worktree() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | for _, a := range args { 39 | a, err = repoRelPath(root, a) 40 | if err != nil { 41 | return err 42 | } 43 | _, err = w.Add(a) 44 | if err != nil { 45 | return err 46 | } 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /cli/auth_plan9.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "bufio" 9 | "crypto/rsa" 10 | "crypto/sha1" 11 | "encoding/base64" 12 | "fmt" 13 | "io" 14 | "math/big" 15 | "net" 16 | "os" 17 | "path/filepath" 18 | "strconv" 19 | "strings" 20 | 21 | "github.com/fhs/go-plan9-auth/auth" 22 | git "github.com/go-git/go-git/v5" 23 | "github.com/go-git/go-git/v5/plumbing/transport" 24 | tssh "github.com/go-git/go-git/v5/plumbing/transport/ssh" 25 | "golang.org/x/crypto/ssh" 26 | "golang.org/x/crypto/ssh/knownhosts" 27 | ) 28 | 29 | func remoteAuth(r *git.Repository, remote string) (transport.AuthMethod, error) { 30 | rem, err := r.Remote(remote) 31 | if err != nil { 32 | return nil, err 33 | } 34 | cfg := rem.Config() 35 | if len(cfg.URLs) == 0 { 36 | return nil, fmt.Errorf("not URLs in remote %v", remote) 37 | } 38 | ep, err := transport.NewEndpoint(cfg.URLs[0]) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return endpointAuth(ep) 43 | } 44 | 45 | func endpointAuth(ep *transport.Endpoint) (transport.AuthMethod, error) { 46 | if ep.Protocol != "ssh" { 47 | // We only care about ssh auth for now. 48 | // In the future, factotum should be used for https also. 49 | return nil, nil 50 | } 51 | // Plan 9 ssh client stores known host keys in the format 52 | // described by thumbprint(6). An initial attempt to reuse this 53 | // file proved not very useful because the Plan 9 ssh client seems 54 | // to be using a different algorithm than golang.org/x/crypto/ssh 55 | // (ssh-rsa instead of ecdsa-sha2-nistp256), leading to thumbprint 56 | // mismatch. We also need to write less code if we just use the 57 | // OpenSSH known_hosts format. 58 | files, err := getDefaultKnownHostsFiles() 59 | if err != nil { 60 | return nil, err 61 | } 62 | hostKeyCallback, err := knownhosts.New(files...) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return &tssh.PublicKeysCallback{ 67 | User: ep.User, 68 | Callback: factotumSigners, 69 | HostKeyCallbackHelper: tssh.HostKeyCallbackHelper{ 70 | HostKeyCallback: printUnknownKey(hostKeyCallback), 71 | }, 72 | }, nil 73 | } 74 | 75 | func getDefaultKnownHostsFiles() ([]string, error) { 76 | files := filepath.SplitList(os.Getenv("SSH_KNOWN_HOSTS")) 77 | if len(files) != 0 { 78 | return files, nil 79 | } 80 | dir, err := gigConfigDir() 81 | if err != nil { 82 | return nil, err 83 | } 84 | return []string{ 85 | filepath.Join(dir, "known_hosts"), 86 | }, nil 87 | } 88 | 89 | func printUnknownKey(f ssh.HostKeyCallback) ssh.HostKeyCallback { 90 | return func(hostname string, remote net.Addr, key ssh.PublicKey) error { 91 | err := f(hostname, remote, key) 92 | if ke, ok := err.(*knownhosts.KeyError); ok && len(ke.Want) == 0 { 93 | b := base64.StdEncoding.EncodeToString(key.Marshal()) 94 | host := hostname 95 | if r := remote.String(); r != host { 96 | host += "," + r 97 | } 98 | fmt.Fprintf(os.Stderr, "Add host key to known_hosts file after verification:\n") 99 | fmt.Fprintf(os.Stderr, "\techo '%v %v %v' >> $home/lib/ssh/known_hosts\n", host, key.Type(), b) 100 | } 101 | return err 102 | } 103 | } 104 | 105 | func factotumSigners() ([]ssh.Signer, error) { 106 | f, err := os.Open("/mnt/factotum/ctl") 107 | if err != nil { 108 | return nil, err 109 | } 110 | defer f.Close() 111 | 112 | scanner := bufio.NewScanner(f) 113 | var signers []ssh.Signer 114 | for scanner.Scan() { 115 | m, err := parseKey(scanner.Text()) 116 | if err != nil { 117 | return nil, err 118 | } 119 | if m["proto"] == "rsa" && m["service"] == "ssh" { 120 | n, ok := new(big.Int).SetString(m["n"], 16) 121 | if !ok { 122 | return nil, fmt.Errorf("could not parse ek in rsa key") 123 | } 124 | ek, err := strconv.ParseUint(m["ek"], 16, 0) 125 | if err != nil { 126 | return nil, err 127 | } 128 | sn, err := newRSASigner(n, int(ek)) 129 | if err != nil { 130 | return nil, err 131 | } 132 | signers = append(signers, sn) 133 | } 134 | } 135 | if err := scanner.Err(); err != nil { 136 | return nil, err 137 | } 138 | return signers, nil 139 | } 140 | 141 | func parseKey(s string) (map[string]string, error) { 142 | f := strings.Fields(s) 143 | if len(f) == 0 || f[0] != "key" { 144 | return nil, fmt.Errorf("invalid factotum key") 145 | } 146 | m := make(map[string]string, len(f)-1) 147 | for _, e := range f { 148 | if i := strings.IndexByte(e, '='); i >= 0 { 149 | m[e[:i]] = e[i+1:] 150 | } 151 | } 152 | return m, nil 153 | } 154 | 155 | type rsaSigner struct { 156 | pub *rsa.PublicKey 157 | spub ssh.PublicKey 158 | } 159 | 160 | func newRSASigner(N *big.Int, E int) (ssh.Signer, error) { 161 | pub := &rsa.PublicKey{ 162 | N: N, // n in factotum 163 | E: E, // ek in factotum 164 | } 165 | spub, err := ssh.NewPublicKey(pub) 166 | if err != nil { 167 | return nil, err 168 | } 169 | return &rsaSigner{ 170 | pub: pub, 171 | spub: spub, 172 | }, nil 173 | } 174 | 175 | func (s *rsaSigner) PublicKey() ssh.PublicKey { 176 | return s.spub 177 | } 178 | 179 | func (s *rsaSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { 180 | rpc, err := auth.NewRPC() 181 | if err != nil { 182 | return nil, err 183 | } 184 | a := fmt.Sprintf("proto=rsa role=sign n=%X ek=%X", s.pub.N, s.pub.E) 185 | if _, _, err = rpc.Call("start", []byte(a)); err != nil { 186 | return nil, err 187 | } 188 | digest := sha1.Sum(data) 189 | if _, _, err = rpc.Call("write", digest[:]); err != nil { 190 | return nil, err 191 | } 192 | _, blob, err := rpc.Call("read", nil) 193 | if err != nil { 194 | return nil, err 195 | } 196 | return &ssh.Signature{ 197 | Format: "ssh-rsa", 198 | Blob: blob, 199 | }, nil 200 | } 201 | 202 | var progressWriter = func() io.Writer { 203 | if os.Getenv("TERM") == "" { // not vt(1) 204 | return plan9Terminal{} 205 | } 206 | return os.Stdout 207 | } 208 | 209 | type plan9Terminal struct{} 210 | 211 | func (p plan9Terminal) Write(data []byte) (int, error) { 212 | for i, b := range data { 213 | if b == '\r' { 214 | data[i] = '\n' 215 | } 216 | } 217 | return os.Stdout.Write(data) 218 | } 219 | -------------------------------------------------------------------------------- /cli/auth_plan9_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "crypto/rand" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | "strings" 13 | "syscall" 14 | "testing" 15 | "unsafe" 16 | ) 17 | 18 | func setupFactotum(t *testing.T) { 19 | if _, err := rfork(syscall.RFNAMEG); err != nil { 20 | t.Fatal(err) 21 | } 22 | cmd := exec.Command("/bin/auth/factotum", "-n") 23 | if err := cmd.Run(); err != nil { 24 | t.Fatal(err) 25 | } 26 | // These keys were generated on Plan 9 using: auth/rsagen -t 'service=ssh' 27 | keys, err := ioutil.ReadFile("testdata/factotum.keys") 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | w, err := os.OpenFile("/mnt/factotum/ctl", os.O_WRONLY|os.O_APPEND, 0) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | defer func() { 36 | if err := w.Close(); err != nil { 37 | t.Fatal(err) 38 | } 39 | }() 40 | for _, line := range strings.Split(string(keys), "\n") { 41 | if _, err := w.WriteString(line); err != nil { 42 | t.Fatal(err) 43 | } 44 | } 45 | } 46 | 47 | func TestFactotum(t *testing.T) { 48 | setupFactotum(t) 49 | signers, err := factotumSigners() 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | for i, priv := range signers { 54 | pub := priv.PublicKey() 55 | t.Logf("checking key %v: %v\n", i, priv) 56 | data := []byte("sign me") 57 | sig, err := priv.Sign(rand.Reader, data) 58 | if err != nil { 59 | t.Fatalf("Sign(%T): %v", priv, err) 60 | } 61 | if err := pub.Verify(data, sig); err != nil { 62 | t.Errorf("publicKey.Verify(%T): %v", priv, err) 63 | } 64 | sig.Blob[5]++ 65 | if err := pub.Verify(data, sig); err == nil { 66 | t.Errorf("publicKey.Verify on broken sig did not fail") 67 | } 68 | } 69 | } 70 | 71 | func rfork(flags int) (pid int, err error) { 72 | r1, _, _ := syscall.RawSyscall(syscall.SYS_RFORK, uintptr(flags), 0, 0) 73 | if int32(r1) == -1 { 74 | return 0, syscall.NewError(errstr()) 75 | } 76 | return int(r1), nil 77 | } 78 | 79 | func cstring(s []byte) string { 80 | for i, b := range s { 81 | if b == 0 { 82 | return string(s[0:i]) 83 | } 84 | } 85 | return string(s) 86 | } 87 | 88 | func errstr() string { 89 | var buf [syscall.ERRMAX]byte 90 | 91 | syscall.RawSyscall(syscall.SYS_ERRSTR, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0) 92 | 93 | buf[len(buf)-1] = 0 94 | return cstring(buf[:]) 95 | } 96 | -------------------------------------------------------------------------------- /cli/auth_posix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !plan9 6 | // +build !plan9 7 | 8 | package cli 9 | 10 | import ( 11 | "io" 12 | "os" 13 | 14 | git "github.com/go-git/go-git/v5" 15 | "github.com/go-git/go-git/v5/plumbing/transport" 16 | ) 17 | 18 | func remoteAuth(r *git.Repository, remote string) (transport.AuthMethod, error) { 19 | return nil, nil // the default one works already 20 | } 21 | 22 | func endpointAuth(ep *transport.Endpoint) (transport.AuthMethod, error) { 23 | return nil, nil // the default one works already 24 | } 25 | 26 | var progressWriter = func() io.Writer { 27 | return os.Stdout 28 | } 29 | -------------------------------------------------------------------------------- /cli/blame.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "strconv" 10 | 11 | git "github.com/go-git/go-git/v5" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func init() { 16 | cmd := &cobra.Command{ 17 | Use: "blame file", 18 | Short: "Show what revision and author last modified each line of a file", 19 | Long: ``, 20 | Args: cobra.ExactArgs(1), 21 | RunE: blameCmd, 22 | } 23 | rootCmd.AddCommand(cmd) 24 | } 25 | 26 | func blameCmd(_ *cobra.Command, args []string) error { 27 | _, r, err := openRepo() 28 | if err != nil { 29 | return err 30 | } 31 | head, err := r.Head() 32 | if err != nil { 33 | return err 34 | } 35 | commit, err := r.CommitObject(head.Hash()) 36 | if err != nil { 37 | return err 38 | } 39 | result, err := git.Blame(commit, args[0]) 40 | if err != nil { 41 | return err 42 | } 43 | pad := len(strconv.Itoa(len(result.Lines) - 1)) 44 | for i, l := range result.Lines { 45 | fmt.Printf("%v (%v %v %*d) %v\n", l.Hash, l.Author, l.Date, pad, i, l.Text) 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /cli/branch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/go-git/go-git/v5/plumbing" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | var bc branchCmd 16 | 17 | cmd := &cobra.Command{ 18 | Use: "branch [name...]", 19 | Aliases: []string{"br"}, 20 | Short: "List, create, or delete branches", 21 | Long: `With no arguments, list existing branches. The current branch is 22 | prefixed with an asterisk. 23 | 24 | If one argument is given, create a new branch named name which points 25 | to the current HEAD. 26 | `, 27 | RunE: bc.run, 28 | } 29 | rootCmd.AddCommand(cmd) 30 | 31 | cmd.Flags().BoolVarP(&bc.forceDelete, "force-delete", "D", false, "Force delete a branch") 32 | } 33 | 34 | type branchCmd struct { 35 | forceDelete bool // -D 36 | } 37 | 38 | func (bc *branchCmd) run(_ *cobra.Command, args []string) error { 39 | _, r, err := openRepo() 40 | if err != nil { 41 | return err 42 | } 43 | head, err := r.Head() 44 | if err == plumbing.ErrReferenceNotFound { 45 | // maybe there are no commits yet 46 | head = nil 47 | } else if err != nil { 48 | return fmt.Errorf("HEAD: %v", err) 49 | } 50 | 51 | if bc.forceDelete { 52 | if len(args) == 0 { 53 | return fmt.Errorf("branch name required") 54 | } 55 | for _, name := range args { 56 | br := plumbing.NewBranchReferenceName(name) 57 | if head != nil && head.Name() == br { 58 | return fmt.Errorf("cannot delete checked out branch %q", br) 59 | } 60 | if err := r.Storer.RemoveReference(br); err != nil { 61 | return err 62 | } 63 | } 64 | return nil 65 | } 66 | 67 | if len(args) == 1 { 68 | if head == nil { 69 | return fmt.Errorf("reference to HEAD not found") 70 | } 71 | return r.Storer.SetReference(plumbing.NewHashReference( 72 | plumbing.NewBranchReferenceName(args[0]), 73 | head.Hash(), 74 | )) 75 | } 76 | 77 | if len(args) != 0 { 78 | return fmt.Errorf("accepts 0 args, received %v", len(args)) 79 | } 80 | bIter, err := r.Branches() 81 | if err != nil { 82 | return err 83 | } 84 | return bIter.ForEach(func(ref *plumbing.Reference) error { 85 | if head != nil && ref.Name() == head.Name() { 86 | fmt.Printf("* %v\n", ref) 87 | } else { 88 | fmt.Printf(" %v\n", ref) 89 | } 90 | return nil 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /cli/checkout.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | git "github.com/go-git/go-git/v5" 9 | "github.com/go-git/go-git/v5/plumbing" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func init() { 14 | var cc checkoutCmd 15 | 16 | cmd := &cobra.Command{ 17 | Use: "checkout branch", 18 | Aliases: []string{"co"}, 19 | Short: "Switch branches", 20 | Long: ``, 21 | Args: cobra.ExactArgs(1), 22 | RunE: cc.run, 23 | } 24 | rootCmd.AddCommand(cmd) 25 | 26 | cmd.Flags().BoolVarP(&cc.create, "create", "b", false, "Create branch before checkout") 27 | } 28 | 29 | type checkoutCmd struct { 30 | create bool 31 | } 32 | 33 | func (cc *checkoutCmd) run(cmd *cobra.Command, args []string) error { 34 | _, r, err := openRepo() 35 | if err != nil { 36 | return err 37 | } 38 | w, err := r.Worktree() 39 | if err != nil { 40 | return err 41 | } 42 | return w.Checkout(&git.CheckoutOptions{ 43 | Hash: plumbing.ZeroHash, 44 | Branch: plumbing.NewBranchReferenceName(args[0]), 45 | Create: cc.create, 46 | Force: false, 47 | Keep: true, 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /cli/clone.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "path" 10 | "strings" 11 | 12 | "github.com/go-git/go-git/v5" 13 | "github.com/go-git/go-git/v5/plumbing/transport" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func init() { 18 | cmd := &cobra.Command{ 19 | Use: "clone url [directory]", 20 | Short: "Clone a repository into a new directory", 21 | Long: `Clone a repository into a new directory. If the directory is not privided, 22 | it's derived from the URL.`, 23 | Args: cobra.MaximumNArgs(2), 24 | RunE: cloneCmd, 25 | } 26 | rootCmd.AddCommand(cmd) 27 | } 28 | 29 | func cloneCmd(_ *cobra.Command, args []string) error { 30 | if len(args) < 1 { 31 | return fmt.Errorf("no URL provided") 32 | } 33 | url := args[0] 34 | 35 | ep, err := transport.NewEndpoint(url) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | dir := "" 41 | if len(args) >= 2 { 42 | dir = args[1] 43 | } else { 44 | dir = cloneDir(ep.Path) 45 | } 46 | 47 | auth, err := endpointAuth(ep) 48 | if err != nil { 49 | return err 50 | } 51 | fmt.Printf("Cloning into %q...\n", dir) 52 | _, err = git.PlainClone(dir, false, &git.CloneOptions{ 53 | URL: url, 54 | Auth: auth, 55 | Progress: progressWriter(), 56 | }) 57 | return err 58 | } 59 | 60 | // cloneDir return the directory where newly cloned repository will be stored 61 | // based on the full path in git URL. For example, it converts 62 | // fhs/gig to gig, and fhs/gig.git to gig. 63 | func cloneDir(dir string) string { 64 | d := path.Base(dir) 65 | if suf := ".git"; strings.HasSuffix(d, suf) && len(d) > len(suf) { 66 | d = d[:len(d)-len(suf)] 67 | } 68 | return d 69 | } 70 | -------------------------------------------------------------------------------- /cli/commit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "os/exec" 14 | "path/filepath" 15 | "strings" 16 | "time" 17 | 18 | "github.com/go-git/go-git/v5" 19 | "github.com/go-git/go-git/v5/plumbing/object" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | func init() { 24 | var cc commitCmd 25 | 26 | cmd := &cobra.Command{ 27 | Use: "commit", 28 | Aliases: []string{"ci"}, 29 | Short: "Record changes to the repository", 30 | Long: `If the commit message (-m flag) is empty, a text editor will be opened to 31 | edit the commit message. The text editor is configured by the either the 32 | $GIT_EDITOR, $VISUAL, or $EDITOR environment variable, in that order, 33 | falling back to a default editor if all three environment variables 34 | are empty. 35 | `, 36 | RunE: cc.run, 37 | } 38 | cmd.Flags().StringVarP(&cc.message, "message", "m", "", "Commit message") 39 | cmd.Flags().BoolVarP(&cc.all, "all", "a", false, "Stage modified/deleted files before commit") 40 | rootCmd.AddCommand(cmd) 41 | } 42 | 43 | type commitCmd struct { 44 | message string 45 | all bool 46 | } 47 | 48 | func (cc *commitCmd) run(_ *cobra.Command, args []string) error { 49 | root, r, err := openRepo() 50 | if err != nil { 51 | return err 52 | } 53 | w, err := r.Worktree() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | status, err := w.Status() 59 | if err != nil { 60 | return err 61 | } 62 | if nothingInStaging(status) { 63 | if !cc.all { 64 | return fmt.Errorf(`nothing to commit (use "gig add")`) 65 | } 66 | if nothingInWorktree(status) { 67 | return fmt.Errorf(`nothing to commit, working tree clean`) 68 | } 69 | } 70 | 71 | name := os.Getenv("GIT_AUTHOR_NAME") 72 | email := os.Getenv("GIT_AUTHOR_EMAIL") 73 | if name == "" || email == "" { 74 | cfg, err := loadRepoConfig(r) 75 | if err != nil { 76 | return err 77 | } 78 | if name == "" { 79 | name = cfg.User.Name 80 | } 81 | if email == "" { 82 | email = cfg.User.Email 83 | } 84 | } 85 | if name == "" || email == "" { 86 | fmt.Fprintf(os.Stderr, "%v\n", unknownUserMsg) 87 | return fmt.Errorf("user's name and/or email are empty") 88 | } 89 | if cc.message == "" { 90 | mfile := filepath.Join(root, ".git", "COMMIT_EDITMSG") 91 | err := ioutil.WriteFile(mfile, []byte(emptyCommit), 0644) 92 | if err != nil { 93 | return err 94 | } 95 | err = editFile(mfile) 96 | if err != nil { 97 | return fmt.Errorf("editor failed: %v", err) 98 | } 99 | msg, err := readCommit(mfile) 100 | if err != nil { 101 | return err 102 | } 103 | if msg == "" { 104 | return fmt.Errorf("aborting commit due to empty commit message") 105 | } 106 | cc.message = msg 107 | } 108 | _, err = w.Commit(cc.message, &git.CommitOptions{ 109 | All: cc.all, 110 | Author: &object.Signature{ 111 | Name: name, 112 | Email: email, 113 | When: time.Now(), 114 | }, 115 | }) 116 | return err 117 | } 118 | 119 | func nothingInStaging(s git.Status) bool { 120 | for _, status := range s { 121 | switch status.Staging { 122 | case git.Unmodified, git.Untracked: 123 | default: 124 | return false 125 | } 126 | } 127 | return true 128 | } 129 | 130 | func nothingInWorktree(s git.Status) bool { 131 | for _, status := range s { 132 | switch status.Worktree { 133 | case git.Unmodified, git.Untracked: 134 | default: 135 | return false 136 | } 137 | } 138 | return true 139 | } 140 | 141 | func editFile(filename string) error { 142 | args := strings.Fields(preferredEditor()) 143 | if len(args) == 0 { 144 | panic("internal error: empty editor") 145 | } 146 | args = append(args, filename) 147 | cmd := exec.Command(args[0], args[1:]...) 148 | cmd.Stdin = os.Stdin 149 | cmd.Stdout = os.Stdout 150 | cmd.Stderr = os.Stderr 151 | return cmd.Run() 152 | } 153 | 154 | func readCommit(filename string) (string, error) { 155 | f, err := os.Open(filename) 156 | if err != nil { 157 | return "", err 158 | } 159 | defer f.Close() 160 | 161 | scanner := bufio.NewScanner(f) 162 | var msg []byte 163 | for scanner.Scan() { 164 | b := append(scanner.Bytes(), '\n') 165 | if b[0] != '#' { 166 | msg = append(msg, b...) 167 | } 168 | } 169 | if err := scanner.Err(); err != nil { 170 | return "", err 171 | } 172 | msg = bytes.TrimFunc(msg, func(r rune) bool { return r == '\n' }) 173 | if len(msg) > 0 { 174 | msg = append(msg, '\n') 175 | } 176 | return string(msg), nil 177 | } 178 | 179 | func preferredEditor() string { 180 | for _, name := range []string{ 181 | "GIT_EDITOR", 182 | "VISUAL", 183 | "EDITOR", 184 | } { 185 | if e := os.Getenv(name); e != "" { 186 | return e 187 | } 188 | } 189 | return defaultEditor() 190 | } 191 | 192 | var unknownUserMsg = `Author identity unknown 193 | 194 | *** Please tell me who you are. 195 | 196 | To set your account's default identity, write a configuration file like 197 | the following to .git/config file or the global /.gitconfig file: 198 | 199 | [user] 200 | email = gitster@example.com 201 | name = Junio C Hamano 202 | 203 | Alternatively, set the GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL environment 204 | variables instead. 205 | ` 206 | 207 | var emptyCommit = ` 208 | # Please enter the commit message for your changes. Lines starting 209 | # with '#' will be ignored, and an empty message aborts the commit. 210 | ` 211 | -------------------------------------------------------------------------------- /cli/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func init() { 14 | var cc configCmd 15 | 16 | cmd := &cobra.Command{ 17 | Use: "config", 18 | Short: "Get repository or global options", 19 | Long: `Gig uses the same configuration files as git(1).`, 20 | RunE: cc.run, 21 | } 22 | rootCmd.AddCommand(cmd) 23 | cmd.Flags().BoolVarP(&cc.list, "list", "l", false, "List all variables set in config file") 24 | } 25 | 26 | type configCmd struct { 27 | list bool 28 | } 29 | 30 | func (cc *configCmd) run(_ *cobra.Command, args []string) error { 31 | if !cc.list { 32 | return fmt.Errorf("no flags given") 33 | } 34 | _, r, err := openRepo() 35 | if err != nil { 36 | return err 37 | } 38 | cfg, err := loadRepoConfig(r) 39 | if err != nil { 40 | return err 41 | } 42 | fmt.Printf("user.name=%v\n", cfg.User.Name) 43 | fmt.Printf("user.email=%v\n", cfg.User.Email) 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /cli/diff.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | 14 | git "github.com/go-git/go-git/v5" 15 | "github.com/go-git/go-git/v5/plumbing" 16 | "github.com/go-git/go-git/v5/plumbing/filemode" 17 | fdiff "github.com/go-git/go-git/v5/plumbing/format/diff" 18 | "github.com/go-git/go-git/v5/plumbing/format/index" 19 | "github.com/go-git/go-git/v5/plumbing/object" 20 | "github.com/go-git/go-git/v5/utils/diff" 21 | "github.com/sergi/go-diff/diffmatchpatch" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | func init() { 26 | cmd := &cobra.Command{ 27 | Use: "diff [commit]", 28 | Aliases: []string{"di"}, 29 | Short: "Show changes between working tree, index, commits, etc.", 30 | Long: `If a commit argument is given, compare working tree with that commit. 31 | Otherwise, compare working tree with the index (staging area for the 32 | next commit).`, 33 | Args: cobra.MaximumNArgs(1), 34 | RunE: diffCmd, 35 | } 36 | rootCmd.AddCommand(cmd) 37 | } 38 | 39 | func diffCmd(_ *cobra.Command, args []string) error { 40 | root, r, err := openRepo() 41 | if err != nil { 42 | return err 43 | } 44 | 45 | if len(args) == 1 { 46 | return diffWithCommit(os.Stdout, r, root, plumbing.Revision(args[0])) 47 | } 48 | return diffWithIndex(os.Stdout, r, root) 49 | } 50 | 51 | func diffWithIndex(w io.Writer, r *git.Repository, root string) error { 52 | idx, err := r.Storer.Index() 53 | if err != nil { 54 | return err 55 | } 56 | iter := &indexEntriesIter{ 57 | idx: idx, 58 | r: r, 59 | k: 0, 60 | } 61 | return worktreeDiff(w, iter, root) 62 | } 63 | 64 | func diffWithCommit(w io.Writer, r *git.Repository, root string, rev plumbing.Revision) error { 65 | hash, err := r.ResolveRevision(rev) 66 | if err != nil { 67 | return err 68 | } 69 | commit, err := r.CommitObject(*hash) 70 | if err != nil { 71 | return err 72 | } 73 | tree, err := commit.Tree() 74 | if err != nil { 75 | return err 76 | } 77 | return worktreeDiff(w, tree.Files(), root) 78 | } 79 | 80 | type fileIter interface { 81 | Next() (*object.File, error) 82 | } 83 | 84 | type indexEntriesIter struct { 85 | idx *index.Index 86 | r *git.Repository 87 | k int 88 | } 89 | 90 | func (i *indexEntriesIter) Next() (*object.File, error) { 91 | entries := i.idx.Entries 92 | if i.k >= len(entries) { 93 | return nil, io.EOF 94 | } 95 | if i.k < 0 { 96 | return nil, fmt.Errorf("index %v out of range", i.k) 97 | } 98 | e := entries[i.k] 99 | b, err := i.r.BlobObject(e.Hash) 100 | if err != nil { 101 | return nil, err 102 | } 103 | i.k++ 104 | return object.NewFile(e.Name, e.Mode, b), nil 105 | } 106 | 107 | func worktreeDiff(w io.Writer, iter fileIter, root string) error { 108 | var filePatches []fdiff.FilePatch 109 | for { 110 | file, err := iter.Next() 111 | if err == io.EOF { 112 | break 113 | } 114 | if err != nil { 115 | return err 116 | } 117 | fromContent, err := file.Contents() 118 | if err != nil { 119 | return err 120 | } 121 | b, err := ioutil.ReadFile(filepath.Join(root, file.Name)) 122 | if err != nil { 123 | if !os.IsNotExist(err) { 124 | return err 125 | } 126 | b = nil 127 | } 128 | toContent := string(b) 129 | if fromContent != toContent { 130 | fp, err := fileDiff(os.Stdout, file, fromContent, toContent) 131 | if err != nil { 132 | return err 133 | } 134 | filePatches = append(filePatches, fp) 135 | } 136 | } 137 | ue := fdiff.NewUnifiedEncoder(w, fdiff.DefaultContextLines) 138 | return ue.Encode(&gigPatch{ 139 | message: "", 140 | filePatches: filePatches, 141 | }) 142 | } 143 | 144 | func fileDiff(w io.Writer, f *object.File, a, b string) (fdiff.FilePatch, error) { 145 | diffs := diff.Do(a, b) 146 | var chunks []fdiff.Chunk 147 | for _, d := range diffs { 148 | var op fdiff.Operation 149 | switch d.Type { 150 | case diffmatchpatch.DiffEqual: 151 | op = fdiff.Equal 152 | case diffmatchpatch.DiffDelete: 153 | op = fdiff.Delete 154 | case diffmatchpatch.DiffInsert: 155 | op = fdiff.Add 156 | } 157 | chunks = append(chunks, &gigChunk{content: d.Text, op: op}) 158 | } 159 | 160 | isBinary, err := f.IsBinary() 161 | if err != nil { 162 | return nil, err 163 | } 164 | fp := &gigFilePatch{ 165 | isBinary: isBinary, 166 | from: &gigFile{ 167 | hash: f.Hash, 168 | mode: f.Mode, 169 | path: f.Name, 170 | }, 171 | to: &gigFile{ 172 | hash: f.Hash, // TODO 173 | mode: f.Mode, // TODO 174 | path: f.Name, 175 | }, 176 | chunks: chunks, 177 | } 178 | return fp, nil 179 | } 180 | 181 | type gigPatch struct { 182 | message string 183 | filePatches []fdiff.FilePatch 184 | } 185 | 186 | func (p *gigPatch) FilePatches() []fdiff.FilePatch { return p.filePatches } 187 | func (p *gigPatch) Message() string { return p.message } 188 | 189 | type gigFilePatch struct { 190 | isBinary bool 191 | from, to *gigFile 192 | chunks []fdiff.Chunk 193 | } 194 | 195 | func (fp *gigFilePatch) IsBinary() bool { return fp.isBinary } 196 | func (fp *gigFilePatch) Files() (from, to fdiff.File) { return fp.from, fp.to } 197 | func (fp *gigFilePatch) Chunks() []fdiff.Chunk { return fp.chunks } 198 | 199 | type gigFile struct { 200 | hash plumbing.Hash 201 | mode filemode.FileMode 202 | path string 203 | } 204 | 205 | func (f *gigFile) Hash() plumbing.Hash { return f.hash } 206 | func (f *gigFile) Mode() filemode.FileMode { return f.mode } 207 | func (f *gigFile) Path() string { return f.path } 208 | 209 | type gigChunk struct { 210 | content string 211 | op fdiff.Operation 212 | } 213 | 214 | func (c *gigChunk) Content() string { return c.content } 215 | func (c *gigChunk) Type() fdiff.Operation { return c.op } 216 | -------------------------------------------------------------------------------- /cli/editor_plan9.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import "os" 8 | 9 | func defaultEditor() string { 10 | _, err := os.Stat("/dev/wsys") 11 | if err != nil && os.IsNotExist(err) { // console 12 | return "ed" 13 | } 14 | return "acme -c 1" 15 | } 16 | -------------------------------------------------------------------------------- /cli/editor_posix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !plan9 6 | // +build !plan9 7 | 8 | package cli 9 | 10 | import "os/exec" 11 | 12 | func defaultEditor() string { 13 | for _, e := range []string{ 14 | "vim", 15 | "vi", 16 | "nano", 17 | } { 18 | if p, err := exec.LookPath(e); err == nil { 19 | return p 20 | } 21 | } 22 | return "ed" 23 | } 24 | -------------------------------------------------------------------------------- /cli/fetch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | git "github.com/go-git/go-git/v5" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | cmd := &cobra.Command{ 16 | Use: "fetch [remote]", 17 | Short: "Download objects and refs from another repository", 18 | Long: `If remote is not specified, the remote named origin is used.`, 19 | Args: cobra.MaximumNArgs(1), 20 | RunE: fetchCmd, 21 | } 22 | rootCmd.AddCommand(cmd) 23 | } 24 | 25 | func fetchCmd(cmd *cobra.Command, args []string) error { 26 | _, r, err := openRepo() 27 | if err != nil { 28 | return err 29 | } 30 | remote := "origin" 31 | if len(args) >= 1 { 32 | remote = args[0] 33 | } 34 | auth, err := remoteAuth(r, remote) 35 | if err != nil { 36 | return err 37 | } 38 | err = r.Fetch(&git.FetchOptions{ 39 | RemoteName: remote, 40 | Auth: auth, 41 | Progress: progressWriter(), 42 | }) 43 | if err == git.NoErrAlreadyUpToDate { 44 | fmt.Printf("%v\n", err) 45 | return nil 46 | } 47 | return err 48 | } 49 | -------------------------------------------------------------------------------- /cli/git.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/go-git/go-billy/v5/osfs" 13 | "github.com/go-git/go-git/v5" 14 | "github.com/go-git/go-git/v5/config" 15 | "github.com/go-git/go-git/v5/plumbing/cache" 16 | "github.com/go-git/go-git/v5/storage/filesystem" 17 | "github.com/imdario/mergo" 18 | ) 19 | 20 | func findRepoRoot() (string, error) { 21 | p, err := os.Getwd() 22 | if err != nil { 23 | return "", err 24 | } 25 | for { 26 | _, err := os.Stat(filepath.Join(p, ".git")) 27 | if err == nil { 28 | return p, nil 29 | } 30 | if !os.IsNotExist(err) { 31 | return "", err 32 | } 33 | newp := filepath.Join(p, "..") 34 | if newp == p { 35 | break 36 | } 37 | p = newp 38 | } 39 | return "", fmt.Errorf("fatal: not a git repository") 40 | } 41 | 42 | // dotGitDir returns the .git directory within dir. 43 | // If there is no .git directory within dir, it returns dir. 44 | func dotGitDir(dir string) string { 45 | if filepath.Base(dir) == ".git" { 46 | return dir 47 | } 48 | dot := filepath.Join(dir, ".git") 49 | fi, err := os.Stat(dot) 50 | if err == nil && fi.IsDir() { 51 | return dot 52 | } 53 | return dir 54 | } 55 | 56 | func openRepo() (string, *git.Repository, error) { 57 | root, err := findRepoRoot() 58 | if err != nil { 59 | return "", nil, err 60 | } 61 | r, err := git.Open( 62 | filesystem.NewStorage( 63 | osfs.New(filepath.Join(root, ".git")), 64 | cache.NewObjectLRUDefault(), 65 | ), 66 | osfs.New(root), 67 | ) 68 | return root, r, err 69 | } 70 | 71 | func repoRelPath(root, filename string) (string, error) { 72 | filename, err := filepath.Abs(filename) 73 | if err != nil { 74 | return "", err 75 | } 76 | return filepath.Rel(root, filename) 77 | } 78 | 79 | // loadRepoConfig loads repostory configuration file (.git/config), 80 | // falling back to user config file at UserConfigDir/gig/config for empty values. 81 | // 82 | // Note: we don't use git config file at HOME/.gitconfig, unlike go-git's 83 | // global-scoped config file loader. 84 | func loadRepoConfig(r *git.Repository) (*config.Config, error) { 85 | cfg, err := r.Storer.Config() 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | dir, err := gigConfigDir() 91 | if err != nil { 92 | return nil, err 93 | } 94 | f, err := os.Open(filepath.Join(dir, "config")) 95 | if err != nil { 96 | if os.IsNotExist(err) { 97 | return cfg, nil 98 | } 99 | return nil, err 100 | } 101 | defer f.Close() 102 | global, err := config.ReadConfig(f) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | _ = mergo.Merge(cfg, global) 108 | return cfg, nil 109 | } 110 | 111 | func gigConfigDir() (string, error) { 112 | dir, err := os.UserConfigDir() 113 | if err != nil { 114 | return "", err 115 | } 116 | return filepath.Join(dir, "gig"), nil 117 | } 118 | -------------------------------------------------------------------------------- /cli/grep.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "regexp" 10 | 11 | git "github.com/go-git/go-git/v5" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func init() { 16 | var gc grepCmd 17 | 18 | cmd := &cobra.Command{ 19 | Use: "grep pattern", 20 | Short: "Print lines matching a pattern", 21 | Long: `Search for the given regular expression pattern in the repository files.`, 22 | Args: cobra.ExactArgs(1), 23 | RunE: gc.run, 24 | } 25 | rootCmd.AddCommand(cmd) 26 | 27 | cmd.Flags().BoolVarP(&gc.lineNumber, "line-number", "n", false, "Print line number") 28 | cmd.Flags().BoolVarP(&gc.invertMatch, "invert-match", "v", false, "Select non-matching lines") 29 | } 30 | 31 | type grepCmd struct { 32 | lineNumber bool 33 | invertMatch bool 34 | } 35 | 36 | func (gc *grepCmd) run(_ *cobra.Command, args []string) error { 37 | _, r, err := openRepo() 38 | if err != nil { 39 | return err 40 | } 41 | w, err := r.Worktree() 42 | if err != nil { 43 | return err 44 | } 45 | reg, err := regexp.Compile(args[0]) 46 | if err != nil { 47 | return err 48 | } 49 | results, err := w.Grep(&git.GrepOptions{ 50 | Patterns: []*regexp.Regexp{reg}, 51 | InvertMatch: gc.invertMatch, 52 | }) 53 | if err != nil { 54 | return err 55 | } 56 | for _, res := range results { 57 | if gc.lineNumber { 58 | fmt.Printf("%v:%v:%v\n", res.FileName, res.LineNumber, res.Content) 59 | } else { 60 | fmt.Printf("%v:%v\n", res.FileName, res.Content) 61 | } 62 | } 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /cli/init.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "path/filepath" 10 | 11 | "github.com/go-git/go-billy/v5/osfs" 12 | "github.com/go-git/go-git/v5" 13 | "github.com/go-git/go-git/v5/storage/filesystem" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func init() { 18 | cmd := &cobra.Command{ 19 | Use: "init [directory]", 20 | Short: "Create an empty Git repository", 21 | Long: ``, 22 | RunE: initCmd, 23 | } 24 | rootCmd.AddCommand(cmd) 25 | } 26 | 27 | func initCmd(_ *cobra.Command, args []string) error { 28 | if len(args) > 1 { 29 | return fmt.Errorf("usage: gig init [directory]") 30 | } 31 | dir := "." 32 | if len(args) > 0 { 33 | dir = args[0] 34 | } 35 | dotgit := filepath.Join(dir, ".git") 36 | fs := osfs.New(dotgit) 37 | s := filesystem.NewStorage(fs, nil) 38 | _, err := git.Init(s, nil) 39 | if err != nil { 40 | return err 41 | } 42 | fmt.Printf("Initialized empty Git repository in %v\n", dotgit) 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /cli/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "path/filepath" 12 | "strings" 13 | 14 | "github.com/go-git/go-git/v5" 15 | "github.com/go-git/go-git/v5/plumbing/object" 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | func init() { 20 | var lc logCmd 21 | 22 | cmd := &cobra.Command{ 23 | Use: "log", 24 | Short: "Show commit logs", 25 | Long: ``, 26 | RunE: lc.run, 27 | } 28 | rootCmd.AddCommand(cmd) 29 | cmd.Flags().IntVarP(&lc.n, "max-count", "n", -1, "Limit the number of commits to output") 30 | cmd.Flags().StringVar(&lc.format, "format", "", "Print commits in the given format") 31 | } 32 | 33 | type logCmd struct { 34 | n int 35 | format string 36 | } 37 | 38 | func (lc *logCmd) run(_ *cobra.Command, args []string) error { 39 | p, err := findRepoRoot() 40 | if err != nil { 41 | return err 42 | } 43 | r, err := git.PlainOpen(filepath.Join(p, ".git")) 44 | if err != nil { 45 | return err 46 | } 47 | ref, err := r.Head() 48 | if err != nil { 49 | return err 50 | } 51 | iter, err := r.Log(&git.LogOptions{From: ref.Hash()}) 52 | if err != nil { 53 | return err 54 | } 55 | defer iter.Close() 56 | for i := 0; lc.n < 0 || i < lc.n; i++ { 57 | c, err := iter.Next() 58 | if err == io.EOF { 59 | break 60 | } 61 | if err != nil { 62 | return err 63 | } 64 | if lc.format == "" { 65 | fmt.Println(c) 66 | } else { 67 | var b bytes.Buffer 68 | if err := formatCommit(&b, lc.format, c); err != nil { 69 | return err 70 | } 71 | fmt.Println(b.String()) 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | func formatCommit(w io.Writer, format string, c *object.Commit) error { 78 | i := strings.IndexByte(format, ':') 79 | if i < 0 || format[:i] != "format" { 80 | return fmt.Errorf("unsupported format string %q", format) 81 | } 82 | format = format[i+1:] 83 | 84 | fb := []byte(format) 85 | for len(fb) > 0 { 86 | i := bytes.IndexByte(fb, '%') 87 | if i < 0 || i == len(fb)-1 { 88 | _, err := w.Write(fb) 89 | return err 90 | } 91 | if i > 0 { 92 | _, err := w.Write(fb[:i]) 93 | if err != nil { 94 | return err 95 | } 96 | fb = fb[i:] 97 | } 98 | fb = fb[1:] // skip '%' 99 | 100 | i = 0 101 | var err error 102 | switch fb[0] { 103 | case '%': 104 | _, err = fmt.Fprintf(w, "%%") 105 | i++ 106 | 107 | case 'H', 'h': // commit hash 108 | // TODO: %h should be abbreviated commit hash, but 109 | // we're just trying to get `go tool dist` working for now. 110 | _, err = fmt.Fprintf(w, "%v", c.Hash) 111 | i++ 112 | 113 | case 'c': 114 | if len(fb) > 1 { 115 | switch fb[1] { 116 | case 'd': 117 | _, err = fmt.Fprintf(w, "%v", c.Committer.When) 118 | i += 2 119 | } 120 | } 121 | } 122 | if i == 0 { // no expansion 123 | _, err = fmt.Fprintf(w, "%%") 124 | } 125 | if err != nil { 126 | return err 127 | } 128 | fb = fb[i:] 129 | } 130 | return nil 131 | } 132 | -------------------------------------------------------------------------------- /cli/log_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | "time" 11 | 12 | "github.com/go-git/go-git/v5/plumbing" 13 | "github.com/go-git/go-git/v5/plumbing/object" 14 | ) 15 | 16 | func TestFormatCommit(t *testing.T) { 17 | for _, tc := range []struct { 18 | format string 19 | commit object.Commit 20 | want string 21 | }{ 22 | { 23 | "format: +%h %cd HEAD", 24 | object.Commit{ 25 | Hash: plumbing.NewHash("9567f7269a075b80e01f4611402344b924a40c35"), 26 | Committer: object.Signature{ 27 | Name: "", 28 | Email: "", 29 | When: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), 30 | }, 31 | }, 32 | " +9567f7269a075b80e01f4611402344b924a40c35 2009-11-10 23:00:00 +0000 UTC HEAD", 33 | }, 34 | } { 35 | var b bytes.Buffer 36 | if err := formatCommit(&b, tc.format, &tc.commit); err != nil { 37 | t.Fatal(err) 38 | } 39 | if got, want := b.String(), tc.want; got != want { 40 | t.Errorf("formatting %q: got %v, want %v", tc.format, got, want) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cli/ls-files.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func init() { 14 | var lfc lsFilesCmd 15 | 16 | cmd := &cobra.Command{ 17 | Use: "ls-files", 18 | Short: "Show information about files in the index", 19 | Long: ``, 20 | RunE: lfc.run, 21 | } 22 | rootCmd.AddCommand(cmd) 23 | cmd.Flags().BoolVarP(&lfc.stage, "stage", "s", false, "Show staged contents' mode bits, object name and stage number") 24 | } 25 | 26 | type lsFilesCmd struct { 27 | stage bool 28 | } 29 | 30 | func (lfc *lsFilesCmd) run(_ *cobra.Command, args []string) error { 31 | _, r, err := openRepo() 32 | if err != nil { 33 | return err 34 | } 35 | idx, err := r.Storer.Index() 36 | if err != nil { 37 | return err 38 | } 39 | for _, e := range idx.Entries { 40 | if lfc.stage { 41 | fmt.Printf("%06o %s %d\t%s\n", e.Mode, e.Hash, e.Stage, e.Name) 42 | } else { 43 | fmt.Printf("%v\n", e.Name) 44 | } 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /cli/ls-remote.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "sort" 11 | 12 | git "github.com/go-git/go-git/v5" 13 | "github.com/go-git/go-git/v5/plumbing" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func init() { 18 | cmd := &cobra.Command{ 19 | Use: "ls-remote [remote]", 20 | Short: "List references in a remote repository", 21 | Long: `List references in a remote repository along with the associted commit 22 | hash. If the remote argument is not provided, origin is used.`, 23 | Args: cobra.MaximumNArgs(1), 24 | RunE: lsRemoteCmd, 25 | } 26 | rootCmd.AddCommand(cmd) 27 | } 28 | 29 | func lsRemoteCmd(cmd *cobra.Command, args []string) error { 30 | _, r, err := openRepo() 31 | if err != nil { 32 | return err 33 | } 34 | name := "origin" 35 | if len(args) >= 1 { 36 | name = args[0] 37 | } 38 | rem, err := r.Remote(name) 39 | if err != nil { 40 | return err 41 | } 42 | auth, err := remoteAuth(r, name) 43 | if err != nil { 44 | return err 45 | } 46 | refs, err := rem.List(&git.ListOptions{ 47 | Auth: auth, 48 | }) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | if cfg := rem.Config(); len(cfg.URLs) > 0 { 54 | fmt.Fprintf(os.Stderr, "From %v\n", cfg.URLs[0]) 55 | } 56 | sort.Slice(refs, func(i, j int) bool { 57 | return refs[i].Name() < refs[j].Name() 58 | }) 59 | m := newRefMap(refs) 60 | for _, ref := range refs { 61 | fmt.Printf("%v\t%v\n", m.resolveHash(ref), ref.Name()) 62 | } 63 | return nil 64 | } 65 | 66 | type refMap map[plumbing.ReferenceName]*plumbing.Reference 67 | 68 | func newRefMap(refs []*plumbing.Reference) refMap { 69 | m := make(refMap, len(refs)) 70 | for _, ref := range refs { 71 | m[ref.Name()] = ref 72 | } 73 | return m 74 | } 75 | 76 | func (m refMap) resolveHash(ref *plumbing.Reference) plumbing.Hash { 77 | for ref.Type() == plumbing.SymbolicReference { 78 | var ok bool 79 | ref, ok = m[ref.Target()] 80 | if !ok { 81 | return plumbing.ZeroHash 82 | } 83 | } 84 | return ref.Hash() 85 | } 86 | -------------------------------------------------------------------------------- /cli/mv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | cmd := &cobra.Command{ 13 | Use: "mv source destination", 14 | Short: "Move or rename a file or directory", 15 | Long: ``, 16 | Args: cobra.ExactArgs(2), 17 | RunE: mvCmd, 18 | } 19 | rootCmd.AddCommand(cmd) 20 | } 21 | 22 | func mvCmd(_ *cobra.Command, args []string) error { 23 | root, r, err := openRepo() 24 | if err != nil { 25 | return err 26 | } 27 | w, err := r.Worktree() 28 | if err != nil { 29 | return err 30 | } 31 | src, err := repoRelPath(root, args[0]) 32 | if err != nil { 33 | return err 34 | } 35 | dst, err := repoRelPath(root, args[1]) 36 | if err != nil { 37 | return err 38 | } 39 | _, err = w.Move(src, dst) 40 | return err 41 | } 42 | -------------------------------------------------------------------------------- /cli/pull.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | git "github.com/go-git/go-git/v5" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | cmd := &cobra.Command{ 16 | Use: "pull [remote]", 17 | Short: "Fetch from and integrate with another repository", 18 | Long: `Incorporate changes from a remote repository into the current branch. 19 | If remote is not specified, the remote named origin is used.`, 20 | Args: cobra.MaximumNArgs(1), 21 | RunE: pullCmd, 22 | } 23 | rootCmd.AddCommand(cmd) 24 | } 25 | 26 | func pullCmd(cmd *cobra.Command, args []string) error { 27 | _, r, err := openRepo() 28 | if err != nil { 29 | return err 30 | } 31 | w, err := r.Worktree() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | remote := "origin" 37 | if len(args) >= 1 { 38 | remote = args[0] 39 | } 40 | auth, err := remoteAuth(r, remote) 41 | if err != nil { 42 | return err 43 | } 44 | err = w.Pull(&git.PullOptions{ 45 | RemoteName: remote, 46 | Auth: auth, 47 | Progress: progressWriter(), 48 | }) 49 | if err == git.NoErrAlreadyUpToDate { 50 | fmt.Printf("%v\n", err) 51 | return nil 52 | } 53 | return err 54 | } 55 | -------------------------------------------------------------------------------- /cli/push.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | git "github.com/go-git/go-git/v5" 11 | "github.com/go-git/go-git/v5/config" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func init() { 16 | cmd := &cobra.Command{ 17 | Use: "push [[repository] refspec...]", 18 | Short: "Update remote refs along with associated objects", 19 | Long: `If not arguments are given, update the remote repository named origin. 20 | 21 | If no refspec is given, pushes current branch.`, 22 | RunE: pushCmd, 23 | } 24 | rootCmd.AddCommand(cmd) 25 | } 26 | 27 | func pushCmd(cmd *cobra.Command, args []string) error { 28 | _, r, err := openRepo() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | remote := "origin" 34 | if len(args) >= 1 { 35 | remote = args[0] 36 | } 37 | 38 | var refspecs []config.RefSpec 39 | if len(args) > 1 { 40 | for _, s := range args[1:] { 41 | refspecs = append(refspecs, config.RefSpec(s)) 42 | } 43 | } else { 44 | // No refspec was specified, so we want to push current branch. 45 | // Nil refspec seems to push all branches. 46 | head, err := r.Head() 47 | if err != nil { 48 | return err 49 | } 50 | refspecs = []config.RefSpec{ 51 | config.RefSpec(head.Name() + ":" + head.Name()), 52 | } 53 | } 54 | auth, err := remoteAuth(r, remote) 55 | if err != nil { 56 | return err 57 | } 58 | err = r.Push(&git.PushOptions{ 59 | RemoteName: remote, 60 | RefSpecs: refspecs, 61 | Auth: auth, 62 | Progress: progressWriter(), 63 | }) 64 | if err == git.NoErrAlreadyUpToDate { 65 | fmt.Printf("%v\n", err) 66 | return nil 67 | } 68 | return err 69 | } 70 | -------------------------------------------------------------------------------- /cli/receive-pack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/go-git/go-git/v5/plumbing/transport/file" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | cmd := &cobra.Command{ 16 | Use: "receive-pack directory", 17 | Short: "Receive what is pushed into the repository", 18 | Long: ``, 19 | RunE: receivePackCmd, 20 | } 21 | rootCmd.AddCommand(cmd) 22 | } 23 | 24 | func receivePackCmd(_ *cobra.Command, args []string) error { 25 | if len(args) == 0 { 26 | return fmt.Errorf("usage: receive-pack ") 27 | } 28 | return file.ServeReceivePack(dotGitDir(args[0])) 29 | } 30 | -------------------------------------------------------------------------------- /cli/remote.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/go-git/go-git/v5/config" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | var rc remoteCmd 16 | 17 | cmd := &cobra.Command{ 18 | Use: "remote", 19 | Short: "Manage set of tracked repositories", 20 | Long: ``, 21 | RunE: rc.list, 22 | } 23 | rootCmd.AddCommand(cmd) 24 | cmd.Flags().BoolVarP(&rc.verbose, "verbose", "v", false, "Be more verbose") 25 | 26 | cmd.AddCommand(&cobra.Command{ 27 | Use: "add name url", 28 | Short: "Add a remote named name for the repository at url", 29 | Long: ``, 30 | Args: cobra.ExactArgs(2), 31 | RunE: rc.add, 32 | }, &cobra.Command{ 33 | Use: "remove name", 34 | Short: "Remove the remote named name", 35 | Long: ``, 36 | Args: cobra.ExactArgs(1), 37 | RunE: rc.remove, 38 | }) 39 | } 40 | 41 | type remoteCmd struct { 42 | verbose bool 43 | } 44 | 45 | func (rc *remoteCmd) list(_ *cobra.Command, args []string) error { 46 | _, repo, err := openRepo() 47 | if err != nil { 48 | return err 49 | } 50 | remotes, err := repo.Remotes() 51 | if err != nil { 52 | return err 53 | } 54 | for _, rem := range remotes { 55 | if rc.verbose { 56 | fmt.Printf("%v\n", rem) 57 | } else { 58 | cfg := rem.Config() 59 | fmt.Printf("%v\n", cfg.Name) 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | func (rc *remoteCmd) add(_ *cobra.Command, args []string) error { 66 | _, repo, err := openRepo() 67 | if err != nil { 68 | return err 69 | } 70 | _, err = repo.CreateRemote(&config.RemoteConfig{ 71 | Name: args[0], 72 | URLs: []string{args[1]}, 73 | }) 74 | return err 75 | } 76 | 77 | func (rc *remoteCmd) remove(_ *cobra.Command, args []string) error { 78 | _, repo, err := openRepo() 79 | if err != nil { 80 | return err 81 | } 82 | return repo.DeleteRemote(args[0]) 83 | } 84 | -------------------------------------------------------------------------------- /cli/reset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | git "github.com/go-git/go-git/v5" 11 | "github.com/go-git/go-git/v5/plumbing" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func init() { 16 | var rc resetCmd 17 | 18 | cmd := &cobra.Command{ 19 | Use: "reset [commit]", 20 | Short: "Reset current HEAD to the specified state", 21 | Long: `Set the current branch head to the specified commit (default HEAD), 22 | and optionally modify the index and the working tree to match depending 23 | on the mode: mixed (default), hard, merge or soft.`, 24 | Args: cobra.MaximumNArgs(1), 25 | RunE: rc.run, 26 | } 27 | rootCmd.AddCommand(cmd) 28 | 29 | cmd.Flags().BoolVar(&rc.mixed, "mixed", false, "Reset index but not working tree (default)") 30 | cmd.Flags().BoolVar(&rc.hard, "hard", false, "Reset index and working tree") 31 | cmd.Flags().BoolVar(&rc.merge, "merge", false, "Reset index but keep unmerged changes in working tree") 32 | cmd.Flags().BoolVar(&rc.soft, "soft", false, "Index and working tree are not changed") 33 | } 34 | 35 | type resetCmd struct { 36 | mixed, hard, merge, soft bool 37 | } 38 | 39 | func (gc *resetCmd) run(_ *cobra.Command, args []string) error { 40 | _, r, err := openRepo() 41 | if err != nil { 42 | return err 43 | } 44 | obj := "HEAD" 45 | if len(args) > 0 { 46 | obj = args[0] 47 | } 48 | h, err := r.ResolveRevision(plumbing.Revision(obj)) 49 | if err != nil { 50 | return err 51 | } 52 | w, err := r.Worktree() 53 | if err != nil { 54 | return err 55 | } 56 | mode, err := gc.mode() 57 | if err != nil { 58 | return err 59 | } 60 | return w.Reset(&git.ResetOptions{ 61 | Commit: *h, 62 | Mode: mode, 63 | }) 64 | } 65 | 66 | func (gc *resetCmd) mode() (git.ResetMode, error) { 67 | var n int 68 | var m git.ResetMode 69 | if gc.mixed { 70 | m = git.MixedReset 71 | n++ 72 | } 73 | if gc.hard { 74 | m = git.HardReset 75 | n++ 76 | } 77 | if gc.merge { 78 | m = git.MergeReset 79 | n++ 80 | } 81 | if gc.soft { 82 | m = git.SoftReset 83 | n++ 84 | } 85 | if n == 0 { 86 | return git.MixedReset, nil 87 | } 88 | if n != 1 { 89 | return 0, fmt.Errorf("exactly one mode should be specified, not %v", n) 90 | } 91 | return m, nil 92 | } 93 | -------------------------------------------------------------------------------- /cli/rev-parse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "path/filepath" 10 | 11 | "github.com/go-git/go-git/v5/plumbing" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func init() { 16 | var rpc revParseCmd 17 | 18 | cmd := &cobra.Command{ 19 | Use: "rev-parse [revision ...]", 20 | Short: "Parse and resolve revision to corresponding hash", 21 | Long: ``, 22 | RunE: rpc.run, 23 | } 24 | rootCmd.AddCommand(cmd) 25 | cmd.Flags().BoolVar(&rpc.abbrevRef, "abbrev-ref", false, "No-op") 26 | cmd.Flags().BoolVar(&rpc.gitDir, "git-dir", false, "Show the path to the .git directory") 27 | } 28 | 29 | type revParseCmd struct { 30 | abbrevRef bool 31 | gitDir bool 32 | } 33 | 34 | func (rpc *revParseCmd) run(_ *cobra.Command, args []string) error { 35 | root, r, err := openRepo() 36 | if err != nil { 37 | return err 38 | } 39 | if rpc.gitDir { 40 | fmt.Printf("%v\n", filepath.Join(root, ".git")) 41 | // don't return here: args may not be empty 42 | } 43 | for _, rev := range args { 44 | h, err := r.ResolveRevision(plumbing.Revision(rev)) 45 | if err != nil { 46 | return err 47 | } 48 | fmt.Printf("%v\n", h) 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /cli/rm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | cmd := &cobra.Command{ 13 | Use: "rm file ...", 14 | Short: "Remove files from the working tree and from the index", 15 | Long: ``, 16 | Args: cobra.MinimumNArgs(1), 17 | RunE: rmCmd, 18 | } 19 | rootCmd.AddCommand(cmd) 20 | } 21 | 22 | func rmCmd(_ *cobra.Command, args []string) error { 23 | root, r, err := openRepo() 24 | if err != nil { 25 | return err 26 | } 27 | w, err := r.Worktree() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | for _, a := range args { 33 | a, err = repoRelPath(root, a) 34 | if err != nil { 35 | return err 36 | } 37 | _, err = w.Remove(a) 38 | if err != nil { 39 | return err 40 | } 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /cli/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "os/exec" 11 | 12 | "github.com/go-git/go-git/v5/plumbing/transport" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // rootCmd represents the base command when called without any subcommands 17 | var rootCmd = &cobra.Command{ 18 | Use: "gig", 19 | Short: "A git clone implemented in Go", 20 | Long: ``, 21 | SilenceUsage: true, 22 | } 23 | 24 | // Execute adds all child commands to the root command and sets flags appropriately. 25 | // This is called by main.main(). It only needs to happen once to the rootCmd. 26 | func Execute() { 27 | if !hasDeps() { 28 | os.Exit(2) 29 | } 30 | if err := rootCmd.Execute(); err != nil { 31 | // The error seems to be already printed by cobra 32 | os.Exit(1) 33 | } 34 | } 35 | 36 | func hasDeps() bool { 37 | ok := true 38 | for _, prog := range []string{ 39 | transport.UploadPackServiceName, 40 | transport.ReceivePackServiceName, 41 | } { 42 | if _, err := exec.LookPath(prog); err != nil { 43 | fmt.Fprintf(os.Stderr, "%v is not installed. Install it from github.com/fhs/gig/cmd/%v.\n", prog, prog) 44 | ok = false 45 | } 46 | } 47 | return ok 48 | } 49 | -------------------------------------------------------------------------------- /cli/show.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "os" 11 | "strings" 12 | 13 | "github.com/go-git/go-git/v5/plumbing" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func init() { 18 | cmd := &cobra.Command{ 19 | Use: "show [object]", 20 | Short: "Show various types of objects", 21 | Long: `Show an object, which can be a reference to a commit or a file within 22 | a commit tree with syntax 'commit:filename'.`, 23 | Args: cobra.MaximumNArgs(1), 24 | RunE: showCmd, 25 | } 26 | rootCmd.AddCommand(cmd) 27 | } 28 | 29 | func showCmd(_ *cobra.Command, args []string) error { 30 | root, r, err := openRepo() 31 | if err != nil { 32 | return err 33 | } 34 | obj := "HEAD" 35 | if len(args) > 0 { 36 | obj = args[0] 37 | } 38 | filename := "" 39 | f := strings.SplitN(obj, ":", 2) 40 | if len(f) >= 2 { 41 | obj = f[0] 42 | filename = f[1] 43 | } 44 | 45 | h, err := r.ResolveRevision(plumbing.Revision(obj)) 46 | if err != nil { 47 | return err 48 | } 49 | commit, err := r.CommitObject(*h) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | if filename != "" { 55 | if strings.HasPrefix(filename, "./") || strings.HasPrefix(filename, "../") { 56 | filename, err = repoRelPath(root, filename) 57 | if err != nil { 58 | return err 59 | } 60 | } 61 | f, err := commit.File(filename) 62 | if err != nil { 63 | return err 64 | } 65 | rd, err := f.Blob.Reader() 66 | if err != nil { 67 | return err 68 | } 69 | defer rd.Close() 70 | _, err = io.Copy(os.Stdout, rd) 71 | return err 72 | } 73 | 74 | fmt.Println(commit.String()) 75 | 76 | // TODO: Implement patch for initial commit (0 parents) 77 | // and merge commits (2 parents). 78 | if commit.NumParents() == 1 { 79 | parent, err := commit.Parent(0) 80 | if err != nil { 81 | return err 82 | } 83 | patch, err := parent.Patch(commit) 84 | if err != nil { 85 | return err 86 | } 87 | fmt.Println(patch) 88 | } 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /cli/status.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func init() { 14 | cmd := &cobra.Command{ 15 | Use: "status", 16 | Aliases: []string{"st"}, 17 | Short: "Show the working tree status", 18 | Long: ``, 19 | RunE: statusCmd, 20 | } 21 | rootCmd.AddCommand(cmd) 22 | } 23 | 24 | func statusCmd(_ *cobra.Command, args []string) error { 25 | _, r, err := openRepo() 26 | if err != nil { 27 | return err 28 | } 29 | w, err := r.Worktree() 30 | if err != nil { 31 | return err 32 | } 33 | // TODO: this is the --porcelain output 34 | // We also need to show paths relative to current working directory, 35 | // not relative to workdir root. 36 | status, err := w.Status() 37 | if err != nil { 38 | return err 39 | } 40 | fmt.Print(status) 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /cli/tag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/go-git/go-git/v5/plumbing" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | var tc tagCmd 16 | 17 | cmd := &cobra.Command{ 18 | Use: "tag [name...]", 19 | Short: "Create, list, or delete a tag", 20 | Long: `If no arguments are given, list existing tags. 21 | 22 | If a tag name is given, create that tag pointing to current HEAD. 23 | `, 24 | RunE: tc.run, 25 | } 26 | rootCmd.AddCommand(cmd) 27 | 28 | cmd.Flags().BoolVarP(&tc.delete, "delete", "d", false, "Delete existing tags with the given names") 29 | } 30 | 31 | type tagCmd struct { 32 | delete bool 33 | } 34 | 35 | func (tc *tagCmd) run(_ *cobra.Command, args []string) error { 36 | _, r, err := openRepo() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | if tc.delete { 42 | for _, name := range args { 43 | if err := r.DeleteTag(name); err != nil { 44 | return err 45 | } 46 | } 47 | return nil 48 | } 49 | 50 | if len(args) == 0 { // list tags 51 | tagrefs, err := r.Tags() 52 | if err != nil { 53 | return err 54 | } 55 | return tagrefs.ForEach(func(t *plumbing.Reference) error { 56 | fmt.Println(t) 57 | return nil 58 | }) 59 | } 60 | 61 | if got, want := len(args), 1; got != want { 62 | return fmt.Errorf("expected %v argument but got %v", want, got) 63 | } 64 | head, err := r.Head() 65 | if err != nil { 66 | return fmt.Errorf("HEAD: %v", err) 67 | } 68 | _, err = r.CreateTag(args[0], head.Hash(), nil) 69 | return err 70 | } 71 | -------------------------------------------------------------------------------- /cli/testdata/factotum.keys: -------------------------------------------------------------------------------- 1 | key proto=rsa service=ssh size=2048 ek=10001 !dk=5813B2000AECBAE9AA5E2C8EF3D8D01CE7F4D81D7CD86113B7B55621A13DBF002E0C77E46CF308643B71D15795078F89C35A061CA5113DB6B5B62CF8018E1AFD648CD7C1D0ADF3C8B2A7598C25C4C5FEEAB711D09E1D5FA1090F1F4F3B32BC593408D56D4689B5D61BBBC166788506A2186E789164438979B86480D9F565E300B5A79302543490B0CBCEE08CAAB0784598A03E17106D98E2A70831CDA8B49FF74406CFE50F47D9931E12811F37A44D481801962D4AAD8B0DB0D138D8646C52A5C7B88C497DB16F488287CED12B5E3C05BA8CDDA6B7769F516F32687EC68D519995528FCB8B34542710D4BE5BD4F648E40641AE808F47F0161DA07948AA9CB379 n=9F00DA93105BC53BC2327F439471E65461BFEDA01DF0DA2E9E1F395BD8407BA51FD443BB3CB8083B3495D760ACD8BF3255AD67F0CA661636731AE4A5C3288089321E56BDC41290415F75C23CB5F85712ABC7DF2A30AF6E8DA57FB3F135805FC33036900DD972ABD99F0D2C72E69A629EA4D4F12416F792415AD238C17CE81E58F7C5C26945975E9E794DDD51A1A2E4A3D0923E015289EC6A7C2EAD89BD44AB36F89984C87DE9E888256211BBD18E4D0BA57DEC6CBD57E6AE9C2C6873A693D960DD112E309268198A1D0F66EEAE9115A49A65EFA783F524674F28D023C60D85D30AD2A9E85C718BE9C6AB41DE91B3D5D64215A9861AE005A5F3CD1DBB4676530F !p=87146BCB108EB2A5AB0E29170F27C5A5440BCC09AA562615996C3B392A03BCA76C085AEEFFAD4FF79B5B27C310C7D76DFF76FC966F889D862D0A3EC8E55EAA5776BC722269A556748F99651931A43ACF3DD0F553EA854190FC781D9E6B0E45F44F3388469CB939732F2A894D3E80F5B8C12573B013480A9ABBA7B4055B13CD83 !q=12D56E179909102DE4094D0F42E0AB1F5A67BDCCCA50BA247FEE9482006FDFDE9C6C1F2B6790C185082FDF4F407AF1E602EF240B5222CB1FCDCB9B6E8CEECE8E631DB4669194DC9A2BAE8457B4B70AF4833DA95968C46AD5965C092B1EDE60D6FC289D1F4092FA6437945F5132378BB30A7BB6C2B0C1506E223469CFD7239DA85 !kp=60EE327BC121E4E824DB40791F92F6AD252B21FB3D941C381BA422CB5E6ED80D7BE472FC55D45431E5F57F8EE697C0EB5A0A542ECF524557673296E32A54C7FF8360AEB62B014E0CCE8ED09147A205E179065D451E61092FB53BAC6565851485BFDFC6C684E81565A64EEBBFD63E4C158BE408242D71E0CA3026A373C46B6D69 !kq=117D3480A151F33E4A3600E31D1D788AE2EED727A6F0140AF4E9DDEA62F180983DF5817452F5EF5ED3DD70A72D9F7DA6342F51EF595489FF7CA18832BE98BC2329135A84ACC7E99B18D93478258E9C93B693BA0116F2DEAA7CB7F6A39E44AEF4B4B01A47FD34FF63B4E1092A6351855EBB6B6AA99C6DC77C9E32C1D78956BF7E9 !c2=F976C63064B8C4F922ECCC43735A845C3248090E34EC842261D91ED0027E0CF2FB493E7A6BCBF50BC23CB04D2D23F7730CE8FD377351A1434C6FACBF0B29C7BFE78B0A24DD231FE33C32D0C7902F9499FDD208CDB0901E5452F4EFEC7A933B9FF2F65DCE91BBE25E5E1D71D420164D730AEC956CE4475F1340F2B1F79BC5E17F 2 | key proto=rsa service=ssh size=2048 ek=10001 !dk=7B99CD4E938C4B4CF6A2A7BBB9F292B69270C37FF05B5CEC6E342542052BD968F6CE7517E991262AFF5A5C0CC23D6824A998BF0671536CC8A2B52964391F2D2759E9AF561EF24F77EB05678DDEC63DB9E09D4A8256694B4646BCF5F406E81C41DB5D1B43B32130B71B2521BB0616BEC806AD08F03E2A4E2A472840A15BFBE38DC7D503492A7347902A701820566AB753A023E7887613629C1FF263982FFC6543FF1E59EA6519EE98B66C9AFF133837FC597E10E20AFEE401BFB82BDA718DEEC3206AC153014750C45715CF9D04F552899956BDF56AB9A0EB3CA76A10F44D6CD8FEFDCD7CF4223E1D1E4E498BDE9B4CB923567DF6406DE4837DCAA657202DCA81 n=F599F46C1CF5538AD7BE8A2DBEE1785D0EBA0CD3F5586ABD220C3A7A8FF312C63D00DA5FA56992F696C6A2B633F57662374667894730B6C68B0B21969D37F302C57FE0B3EFBC0E6C57D7C8638643BFBBAEE5EDD5A0AE9F6C9A743F08975B39CF48B066568424DA13FE4B53D02F93FE505C8F5F86D8C2B325A7B0B42609A26397441F5ADE8158F3F7AF3F1D6F25202A6A9994B23BE3D0588C66B322A6567E9ACF62A8790F1CEBE12B8DECD3D9E3D0F5B55F70D2BEED6AFAFD1EDD210B15F778B742CF0853119362E370D91E2EF8391013C3B1F208ECC20F042CEA3BC43B31F4CF9694A7DFDA77523D46B2CBF21F66BF1227E0CCB3DC19ED160E92760B22544181 !p=8059C893AD6A438C75021F097F02C9F5C53A05A0CC874AA002F75992C8810A7E8B8501EC0B6A9B53C4B817CD4E0F85030D030DD53211A89C1ED466CE88B52C78BD24944DE31393FEDE74F88E928DD519C1BED056CC42B36D5FDFA7A03588C3F0462BC9D82283FB8E4766780D6B782FD8C57B90F6DEDD21BB2877FB2E2BA72599 !q=1E9DC4E0C908E714DDB572191FAE97FB3E7F6D4302ECE0A3790EF300E3BB9EEE1F3C2EA7CB9939FC427FBA4A854EF39BD11817B34BE2EB79A7C78D7A625E659568332D08EF2F6B4062E7EDB928BAEFDB29D8E4BAF9B57E24BDB319D6FBE53B18366668105B4136C8BB96E53594CB0D3C5E757B3C2D6ACF9EA49C393E392969C29 !kp=7BCA1C519227B30E7CAB9713A9941F05133B6AC2338128D03B0C25301481A0857C3AC427CCBAFD2445CEE7E5F32BB4B5C48EF58071D1E634ECE71E60842978C2DA69B53F898B1069F85408863982E1CCFDB7F3684388F154C3EB09D8D2C91D7157BC53E84921D234DF584CE1648D7E582BDF8969107966570428F3F10BD921C1 !kq=1C8021D65F4EBA4EE2551671ECDC4AFEC79F37C739E1E8FC44A575F14E0E4A54C11EF50F9EA875222F85579627A2E7841591A7FE70963D3E72C181227C900360A22F81ACDF7BF50B6B071C75514534238141372C1768AC94E742E2BB0EA2C5F03434008BB9676BE851389706DA0C6BB5D6F8E64F8EB7EFAE6EEE3F1B7D7ADFC9 !c2=2F53D7DB821501A9C82C282DE15D17AF59488093C2FB05699E8C297AB756775A61CDB62E9C970C139DF30A7B111F035E0FCB19BF5BB37F47DB1DE17A47FB4AC9E2E629E6BAC19386B01B2F4396E32704939F5505ECD749D14FB55183B65ECE6C4AD5DC0F6622EDA03B995199A27C20F35816CEA7C45778BF97A5914EF3B8F25D 3 | -------------------------------------------------------------------------------- /cli/upload-pack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package cli 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/go-git/go-git/v5/plumbing/transport/file" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | cmd := &cobra.Command{ 16 | Use: "upload-pack directory", 17 | Short: "Send objects packed back to git-fetch-pack", 18 | Long: ``, 19 | RunE: uploadPackCmd, 20 | } 21 | rootCmd.AddCommand(cmd) 22 | } 23 | 24 | func uploadPackCmd(_ *cobra.Command, args []string) error { 25 | if len(args) == 0 { 26 | return fmt.Errorf("usage: upload-pack ") 27 | } 28 | return file.ServeUploadPack(dotGitDir(args[0])) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/gig/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // Program gig is a clone of git command. 6 | package main 7 | 8 | import "github.com/fhs/gig/cli" 9 | 10 | func main() { 11 | cli.Execute() 12 | } 13 | -------------------------------------------------------------------------------- /cmd/git-receive-pack/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | 10 | "github.com/fhs/gig/cli" 11 | ) 12 | 13 | func main() { 14 | os.Args = append([]string{"gig", "receive-pack"}, os.Args[1:]...) 15 | cli.Execute() 16 | } 17 | -------------------------------------------------------------------------------- /cmd/git-upload-pack/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | 10 | "github.com/fhs/gig/cli" 11 | ) 12 | 13 | func main() { 14 | os.Args = append([]string{"gig", "upload-pack"}, os.Args[1:]...) 15 | cli.Execute() 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fhs/gig 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/fhs/go-plan9-auth v0.3.0 7 | github.com/go-git/go-billy/v5 v5.3.1 8 | github.com/go-git/go-git/v5 v5.4.2 9 | github.com/imdario/mergo v0.3.12 10 | github.com/rogpeppe/go-internal v1.10.0 11 | github.com/sergi/go-diff v1.1.0 12 | github.com/spf13/cobra v1.1.1 13 | golang.org/x/crypto v0.1.0 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 16 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 17 | github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= 18 | github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= 19 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 20 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= 21 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= 22 | github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= 23 | github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= 24 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 25 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 26 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= 27 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 28 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 29 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 30 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 31 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 32 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 33 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 34 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 35 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 36 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 37 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 38 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 39 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 40 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 41 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 42 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 43 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 44 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 45 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 46 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 47 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 48 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 49 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 50 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 51 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 52 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 53 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 54 | github.com/fhs/9fans-go v0.0.0-fhs.20200606 h1:kwqns/76paQLNLDx9I5UFkFG1z1tSqHq8a3j/I0kLr4= 55 | github.com/fhs/9fans-go v0.0.0-fhs.20200606/go.mod h1:9PelHyep+qBAEyYdGnqAa66ZGjWE0L8EJP+GIDz8p7M= 56 | github.com/fhs/go-plan9-auth v0.3.0 h1:Hzz2CMkmcKtpPIL3iANDK+ebhAGrOLgxiSBa7P3+oy4= 57 | github.com/fhs/go-plan9-auth v0.3.0/go.mod h1:KEJUv05V8WDQa7Yos8kday3zW9Jldozre3LR3BsboNI= 58 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= 59 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 60 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 61 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 62 | github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= 63 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 64 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= 65 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= 66 | github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 67 | github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= 68 | github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 69 | github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= 70 | github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= 71 | github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= 72 | github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= 73 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 74 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 75 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 76 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 77 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 78 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 79 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 80 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 81 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 82 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 83 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 84 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 85 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 86 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 87 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 88 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 89 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 90 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 91 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 92 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 93 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 94 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 95 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 96 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 97 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 98 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 99 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 100 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 101 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 102 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 103 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 104 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 105 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 106 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 107 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 108 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 109 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 110 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 111 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 112 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 113 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 114 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 115 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 116 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 117 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 118 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 119 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 120 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 121 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 122 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 123 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 124 | github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= 125 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 126 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 127 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 128 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 129 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 130 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 131 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 132 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 133 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 134 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 135 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 136 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= 137 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 138 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 139 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 140 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 141 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 142 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 143 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 144 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 145 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 146 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 147 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 148 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 149 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 150 | github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= 151 | github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 152 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 153 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 154 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 155 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 156 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 157 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 158 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 159 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 160 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 161 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 162 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 163 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 164 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 165 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 166 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 167 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 168 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 169 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 170 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 171 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 172 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 173 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 174 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 175 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 176 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 177 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 178 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 179 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 180 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 181 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 182 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 183 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 184 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 185 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 186 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 187 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 188 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 189 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 190 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 191 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 192 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 193 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 194 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 195 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 196 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 197 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 198 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 199 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 200 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 201 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 202 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 203 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 204 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 205 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 206 | github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= 207 | github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= 208 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 209 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 210 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 211 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 212 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 213 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 214 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 215 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 216 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 217 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 218 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 219 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 220 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 221 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 222 | github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= 223 | github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= 224 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 225 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 226 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 227 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 228 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 229 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 230 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 231 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 232 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 233 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 234 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 235 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 236 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 237 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 238 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 239 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 240 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 241 | golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= 242 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 243 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 244 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 245 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 246 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 247 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 248 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 249 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 250 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 251 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 252 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 253 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 254 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 255 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 256 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 257 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 258 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 259 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 260 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 261 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 262 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 263 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 264 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 265 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 266 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 267 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 268 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 269 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 270 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 271 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 272 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 273 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 274 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 275 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 276 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 277 | golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= 278 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 279 | golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= 280 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 281 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 282 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 283 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 284 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 285 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 286 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 287 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 288 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 289 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 290 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 291 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 292 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 293 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 294 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 295 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 296 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 297 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 310 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 311 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 312 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 313 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 314 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 315 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 316 | golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= 317 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 318 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 319 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 320 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 321 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 322 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 323 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 324 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 325 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 326 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 327 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 328 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 329 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 330 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 331 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 332 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 333 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 334 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 335 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 336 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 337 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 338 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 339 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 340 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 341 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 342 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 343 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 344 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 345 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 346 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 347 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 348 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 349 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 350 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 351 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 352 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 353 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 354 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 355 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 356 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 357 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 358 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 359 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 360 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 361 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 362 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 363 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 364 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 365 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 366 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 367 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 368 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 369 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 370 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 371 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 372 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 373 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 374 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 375 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 376 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 377 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 378 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 379 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 380 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 381 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 382 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 383 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 384 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 385 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 386 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 387 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 388 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 389 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 390 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 391 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 392 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 393 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 394 | -------------------------------------------------------------------------------- /script_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Fazlul Shahriar. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | package gig 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/fhs/gig/cli" 12 | "github.com/rogpeppe/go-internal/testscript" 13 | ) 14 | 15 | func TestMain(m *testing.M) { 16 | os.Exit(testscript.RunMain(m, map[string]func() int{ 17 | "gig": gig, 18 | })) 19 | } 20 | 21 | func TestGig(t *testing.T) { 22 | testscript.Run(t, testscript.Params{ 23 | Dir: "testdata", 24 | Setup: func(env *testscript.Env) error { 25 | env.Vars = append( 26 | env.Vars, 27 | "GIT_AUTHOR_NAME=Test User", 28 | "GIT_AUTHOR_EMAIL=testuser@example.com", 29 | "GIT_EDITOR=/invalid-editor", 30 | ) 31 | return nil 32 | }, 33 | }) 34 | } 35 | 36 | func gig() int { 37 | cli.Execute() 38 | return 0 39 | } 40 | -------------------------------------------------------------------------------- /testdata/add.txt: -------------------------------------------------------------------------------- 1 | gig init one 2 | 3 | # With no arguments, exit code is still zero. 4 | gig add 5 | stdout 'Nothing specified, nothing added.' 6 | -------------------------------------------------------------------------------- /testdata/blame.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | gig add hello.txt 3 | gig commit -m 'Initial commit' 4 | cp v2.txt hello.txt 5 | gig ci -a -m 'change two to twei' 6 | 7 | gig blame hello.txt 8 | stdout 'zwei' 9 | 10 | -- hello.txt -- 11 | one 12 | two 13 | three 14 | -- v2.txt -- 15 | one 16 | zwei 17 | three 18 | -------------------------------------------------------------------------------- /testdata/branch.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | stdout 'Initialized empty Git repository in .git' 3 | 4 | ! gig branch awesome 5 | stderr 'reference to HEAD not found' 6 | 7 | gig add hello.txt 8 | gig commit -m 'Initial commit' 9 | 10 | gig branch 11 | ! stdout 'awesome' 12 | 13 | gig branch awesome 14 | gig branch 15 | stdout 'awesome' 16 | 17 | ! gig branch -D 18 | stderr 'branch name required' 19 | 20 | ! gig branch -D master 21 | stderr 'cannot delete checked out branch' 22 | 23 | gig branch -D awesome 24 | gig branch 25 | ! stdout 'awesome' 26 | 27 | # Without -D flag, we expect 1 or 0 arguments 28 | ! gig branch feature1 feature2 29 | stderr 'accepts 0 args, received 2' 30 | 31 | # Delete multiple branches in one shot 32 | gig branch feature1 33 | gig branch feature2 34 | gig branch 35 | stdout 'feature' 36 | gig branch -D feature1 feature2 37 | gig branch 38 | ! stdout 'feature' 39 | 40 | -- hello.txt -- 41 | hello world 42 | -------------------------------------------------------------------------------- /testdata/checkout.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | stdout 'Initialized empty Git repository in .git' 3 | 4 | ! gig checkout -b awesome 5 | stderr 'reference not found' 6 | 7 | gig add hello.txt 8 | gig commit -m 'Initial commit' 9 | 10 | ! gig checkout awesome 11 | stderr 'reference not found' 12 | 13 | gig branch awesome 14 | gig branch 15 | stdout '^\* .* refs/heads/master' 16 | 17 | gig checkout awesome 18 | gig branch 19 | stdout '^\* .* refs/heads/awesome' 20 | 21 | gig checkout -b plan9 22 | gig branch 23 | stdout '^\* .* refs/heads/plan9' 24 | 25 | -- hello.txt -- 26 | hello world 27 | -------------------------------------------------------------------------------- /testdata/clone.txt: -------------------------------------------------------------------------------- 1 | cd repo 2 | gig init 3 | gig add hello.txt 4 | gig commit -m 'Initial commit' 5 | cd .. 6 | 7 | mkdir subdir 8 | cd subdir 9 | ! gig clone 10 | stderr 'no URL provided' 11 | gig clone $WORK/repo/ 12 | cd repo 13 | gig log 14 | stdout 'Initial commit' 15 | 16 | -- repo/hello.txt -- 17 | hello world 18 | -------------------------------------------------------------------------------- /testdata/commit.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | stdout 'Initialized empty Git repository in .git' 3 | 4 | ! gig commit -m 'this commit should not happen' 5 | stderr 'nothing to commit \(use "gig add"\)' 6 | 7 | ! gig commit -a -m 'this commit should not happen' 8 | stderr 'nothing to commit, working tree clean' 9 | 10 | gig add hello.txt 11 | gig commit -m 'add hello.txt' 12 | 13 | # worktree is dirty but staging is clean 14 | cp hello2.txt hello.txt 15 | ! gig commit -m 'update message' 16 | stderr 'nothing to commit \(use "gig add"\)' 17 | gig commit -a -m 'update message' 18 | 19 | # worktree is clean but staging is dirty 20 | cp hello3.txt hello.txt 21 | gig add hello.txt 22 | gig commit -a -m 'update message' 23 | 24 | 25 | -- hello.txt -- 26 | hello world 27 | 28 | -- hello2.txt -- 29 | Hello, 世界 30 | 31 | -- hello3.txt -- 32 | Hello, Welt 33 | -------------------------------------------------------------------------------- /testdata/diff.txt: -------------------------------------------------------------------------------- 1 | gig init repo 2 | cd repo 3 | gig add a.txt b.txt c.txt 4 | gig commit -m 'Initial commit' 5 | 6 | cp ../a2.txt a.txt 7 | rm b.txt c.txt 8 | gig add c.txt 9 | 10 | gig diff 11 | cmp stdout ../index-diff.txt 12 | 13 | gig diff HEAD 14 | cmp stdout ../head-diff.txt 15 | 16 | -- repo/a.txt -- 17 | one 18 | two 19 | three 20 | -- repo/b.txt -- 21 | eins 22 | zwei 23 | drei 24 | -- repo/c.txt -- 25 | un 26 | deux 27 | trois 28 | -- a2.txt -- 29 | one 30 | three 31 | -- index-diff.txt -- 32 | diff --git a/a.txt b/a.txt 33 | @@ -1,3 +1,2 @@ 34 | one 35 | -two 36 | three 37 | diff --git a/b.txt b/b.txt 38 | @@ -1,3 +0,0 @@ 39 | -eins 40 | -zwei 41 | -drei 42 | -- head-diff.txt -- 43 | diff --git a/a.txt b/a.txt 44 | @@ -1,3 +1,2 @@ 45 | one 46 | -two 47 | three 48 | diff --git a/b.txt b/b.txt 49 | @@ -1,3 +0,0 @@ 50 | -eins 51 | -zwei 52 | -drei 53 | diff --git a/c.txt b/c.txt 54 | @@ -1,3 +0,0 @@ 55 | -un 56 | -deux 57 | -trois 58 | -------------------------------------------------------------------------------- /testdata/fetch.txt: -------------------------------------------------------------------------------- 1 | gig init one 2 | cd one 3 | gig add hello.txt 4 | gig commit -m 'Initial commit' 5 | cd .. 6 | 7 | gig init two 8 | cd two 9 | gig remote add origin $WORK/one 10 | ! gig show origin/master 11 | stderr 'reference not found' 12 | gig fetch origin 13 | gig show origin/master 14 | stdout 'Initial commit' 15 | 16 | gig fetch origin 17 | stdout 'already up-to-date' 18 | 19 | -- one/hello.txt -- 20 | hello world 21 | -------------------------------------------------------------------------------- /testdata/grep.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | gig add fruits.txt 3 | gig commit -m 'Initial commit' 4 | 5 | gig grep -n apple 6 | stdout 'fruits.txt:1:apple' 7 | ! stdout 'orange' 8 | 9 | -- fruits.txt -- 10 | apple 11 | orange 12 | -------------------------------------------------------------------------------- /testdata/init_cwd.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | stdout 'Initialized empty Git repository in .git' 3 | 4 | gig status 5 | stdout '\?\? hello.txt' 6 | stdout '\?\? file1.txt' 7 | 8 | gig add hello.txt 9 | 10 | gig status 11 | stdout 'A hello.txt' 12 | stdout '\?\? file1.txt' 13 | 14 | gig commit -m 'Initial commit' 15 | 16 | gig log 17 | stdout '^commit .*' 18 | stdout '^Author: .*' 19 | stdout '^Date: .*' 20 | stdout 'Initial commit' 21 | 22 | gig branch 23 | stdout ' refs/heads/master$' 24 | 25 | gig add file1.txt 26 | gig commit -m 'Add file1' 27 | gig show 28 | stdout 'Add file1' 29 | stdout '^\+line one$' 30 | stdout '^\+line two$' 31 | stdout '^\+line three$' 32 | 33 | -- hello.txt -- 34 | hello world 35 | こんにちは世界 36 | -- file1.txt -- 37 | line one 38 | line two 39 | line three 40 | -------------------------------------------------------------------------------- /testdata/init_foo.txt: -------------------------------------------------------------------------------- 1 | gig init foo 2 | stdout 'Initialized empty Git repository in foo[/\\].git' 3 | 4 | cd foo 5 | gig status 6 | 7 | ! gig log 8 | stderr 'reference not found' 9 | 10 | gig diff 11 | 12 | gig branch 13 | -------------------------------------------------------------------------------- /testdata/log.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | gig add one.txt 3 | gig commit -m 'add file one' 4 | gig add two.txt 5 | gig commit -m 'add file two' 6 | gig log 7 | stdout 'add file one' 8 | stdout 'add file two' 9 | gig log -n 10 10 | stdout 'add file one' 11 | stdout 'add file two' 12 | gig log -n 0 13 | ! stdout . 14 | gig log -n 1 15 | ! stdout 'add file one' 16 | stdout 'add file two' 17 | 18 | gig log -n 1 --format 'format: +%h %cd HEAD' 19 | stdout '\+.* .* HEAD$' 20 | 21 | -- one.txt -- 22 | this is file one 23 | -- two.txt -- 24 | this is file two 25 | -------------------------------------------------------------------------------- /testdata/ls-files.txt: -------------------------------------------------------------------------------- 1 | gig init repo 2 | cd repo 3 | gig add a.txt 4 | gig commit -m 'Initial commit' 5 | 6 | gig ls-files 7 | stdout 'a.txt' 8 | 9 | gig ls-files -s 10 | stdout '100644 4cb29ea38f70d7c61b2a3a25b02e3bdf44905402 0 a.txt' 11 | 12 | -- repo/a.txt -- 13 | one 14 | two 15 | three 16 | -------------------------------------------------------------------------------- /testdata/ls-remote.txt: -------------------------------------------------------------------------------- 1 | cd repo 2 | gig init 3 | gig add hello.txt 4 | gig commit -m 'Initial commit' 5 | cd .. 6 | 7 | gig clone $WORK/repo/ subdir 8 | cd subdir 9 | gig ls-remote 10 | stderr '^From .*/repo/$' 11 | stdout '\tHEAD$' 12 | stdout '\trefs/heads/master$' 13 | 14 | -- repo/hello.txt -- 15 | hello world 16 | -------------------------------------------------------------------------------- /testdata/mv.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | gig add hello.txt 3 | gig status 4 | stdout 'A hello.txt' 5 | gig commit -m 'Initial commit' 6 | 7 | # we want to test relative file paths 8 | mkdir a 9 | cd a 10 | 11 | gig mv ../hello.txt ../world.txt 12 | gig status 13 | stdout 'D hello.txt' 14 | stdout 'A world.txt' 15 | gig commit -m 'rename file' 16 | ! exists ../hello.txt 17 | exists ../world.txt 18 | 19 | -- hello.txt -- 20 | hello world 21 | -------------------------------------------------------------------------------- /testdata/pull.txt: -------------------------------------------------------------------------------- 1 | gig init one 2 | stdout 'Initialized empty Git repository in' 3 | cd one 4 | gig add hello.txt 5 | gig commit -m 'Initial commit' 6 | cd .. 7 | 8 | # local URL requires an absote path. See https://github.com/fhs/gig/issues/3 9 | gig clone $WORK/one two 10 | cd two 11 | gig remote -v 12 | stdout 'one' 13 | cd .. 14 | 15 | cd one 16 | gig add world.txt 17 | gig commit -m 'second commit' 18 | cd .. 19 | 20 | cd two 21 | gig log 22 | ! stdout 'second commit' 23 | gig pull 24 | gig log 25 | stdout 'second commit' 26 | 27 | gig pull origin 28 | stdout 'already up-to-date' 29 | 30 | -- one/hello.txt -- 31 | hello world 32 | -- one/world.txt -- 33 | hello world 34 | -------------------------------------------------------------------------------- /testdata/push-current-branch.txt: -------------------------------------------------------------------------------- 1 | # Test we only push the current branch if no branch is specified. 2 | 3 | gig init one 4 | cd one 5 | gig add hello.txt 6 | gig commit -m 'Initial commit' 7 | cd .. 8 | 9 | # local URL requires an absote path. See https://github.com/fhs/gig/issues/3 10 | gig clone $WORK/one two 11 | 12 | # Create a new branch and then push from master. 13 | cd two 14 | gig co -b branch1 15 | cp ../world.txt . 16 | rm ../world.txt 17 | gig add world.txt 18 | gig commit -m 'second commit' 19 | gig co master 20 | gig push 21 | stdout 'already up-to-date' 22 | cd .. 23 | 24 | # The new branch should not show up on remote. 25 | cd one 26 | gig branch 27 | ! stdout 'branch1' 28 | 29 | -- one/hello.txt -- 30 | hello world 31 | -- world.txt -- 32 | hello world 33 | -------------------------------------------------------------------------------- /testdata/push.txt: -------------------------------------------------------------------------------- 1 | gig init one 2 | stdout 'Initialized empty Git repository in' 3 | cd one 4 | gig add hello.txt 5 | gig commit -m 'Initial commit' 6 | cd .. 7 | 8 | # local URL requires an absote path. See https://github.com/fhs/gig/issues/3 9 | gig clone $WORK/one two 10 | 11 | cd two 12 | gig remote -v 13 | stdout 'one' 14 | cp ../world.txt . 15 | rm ../world.txt 16 | gig add world.txt 17 | gig commit -m 'second commit' 18 | gig push origin refs/heads/master:refs/heads/master 19 | cd .. 20 | 21 | cd one 22 | gig log 23 | stdout 'second commit' 24 | 25 | -- one/hello.txt -- 26 | hello world 27 | -- world.txt -- 28 | hello world 29 | -------------------------------------------------------------------------------- /testdata/remote.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | stdout 'Initialized empty Git repository in .git' 3 | 4 | gig remote add origin git@github.com:fhs/gig.git 5 | gig remote 6 | stdout 'origin' 7 | gig remote -v 8 | stdout 'origin git@github.com:fhs/gig.git \(fetch\)' 9 | stdout 'origin git@github.com:fhs/gig.git \(push\)' 10 | 11 | gig remote remove origin 12 | gig remote 13 | ! stdout 'origin' 14 | -------------------------------------------------------------------------------- /testdata/reset.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | gig add fruits.txt 3 | gig commit -m 'Initial commit' 4 | 5 | ! gig reset --hard --soft 6 | stderr 'exactly one mode should be specified, not 2' 7 | 8 | cp fruits1.txt fruits.txt 9 | rm fruits1.txt 10 | 11 | gig status 12 | stdout 'fruits.txt' 13 | gig reset --hard 14 | gig status 15 | ! stdout '.' 16 | 17 | -- fruits.txt -- 18 | apple 19 | -- fruits1.txt -- 20 | apple 21 | orange 22 | -------------------------------------------------------------------------------- /testdata/rev-parse.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | stdout 'Initialized empty Git repository in .git' 3 | 4 | gig add hello.txt 5 | gig commit -m 'Initial commit' 6 | 7 | gig rev-parse HEAD 8 | stdout . 9 | 10 | gig rev-parse --git-dir 11 | stdout '[\\/]\.git$' 12 | 13 | -- hello.txt -- 14 | hello world 15 | -------------------------------------------------------------------------------- /testdata/rm.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | gig add hello.txt 3 | gig status 4 | stdout 'A hello.txt' 5 | gig commit -m 'Initial commit' 6 | 7 | # we want to test relative file paths 8 | mkdir a 9 | cd a 10 | 11 | gig rm ../hello.txt 12 | gig status 13 | stdout 'D hello.txt' 14 | gig commit -m 'remove file' 15 | ! exists ../hello.txt 16 | 17 | -- hello.txt -- 18 | hello world 19 | -------------------------------------------------------------------------------- /testdata/show.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | 3 | ! gig show 4 | stderr 'reference not found' 5 | 6 | gig add hello.txt 7 | gig commit -m 'Initial commit' 8 | gig tag v1.2.3 9 | 10 | gig show 11 | stdout 'Initial commit' 12 | gig show HEAD 13 | stdout 'Initial commit' 14 | gig show master 15 | stdout 'Initial commit' 16 | gig show v1.2.3 17 | stdout 'Initial commit' 18 | 19 | gig show HEAD:hello.txt 20 | cmp stdout hello.txt 21 | gig show HEAD:./hello.txt 22 | cmp stdout hello.txt 23 | mkdir a 24 | cd a 25 | gig show HEAD:../hello.txt 26 | cmp stdout ../hello.txt 27 | cd ../ 28 | rm a 29 | 30 | -- hello.txt -- 31 | hello world 32 | -------------------------------------------------------------------------------- /testdata/tag.txt: -------------------------------------------------------------------------------- 1 | gig init 2 | stdout 'Initialized empty Git repository in .git' 3 | 4 | ! gig tag v1 5 | stderr 'reference not found' 6 | 7 | gig add hello.txt 8 | gig commit -m 'Initial commit' 9 | 10 | gig tag 11 | ! stdout v1 12 | 13 | gig tag v1 14 | gig tag 15 | stdout v1 16 | 17 | ! gig tag v1 18 | stderr 'tag already exists' 19 | 20 | gig tag -d v1 21 | gig tag 22 | ! stdout v1 23 | 24 | ! gig tag v1 v2 25 | stderr 'expected 1 argument but got 2' 26 | 27 | -- hello.txt -- 28 | hello world 29 | --------------------------------------------------------------------------------