@@ -51,9 +51,11 @@ var branchRe = regexp.MustCompile(`^release/(\d+)\.(\d+)$`)
5151// ref is the branch name from the "Use workflow from" dropdown
5252// (github.ref_name). commitSHA is an optional override; when empty
5353// the tool defaults to HEAD of the ref.
54- func calculateNextVersion (releaseType , ref , commitSHA string ) (calculateResult , error ) {
55- // Ensure we have up-to-date remote state.
56- if _ , err := gitOutput ("fetch" , "--tags" , "--force" , "origin" ); err != nil {
54+ func calculateNextVersion (exec CommandExecutor , releaseType , ref , commitSHA string ) (calculateResult , error ) {
55+ // Ensure we have up-to-date remote state. Fetching only updates
56+ // local remote-tracking refs, so it runs even in dry-run mode to
57+ // keep version calculation accurate.
58+ if _ , err := gitOutput (exec , "fetch" , "--tags" , "--force" , "origin" ); err != nil {
5759 return nil , xerrors .Errorf ("git fetch: %w" , err )
5860 }
5961
@@ -66,21 +68,21 @@ func calculateNextVersion(releaseType, ref, commitSHA string) (calculateResult,
6668 return nil , xerrors .Errorf ("rc must be run from main or a release/X.Y branch, got %q" , ref )
6769 }
6870 if isMain {
69- return calculateRCFromMainReleaseRequest (ref , commitSHA )
71+ return calculateRCFromMainReleaseRequest (exec , ref , commitSHA )
7072 }
71- return calculateRCFromBranchReleaseRequest (ref , commitSHA )
73+ return calculateRCFromBranchReleaseRequest (exec , ref , commitSHA )
7274
7375 case "release" :
7476 if ! isReleaseBranch {
7577 return nil , xerrors .Errorf ("release must be run from a release/X.Y branch, got %q" , ref )
7678 }
77- return createRegularReleaseRequest (ref )
79+ return createRegularReleaseRequest (exec , ref )
7880
7981 case "create-release-branch" :
8082 if ! isMain {
8183 return nil , xerrors .Errorf ("create-release-branch must be run from main, got %q" , ref )
8284 }
83- return calculateCreateBranchRequest (ref , commitSHA )
85+ return calculateCreateBranchRequest (exec , ref , commitSHA )
8486
8587 default :
8688 return nil , xerrors .Errorf ("unknown release type %q (expected rc, release, or create-release-branch)" , releaseType )
@@ -90,33 +92,42 @@ func calculateNextVersion(releaseType, ref, commitSHA string) (calculateResult,
9092// resolveCommit returns the commit SHA to tag. If commitSHA is
9193// provided it is validated and returned; otherwise HEAD of the
9294// ref is used.
93- func resolveCommit (ref , commitSHA string ) (string , error ) {
95+ func resolveCommit (exec CommandExecutor , ref , commitSHA string ) (string , error ) {
9496 if commitSHA != "" {
9597 if ! isHexSHA (commitSHA ) {
9698 return "" , xerrors .Errorf ("invalid commit SHA %q: must be a hex string" , commitSHA )
9799 }
98- return commitSHA , nil
100+ // Resolve to a full commit SHA. The idempotency checks in
101+ // createAndPushTag/createAndPushBranch compare targetRef
102+ // against full SHAs (git rev-parse of an existing tag,
103+ // ls-remote branch output), so a short SHA passed via the
104+ // commit input would never match and would break re-runs.
105+ sha , err := gitOutput (exec , "rev-parse" , "--verify" , commitSHA + "^{commit}" )
106+ if err != nil {
107+ return "" , xerrors .Errorf ("resolve commit %s: %w" , commitSHA , err )
108+ }
109+ return sha , nil
99110 }
100- sha , err := gitOutput ("rev-parse" , fmt .Sprintf ("origin/%s" , ref ))
111+ sha , err := gitOutput (exec , "rev-parse" , fmt .Sprintf ("origin/%s" , ref ))
101112 if err != nil {
102113 return "" , xerrors .Errorf ("resolve HEAD of %s: %w" , ref , err )
103114 }
104115 return sha , nil
105116}
106117
107118// calculateRCFromMainReleaseRequest tags an RC from a commit on main.
108- func calculateRCFromMainReleaseRequest (ref , commitSHA string ) (ReleaseRequest , error ) {
109- targetRef , err := resolveCommit (ref , commitSHA )
119+ func calculateRCFromMainReleaseRequest (exec CommandExecutor , ref , commitSHA string ) (ReleaseRequest , error ) {
120+ targetRef , err := resolveCommit (exec , ref , commitSHA )
110121 if err != nil {
111122 return ReleaseRequest {}, err
112123 }
113124
114125 // Verify commit is an ancestor of origin/main.
115- if err := gitRun ("merge-base" , "--is-ancestor" , targetRef , "origin/main" ); err != nil {
126+ if err := gitRun (exec , "merge-base" , "--is-ancestor" , targetRef , "origin/main" ); err != nil {
116127 return ReleaseRequest {}, xerrors .Errorf ("commit %s is not an ancestor of origin/main" , targetRef )
117128 }
118129
119- allTags , err := listSemverTags ()
130+ allTags , err := listSemverTags (exec )
120131 if err != nil {
121132 return ReleaseRequest {}, err
122133 }
@@ -158,7 +169,7 @@ func calculateRCFromMainReleaseRequest(ref, commitSHA string) (ReleaseRequest, e
158169}
159170
160171// calculateRCFromBranchReleaseRequest tags an RC from the tip of a release branch.
161- func calculateRCFromBranchReleaseRequest (ref , commitSHA string ) (ReleaseRequest , error ) {
172+ func calculateRCFromBranchReleaseRequest (exec CommandExecutor , ref , commitSHA string ) (ReleaseRequest , error ) {
162173 m := branchRe .FindStringSubmatch (ref )
163174 if m == nil {
164175 return ReleaseRequest {}, xerrors .Errorf ("ref %q does not match release/X.Y" , ref )
@@ -167,17 +178,17 @@ func calculateRCFromBranchReleaseRequest(ref, commitSHA string) (ReleaseRequest,
167178 major , _ := strconv .Atoi (m [1 ])
168179 minor , _ := strconv .Atoi (m [2 ])
169180
170- targetRef , err := resolveCommit (ref , commitSHA )
181+ targetRef , err := resolveCommit (exec , ref , commitSHA )
171182 if err != nil {
172183 return ReleaseRequest {}, err
173184 }
174185
175186 // Fail if there are open PRs targeting this release branch.
176- if err := checkOpenPRs (ref ); err != nil {
187+ if err := checkOpenPRs (exec , ref ); err != nil {
177188 return ReleaseRequest {}, err
178189 }
179190
180- allTags , err := listSemverTags ()
191+ allTags , err := listSemverTags (exec )
181192 if err != nil {
182193 return ReleaseRequest {}, err
183194 }
@@ -215,7 +226,7 @@ func calculateRCFromBranchReleaseRequest(ref, commitSHA string) (ReleaseRequest,
215226
216227// createRegularReleaseRequest calculates the next release (non-RC) version from
217228// a release branch. Uses HEAD of the branch.
218- func createRegularReleaseRequest (ref string ) (ReleaseRequest , error ) {
229+ func createRegularReleaseRequest (exec CommandExecutor , ref string ) (ReleaseRequest , error ) {
219230 m := branchRe .FindStringSubmatch (ref )
220231 if m == nil {
221232 return ReleaseRequest {}, xerrors .Errorf ("ref %q does not match release/X.Y" , ref )
@@ -225,17 +236,17 @@ func createRegularReleaseRequest(ref string) (ReleaseRequest, error) {
225236 minor , _ := strconv .Atoi (m [2 ])
226237
227238 // Resolve branch HEAD.
228- headSHA , err := gitOutput ("rev-parse" , fmt .Sprintf ("origin/%s" , ref ))
239+ headSHA , err := gitOutput (exec , "rev-parse" , fmt .Sprintf ("origin/%s" , ref ))
229240 if err != nil {
230241 return ReleaseRequest {}, xerrors .Errorf ("resolve branch %s: %w" , ref , err )
231242 }
232243
233244 // Fail if there are open PRs targeting this release branch.
234- if err := checkOpenPRs (ref ); err != nil {
245+ if err := checkOpenPRs (exec , ref ); err != nil {
235246 return ReleaseRequest {}, err
236247 }
237248
238- allTags , err := listSemverTags ()
249+ allTags , err := listSemverTags (exec )
239250 if err != nil {
240251 return ReleaseRequest {}, err
241252 }
@@ -264,18 +275,18 @@ func createRegularReleaseRequest(ref string) (ReleaseRequest, error) {
264275
265276// calculateCreateBranchRequest creates a release branch and tags the next
266277// RC in one atomic step. Must be run from main.
267- func calculateCreateBranchRequest (ref , commitSHA string ) (CreateBranchRequest , error ) {
268- targetRef , err := resolveCommit (ref , commitSHA )
278+ func calculateCreateBranchRequest (exec CommandExecutor , ref , commitSHA string ) (CreateBranchRequest , error ) {
279+ targetRef , err := resolveCommit (exec , ref , commitSHA )
269280 if err != nil {
270281 return CreateBranchRequest {}, err
271282 }
272283
273284 // Verify commit is an ancestor of origin/main.
274- if err := gitRun ("merge-base" , "--is-ancestor" , targetRef , "origin/main" ); err != nil {
285+ if err := gitRun (exec , "merge-base" , "--is-ancestor" , targetRef , "origin/main" ); err != nil {
275286 return CreateBranchRequest {}, xerrors .Errorf ("commit %s is not an ancestor of origin/main" , targetRef )
276287 }
277288
278- allTags , err := listSemverTags ()
289+ allTags , err := listSemverTags (exec )
279290 if err != nil {
280291 return CreateBranchRequest {}, err
281292 }
@@ -291,7 +302,7 @@ func calculateCreateBranchRequest(ref, commitSHA string) (CreateBranchRequest, e
291302 branchName := fmt .Sprintf ("release/%d.%d" , nextMajor , nextMinor )
292303
293304 // Check that the branch doesn't already exist.
294- if _ , err := gitOutput ("rev-parse" , "--verify" , fmt .Sprintf ("origin/%s" , branchName )); err == nil {
305+ if _ , err := gitOutput (exec , "rev-parse" , "--verify" , fmt .Sprintf ("origin/%s" , branchName )); err == nil {
295306 return CreateBranchRequest {}, xerrors .Errorf ("branch %s already exists" , branchName )
296307 }
297308
@@ -417,8 +428,8 @@ func versionIsLess(a, b version) bool {
417428}
418429
419430// listSemverTags returns all semver tags from the repo.
420- func listSemverTags () ([]version , error ) {
421- out , err := gitOutput ("tag" , "--list" , "v*" )
431+ func listSemverTags (exec CommandExecutor ) ([]version , error ) {
432+ out , err := gitOutput (exec , "tag" , "--list" , "v*" )
422433 if err != nil {
423434 return nil , xerrors .Errorf ("list tags: %w" , err )
424435 }
0 commit comments