Skip to content

Commit f22450c

Browse files
authored
[0.15] fix(submit): push should run from worktree (#726)
'git push' should be run from the worktree where the submit was run instead of the .git directory of the repository even if the push operation can be done without a worktree as pre-push hooks may depend on the worktree state. Resolves #725
1 parent 508090e commit f22450c

File tree

8 files changed

+77
-17
lines changed

8 files changed

+77
-17
lines changed

.changes/v0.15.2.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## <a name="v0.15.2">v0.15.2</a> - 2025-07-07
2+
### Fixed
3+
- submit: Pre-push hooks should run in the same worktree as the submit command.

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
66
and is generated by [Changie](https://github.com/miniscruff/changie).
77

8+
## <a name="v0.15.2">v0.15.2</a> - 2025-07-07
9+
### Fixed
10+
- submit: Pre-push hooks should run in the same worktree as the submit command.
11+
812
## <a name="v0.15.1">v0.15.1</a> - 2025-06-25
913
### Fixed
1014
- Fix several operations using the incorrect Git worktree when invoked from the non-primary worktree.

branch_submit.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ func (cmd *branchSubmitCmd) run(
440440
}
441441
}
442442

443-
err = repo.Push(ctx, pushOpts)
443+
err = wt.Push(ctx, pushOpts)
444444
if err != nil {
445445
return fmt.Errorf("push branch: %w", err)
446446
}
@@ -549,7 +549,7 @@ func (cmd *branchSubmitCmd) run(
549549
}
550550
}
551551

552-
if err := repo.Push(ctx, pushOpts); err != nil {
552+
if err := wt.Push(ctx, pushOpts); err != nil {
553553
log.Error("Push failed. Branch may have been updated by someone else. Try with --force.")
554554
return fmt.Errorf("push branch: %w", err)
555555
}

internal/forge/github/integration_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,15 +293,15 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {
293293

294294
t.Logf("Pushing to origin")
295295
require.NoError(t,
296-
gitRepo.Push(ctx, git.PushOptions{
296+
gitWork.Push(ctx, git.PushOptions{
297297
Remote: "origin",
298298
Refspec: git.Refspec(branchName),
299299
}), "error pushing branch")
300300

301301
t.Cleanup(func() {
302302
t.Logf("Deleting remote branch: %s", branchName)
303303
assert.NoError(t,
304-
gitRepo.Push(context.WithoutCancel(ctx), git.PushOptions{
304+
gitWork.Push(context.WithoutCancel(ctx), git.PushOptions{
305305
Remote: "origin",
306306
Refspec: git.Refspec(":" + branchName),
307307
}), "error deleting branch")
@@ -333,7 +333,7 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {
333333
t.Logf("Pushing new base: %s", newBase)
334334
if *_update {
335335
require.NoError(t,
336-
gitRepo.Push(t.Context(), git.PushOptions{
336+
gitWork.Push(t.Context(), git.PushOptions{
337337
Remote: "origin",
338338
Refspec: git.Refspec("main:" + newBase),
339339
}), "could not push base branch")
@@ -342,7 +342,7 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {
342342
t.Logf("Deleting remote branch: %s", newBase)
343343
ctx := context.WithoutCancel(t.Context())
344344
require.NoError(t,
345-
gitRepo.Push(ctx, git.PushOptions{
345+
gitWork.Push(ctx, git.PushOptions{
346346
Remote: "origin",
347347
Refspec: git.Refspec(":" + newBase),
348348
}), "error deleting branch")
@@ -455,15 +455,15 @@ func TestIntegration_Repository_SubmitChange_baseBranchDoesNotExist(t *testing.T
455455

456456
t.Logf("Pushing to origin")
457457
require.NoError(t,
458-
gitRepo.Push(ctx, git.PushOptions{
458+
gitWork.Push(ctx, git.PushOptions{
459459
Remote: "origin",
460460
Refspec: git.Refspec(branchName),
461461
}), "error pushing branch")
462462

463463
t.Cleanup(func() {
464464
t.Logf("Deleting remote branch: %s", branchName)
465465
assert.NoError(t,
466-
gitRepo.Push(context.WithoutCancel(ctx), git.PushOptions{
466+
gitWork.Push(context.WithoutCancel(ctx), git.PushOptions{
467467
Remote: "origin",
468468
Refspec: git.Refspec(":" + branchName),
469469
}), "error deleting branch")

internal/forge/gitlab/integration_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {
305305

306306
t.Logf("Pushing to origin")
307307
require.NoError(t,
308-
gitRepo.Push(ctx, git.PushOptions{
308+
gitWork.Push(ctx, git.PushOptions{
309309
Remote: "origin",
310310
Refspec: git.Refspec(branchName),
311311
}), "error pushing branch")
@@ -314,7 +314,7 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {
314314
ctx := context.WithoutCancel(t.Context())
315315
t.Logf("Deleting remote branch: %s", branchName)
316316
assert.NoError(t,
317-
gitRepo.Push(ctx, git.PushOptions{
317+
gitWork.Push(ctx, git.PushOptions{
318318
Remote: "origin",
319319
Refspec: git.Refspec(":" + branchName),
320320
}), "error deleting branch")
@@ -347,7 +347,7 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {
347347
t.Logf("Pushing new base: %s", newBase)
348348
if *_update {
349349
require.NoError(t,
350-
gitRepo.Push(ctx, git.PushOptions{
350+
gitWork.Push(ctx, git.PushOptions{
351351
Remote: "origin",
352352
Refspec: git.Refspec("main:" + newBase),
353353
}), "could not push base branch")
@@ -356,7 +356,7 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {
356356
ctx := context.WithoutCancel(t.Context())
357357
t.Logf("Deleting remote branch: %s", newBase)
358358
require.NoError(t,
359-
gitRepo.Push(ctx, git.PushOptions{
359+
gitWork.Push(ctx, git.PushOptions{
360360
Remote: "origin",
361361
Refspec: git.Refspec(":" + newBase),
362362
}), "error deleting branch")

internal/git/push_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ func TestPushOptions_NoVerify(t *testing.T) {
6969
return nil
7070
})
7171

72-
repo := &Repository{
72+
wt := &Worktree{
7373
exec: mockExecer,
7474
}
7575

76-
err := repo.Push(t.Context(), tt.opts)
76+
err := wt.Push(t.Context(), tt.opts)
7777
require.NoError(t, err)
7878
assert.Equal(t, tt.wantCmd, gotCmd)
7979
})

internal/git/push.go renamed to internal/git/push_wt.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ type PushOptions struct {
3636
}
3737

3838
// Push pushes objects and refs to a remote repository.
39-
func (r *Repository) Push(ctx context.Context, opts PushOptions) error {
39+
func (w *Worktree) Push(ctx context.Context, opts PushOptions) error {
4040
if opts.Remote == "" && opts.Refspec == "" {
4141
return errors.New("push: no remote or refspec specified")
4242
}
4343

44-
r.log.Debug("Pushing to remote",
44+
w.log.Debug("Pushing to remote",
4545
silog.NonZero("name", opts.Remote),
4646
silog.NonZero("force", opts.Force),
4747
silog.NonZero("lease", forceWithLease(opts.ForceWithLease)))
@@ -63,7 +63,7 @@ func (r *Repository) Push(ctx context.Context, opts PushOptions) error {
6363
args = append(args, opts.Refspec.String())
6464
}
6565

66-
if err := r.gitCmd(ctx, args...).Run(r.exec); err != nil {
66+
if err := w.gitCmd(ctx, args...).Run(w.exec); err != nil {
6767
return fmt.Errorf("push: %w", err)
6868
}
6969

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Test for issue #725: pre-push hooks should work correctly with worktrees
2+
# The hook checks for file existence in the worktree, not just the main checkout
3+
4+
as 'Test User <[email protected]>'
5+
at 2025-01-15T10:00:00Z
6+
7+
mkdir repo
8+
cd repo
9+
git init
10+
git add required.txt
11+
git commit -m 'initial commit'
12+
13+
shamhub init
14+
shamhub register alice
15+
shamhub new origin alice/example.git
16+
git push origin main
17+
env SHAMHUB_USERNAME=alice
18+
gs auth login
19+
20+
# Create a pre-push hook that checks for file existence in worktree
21+
cp $WORK/extra/pre-push .git/hooks/pre-push
22+
exec chmod +x .git/hooks/pre-push
23+
24+
# Create a new branch and commit
25+
git add feature.txt
26+
gs branch create feature1 -m 'Add feature1'
27+
28+
# Remove the required file to simulate the issue
29+
git rm required.txt
30+
gs commit create -m 'remove required file'
31+
32+
# submit should fail because the required file is missing
33+
! gs branch submit --fill
34+
stderr 'required.txt not found in worktree'
35+
36+
# Restore the required file and submit again
37+
git revert HEAD --no-edit
38+
gs branch submit --fill
39+
40+
-- repo/required.txt --
41+
This file is required by the pre-push hook.
42+
-- repo/feature.txt --
43+
This is a feature file.
44+
-- extra/pre-push --
45+
#!/bin/bash
46+
47+
# Pre-push hook that checks for file existence in worktree.
48+
# This hook will always fail if the required file is not found.
49+
50+
[ -f "required.txt" ] || {
51+
echo "Error: required.txt not found in worktree" >&2
52+
exit 1
53+
}

0 commit comments

Comments
 (0)