├── .gitignore
├── .gitattributes
├── testdata
├── add.txt
├── grep.txt
├── init_foo.txt
├── ls-files.txt
├── rev-parse.txt
├── blame.txt
├── clone.txt
├── ls-remote.txt
├── rm.txt
├── remote.txt
├── reset.txt
├── fetch.txt
├── mv.txt
├── tag.txt
├── log.txt
├── checkout.txt
├── show.txt
├── push.txt
├── pull.txt
├── push-current-branch.txt
├── init_cwd.txt
├── commit.txt
├── branch.txt
└── diff.txt
├── .github
├── dependabot.yml
└── workflows
│ ├── test.yml
│ └── cross-compile.yml
├── cmd
├── gig
│ └── main.go
├── git-upload-pack
│ └── main.go
└── git-receive-pack
│ └── main.go
├── go.mod
├── cli
├── editor_plan9.go
├── editor_posix.go
├── auth_posix.go
├── upload-pack.go
├── receive-pack.go
├── mv.go
├── rm.go
├── status.go
├── add.go
├── config.go
├── init.go
├── ls-files.go
├── fetch.go
├── blame.go
├── log_test.go
├── checkout.go
├── pull.go
├── rev-parse.go
├── root.go
├── grep.go
├── tag.go
├── push.go
├── clone.go
├── remote.go
├── show.go
├── ls-remote.go
├── reset.go
├── branch.go
├── auth_plan9_test.go
├── git.go
├── log.go
├── testdata
│ └── factotum.keys
├── commit.go
├── diff.go
└── auth_plan9.go
├── script_test.go
├── LICENSE
├── README.md
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | gig
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
--------------------------------------------------------------------------------
/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/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_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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------