From dfef6151d1d6452fc3b853b0440ea473c5023322 Mon Sep 17 00:00:00 2001 From: Grzegorz Dlugoszewski Date: Mon, 8 Jun 2020 21:37:26 +0200 Subject: [PATCH] Add a --branch flag that specifies which branch or tag to check out after cloning --- cfg/config.go | 2 ++ cmd/main.go | 7 +++++-- git/repo.go | 13 +++++++++++-- git/repo_test.go | 38 ++++++++++++++++++++++++++++++++------ git/status_test.go | 24 ++++++++++++++++++++++-- 5 files changed, 72 insertions(+), 12 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 561edff..7569af5 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -23,6 +23,8 @@ const ( DefPrivateKey = "id_rsa" KeyOutput = "out" DefOutput = OutFlat + KeyBranch = "branch" + DefBranch = "master" KeyFetch = "fetch" KeyList = "list" ) diff --git a/cmd/main.go b/cmd/main.go index 3d0a860..7139225 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,12 +34,14 @@ func init() { cmd.PersistentFlags().StringP(cfg.KeyReposRoot, "r", "", "repos root") cmd.PersistentFlags().StringP(cfg.KeyPrivateKey, "p", "", "SSH private key path") cmd.PersistentFlags().StringP(cfg.KeyOutput, "o", cfg.DefOutput, "output format.") + cmd.PersistentFlags().StringP(cfg.KeyBranch, "b", cfg.DefBranch, "Branch (or tag) to checkout after cloning") viper.BindPFlag(cfg.KeyList, cmd.PersistentFlags().Lookup(cfg.KeyList)) viper.BindPFlag(cfg.KeyFetch, cmd.PersistentFlags().Lookup(cfg.KeyFetch)) viper.BindPFlag(cfg.KeyReposRoot, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot)) viper.BindPFlag(cfg.KeyPrivateKey, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot)) viper.BindPFlag(cfg.KeyOutput, cmd.PersistentFlags().Lookup(cfg.KeyOutput)) + viper.BindPFlag(cfg.KeyBranch, cmd.PersistentFlags().Lookup(cfg.KeyBranch)) } func Run(cmd *cobra.Command, args []string) { @@ -74,9 +76,10 @@ func Run(cmd *cobra.Command, args []string) { url, err := path.ParseURL(args[0]) exitIfError(err) - repoPath := pathpkg.Join(root, path.URLToPath(url)) - _, err = git.CloneRepo(url, repoPath, false) + branch := viper.GetString(cfg.KeyBranch) + repoPath := pathpkg.Join(root, path.URLToPath(url)) + _, err = git.CloneRepo(url, repoPath, branch, false) exitIfError(err) } diff --git a/git/repo.go b/git/repo.go index 6e96365..eb24bb6 100644 --- a/git/repo.go +++ b/git/repo.go @@ -4,6 +4,8 @@ import ( "fmt" "git-get/cfg" + "github.com/go-git/go-git/v5/plumbing" + "io" "io/ioutil" "net/url" @@ -24,7 +26,7 @@ type Repo struct { Status *RepoStatus } -func CloneRepo(url *url.URL, path string, quiet bool) (*Repo, error) { +func CloneRepo(url *url.URL, path string, branch string, quiet bool) (*Repo, error) { var progress io.Writer if !quiet { progress = os.Stdout @@ -40,11 +42,18 @@ func CloneRepo(url *url.URL, path string, quiet bool) (*Repo, error) { } } + // If branch name is actually a tag (ie. is prefixed with refs/tags) - check out that tag. + // Otherwise, assume it's a branch name and check it out. + refName := plumbing.ReferenceName(branch) + if !refName.IsTag() { + refName = plumbing.NewBranchReferenceName(branch) + } + opts := &git.CloneOptions{ URL: url.String(), Auth: auth, RemoteName: git.DefaultRemoteName, - ReferenceName: "", + ReferenceName: refName, SingleBranch: false, NoCheckout: false, Depth: 0, diff --git a/git/repo_test.go b/git/repo_test.go index 2aedcfa..60e0d61 100644 --- a/git/repo_test.go +++ b/git/repo_test.go @@ -83,7 +83,7 @@ func newRepoWithLocalBranch(t *testing.T) *Repo { func newRepoWithClonedBranch(t *testing.T) *Repo { origin := newRepoWithCommit(t) - r := origin.clone(t) + r := origin.clone(t, "master") r.newBranch(t, "local") r.checkoutBranch(t, "local") @@ -105,7 +105,7 @@ func newRepoWithDetachedHead(t *testing.T) *Repo { func newRepoWithBranchAhead(t *testing.T) *Repo { origin := newRepoWithCommit(t) - r := origin.clone(t) + r := origin.clone(t, "master") r.writeFile(t, "new", "I'm a new file") r.addFile(t, "new") r.newCommit(t, "new commit") @@ -116,7 +116,7 @@ func newRepoWithBranchAhead(t *testing.T) *Repo { func newRepoWithBranchBehind(t *testing.T) *Repo { origin := newRepoWithCommit(t) - r := origin.clone(t) + r := origin.clone(t, "master") origin.writeFile(t, "origin.new", "I'm a new file on origin") origin.addFile(t, "origin.new") @@ -130,7 +130,7 @@ func newRepoWithBranchBehind(t *testing.T) *Repo { func newRepoWithBranchAheadAndBehind(t *testing.T) *Repo { origin := newRepoWithCommit(t) - r := origin.clone(t) + r := origin.clone(t, "master") r.writeFile(t, "local.new", "local 1") r.addFile(t, "local.new") r.newCommit(t, "1st local commit") @@ -155,6 +155,22 @@ func newRepoWithBranchAheadAndBehind(t *testing.T) *Repo { return r } +func newRepoWithCheckedOutBranch(t *testing.T) *Repo { + origin := newRepoWithCommit(t) + origin.newBranch(t, "feature/branch1") + + r := origin.clone(t, "feature/branch1") + return r +} + +func newRepoWithCheckedOutTag(t *testing.T) *Repo { + origin := newRepoWithCommit(t) + origin.newTag(t, "v1.0.0") + + r := origin.clone(t, "refs/tags/v1.0.0") + return r +} + func newTempDir(t *testing.T) string { dir, err := ioutil.TempDir("", "git-get-repo-") checkFatal(t, errors.Wrap(err, "Failed creating test repo directory")) @@ -216,12 +232,22 @@ func (r *Repo) newBranch(t *testing.T, name string) { checkFatal(t, err) } -func (r *Repo) clone(t *testing.T) *Repo { +func (r *Repo) newTag(t *testing.T, name string) { + head, err := r.Head() + checkFatal(t, err) + + ref := plumbing.NewHashReference(plumbing.NewTagReferenceName(name), head.Hash()) + + err = r.Storer.SetReference(ref) + checkFatal(t, err) +} + +func (r *Repo) clone(t *testing.T, branch string) *Repo { dir := newTempDir(t) repoURL, err := url.Parse("file://" + r.Path) checkFatal(t, err) - repo, err := CloneRepo(repoURL, dir, true) + repo, err := CloneRepo(repoURL, dir, branch, true) checkFatal(t, err) return repo diff --git a/git/status_test.go b/git/status_test.go index f928c8a..d9697df 100644 --- a/git/status_test.go +++ b/git/status_test.go @@ -155,16 +155,36 @@ func TestStatus(t *testing.T) { }, }, }}, + {newRepoWithCheckedOutBranch, &RepoStatus{ + HasUntrackedFiles: false, + HasUncommittedChanges: false, + CurrentBranch: "feature/branch1", + Branches: []*BranchStatus{ + { + Name: "feature/branch1", + Upstream: "origin/feature/branch1", + Behind: 0, + Ahead: 0, + }, + }, + }}, + {newRepoWithCheckedOutTag, &RepoStatus{ + HasUntrackedFiles: false, + HasUncommittedChanges: false, + // TODO: is this correct? Can we show tag name instead of "detached HEAD"? + CurrentBranch: StatusDetached, + Branches: nil, + }}, } - for _, test := range tests { + for i, test := range tests { repo := test.makeTestRepo(t) err := repo.LoadStatus() checkFatal(t, err) if !reflect.DeepEqual(repo.Status, test.want) { - t.Errorf("Wrong repo status, got: %+v; want: %+v", repo.Status, test.want) + t.Errorf("Failed test case %d, got: %+v; want: %+v", i, repo.Status, test.want) } } }