From a04ca52b8a8b660c40bec50dffaef469289971e5 Mon Sep 17 00:00:00 2001 From: "Navrkal, David" Date: Wed, 14 Feb 2024 18:32:00 +0100 Subject: [PATCH 1/3] fixes #109 - fix checkout existing branch --- repository/repo-operations.go | 102 +++++++++++++++++----------------- stats/stats.go | 6 +- 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/repository/repo-operations.go b/repository/repo-operations.go index d1f3761..01efd38 100644 --- a/repository/repo-operations.go +++ b/repository/repo-operations.go @@ -15,6 +15,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/sirupsen/logrus" + gitConfig "github.com/go-git/go-git/v5/config" "github.com/google/go-github/v43/github" "github.com/gruntwork-io/git-xargs/common" @@ -162,77 +163,78 @@ func checkoutLocalBranch(config *config.GitXargsConfig, ref *plumbing.Reference, // BranchName is a global variable that is set in cmd/root.go. It is override-able by the operator via the --branch-name or -b flag. It defaults to "git-xargs" branchName := plumbing.NewBranchReferenceName(config.BranchName) + logger.WithFields(logrus.Fields{ - "Branch Name": branchName, - "Repo": remoteRepository.GetName(), - }).Debug("Created branch") + "Repo": remoteRepository.GetName(), + }).Debug("Fetching remote branches") - // Create a branch specific to the multi repo script runner - co := &git.CheckoutOptions{ - Hash: ref.Hash(), + // Fetch remote branches + fetchRemoteBranchesErr := localRepository.Fetch(&git.FetchOptions{ + RefSpecs: []gitConfig.RefSpec{"refs/*:refs/*"}, + Auth: &http.BasicAuth{ + Username: remoteRepository.GetOwner().GetLogin(), + Password: os.Getenv("GITHUB_OAUTH_TOKEN"), + }, + }) + + if fetchRemoteBranchesErr != nil { + logger.WithFields(logrus.Fields{ + "Error": fetchRemoteBranchesErr, + "Repo": remoteRepository.GetName(), + }).Debug("Error fetching remote branches") + + // Track the error fetching branches from the remote + config.Stats.TrackSingle(stats.BranchRemoteFetchFailed, remoteRepository) + + return branchName, errors.WithStackTrace(fetchRemoteBranchesErr) + } + + // Checkout existing branch + checkoutExistingBranchErr := worktree.Checkout(&git.CheckoutOptions{ Branch: branchName, - Create: true, + }) + + if checkoutExistingBranchErr == nil { + logger.WithFields(logrus.Fields{ + "Branch Name": branchName, + "Repo": remoteRepository.GetName(), + }).Debug("Checkout existing branch") + + // We have successfully checkout existing branch, exiting + return branchName, nil } - // Attempt to checkout the new tool-specific branch on which the supplied command will be executed - checkoutErr := worktree.Checkout(co) + // Create new branch + checkoutNewBranchErr := worktree.Checkout( + &git.CheckoutOptions{ + Hash: ref.Hash(), + Branch: branchName, + Create: true, + }) - if checkoutErr != nil { + if checkoutNewBranchErr != nil { if config.SkipPullRequests && remoteRepository.GetDefaultBranch() == config.BranchName && - strings.Contains(checkoutErr.Error(), "already exists") { - // User has requested pull requess be skipped, meaning they want their commits pushed on their target branch + strings.Contains(checkoutNewBranchErr.Error(), "already exists") { + // User has requested pull request be skipped, meaning they want their commits pushed on their target branch // If the target branch is also the repo's default branch and therefore already exists, we don't have an error } else { logger.WithFields(logrus.Fields{ - "Error": checkoutErr, + "Error": checkoutNewBranchErr, "Repo": remoteRepository.GetName(), }).Debug("Error creating new branch") // Track the error checking out the branch config.Stats.TrackSingle(stats.BranchCheckoutFailed, remoteRepository) - return branchName, errors.WithStackTrace(checkoutErr) + return branchName, errors.WithStackTrace(checkoutNewBranchErr) } } - // Pull latest code from remote branch if it exists to avoid fast-forwarding errors - gitProgressBuffer := bytes.NewBuffer(nil) - po := &git.PullOptions{ - RemoteName: "origin", - ReferenceName: branchName, - Auth: &http.BasicAuth{ - Username: remoteRepository.GetOwner().GetLogin(), - Password: os.Getenv("GITHUB_OAUTH_TOKEN"), - }, - Progress: gitProgressBuffer, - } - logger.WithFields(logrus.Fields{ - "Repo": remoteRepository.GetName(), - }).Debug(gitProgressBuffer) - - pullErr := worktree.Pull(po) - - if pullErr != nil { - - if pullErr == plumbing.ErrReferenceNotFound { - // The supplied branch just doesn't exist yet on the remote - this is not a fatal error and will - // allow the new branch to be pushed in pushLocalBranch - config.Stats.TrackSingle(stats.BranchRemoteDidntExistYet, remoteRepository) - return branchName, nil - } - - if pullErr == git.NoErrAlreadyUpToDate { - // The local branch is already up to date, which is not a fatal error - return branchName, nil - } - - // Track the error pulling the latest from the remote branch - config.Stats.TrackSingle(stats.BranchRemotePullFailed, remoteRepository) - - return branchName, errors.WithStackTrace(pullErr) - } + "Branch Name": branchName, + "Repo": remoteRepository.GetName(), + }).Debug("Created new branch") return branchName, nil } diff --git a/stats/stats.go b/stats/stats.go index 777c5a9..ae68651 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -62,8 +62,8 @@ const ( CommitsMadeDirectlyToBranch types.Event = "commits-made-directly-to-branch" // DirectCommitsPushedToRemoteBranch denotes a repo whose changes were pushed to the remote specified branch because the --skip-pull-requests flag was passed DirectCommitsPushedToRemoteBranch types.Event = "direct-commits-pushed-to-remote" - // BranchRemotePullFailed denotes a repo whose remote branch could not be fetched successfully - BranchRemotePullFailed types.Event = "branch-remote-pull-failed" + // BranchRemoteFetchFailed denotes a repo whose remote branch could not be fetched successfully + BranchRemoteFetchFailed types.Event = "branch-remote-fetch-failed" // BranchRemoteDidntExistYet denotes a repo whose specified branch didn't exist remotely yet and so was just created locally to begin with BranchRemoteDidntExistYet types.Event = "branch-remote-didnt-exist-yet" // RepoFlagSuppliedRepoMalformed denotes a repo passed via the --repo flag that was malformed (perhaps missing it's Github org prefix) and therefore unprocessable @@ -104,7 +104,7 @@ var allEvents = []types.AnnotatedEvent{ {Event: PullRequestAlreadyExists, Description: "Repos where opening a pull request was skipped because a pull request was already open"}, {Event: CommitsMadeDirectlyToBranch, Description: "Repos whose local changes were committed directly to the specified branch because --skip-pull-requests was passed"}, {Event: DirectCommitsPushedToRemoteBranch, Description: "Repos whose changes were pushed directly to the remote branch because --skip-pull-requests was passed"}, - {Event: BranchRemotePullFailed, Description: "Repos whose remote branches could not be successfully pulled"}, + {Event: BranchRemoteFetchFailed, Description: "Repos whose remote branches could not be successfully fetched"}, {Event: BranchRemoteDidntExistYet, Description: "Repos whose specified branches did not exist on the remote, and so were first created locally"}, {Event: RepoFlagSuppliedRepoMalformed, Description: "Repos passed via the --repo flag that were malformed (missing their Github org prefix?) and therefore unprocessable"}, {Event: RepoDoesntSupportDraftPullRequestsErr, Description: "Repos that do not support Draft PRs (--draft flag was passed)"}, From 4875ed93233aa002d18e11963172baa209f5b4a0 Mon Sep 17 00:00:00 2001 From: "Navrkal, David" Date: Wed, 14 Feb 2024 22:04:31 +0100 Subject: [PATCH 2/3] Fix the tests. --- repository/repo-operations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repository/repo-operations.go b/repository/repo-operations.go index 01efd38..d4755fe 100644 --- a/repository/repo-operations.go +++ b/repository/repo-operations.go @@ -177,7 +177,7 @@ func checkoutLocalBranch(config *config.GitXargsConfig, ref *plumbing.Reference, }, }) - if fetchRemoteBranchesErr != nil { + if fetchRemoteBranchesErr != git.NoErrAlreadyUpToDate && fetchRemoteBranchesErr != nil { logger.WithFields(logrus.Fields{ "Error": fetchRemoteBranchesErr, "Repo": remoteRepository.GetName(), From b83b88dc6b9a16ca21c1f65ebc6355e0c4c9b8a3 Mon Sep 17 00:00:00 2001 From: "Navrkal, David" Date: Wed, 14 Feb 2024 22:13:59 +0100 Subject: [PATCH 3/3] docs: Updated branch behavior documentation. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b05599..e64a4f6 100644 --- a/README.md +++ b/README.md @@ -320,7 +320,7 @@ If your job is consistently being rate-limited, try incrementally increasing the ## Branch behavior -Passing the `--branch-name` (`-b`) flag is required when running `git-xargs`. If you specify the name of a branch that exists on your remote, its latest changes will be pulled locally prior to your command or script being run. If you specify the name of a new branch that does not yet exist on your remote, it will be created locally and pushed once your changes are committed. +Passing the `--branch-name` (`-b`) flag is required when running `git-xargs`. If you specify the name of a branch that exists on your remote, it is checkout locally prior to your command or script being run. If you specify the name of a new branch that does not yet exist on your remote, it will be created locally and pushed once your changes are committed. ## Default repository branch