Skip to content

Commit

Permalink
branch create: Don't lose data on branch creation error (#318)
Browse files Browse the repository at this point in the history
If branch creation fails for any reason,
reset to the state before commit before checking out original branch.
This will prevent those committed changes from being lost.

Resolves #307
  • Loading branch information
abhinav authored Jul 30, 2024
1 parent 35c0b76 commit ec843e3
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .changes/unreleased/Fixed-20240729-210429.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Fixed
body: 'branch create: Don''t lose data if the branch cannot be created for any reason.'
time: 2024-07-29T21:04:29.049874-07:00
41 changes: 37 additions & 4 deletions branch_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,39 @@ func (cmd *branchCreateCmd) Run(ctx context.Context, log *log.Logger, opts *glob
if err := repo.DetachHead(ctx, baseName); err != nil {
return fmt.Errorf("detach head: %w", err)
}
// From this point on, if there's an error,
// restore the original branch.

// From this point on, to prevent data loss,
// we'll revert to original branch while keeping the changes
// if we failed to successfully create the new branch.
//
// The condition for this is not whether an error is returned,
// and whether the new branch was successfully created.
var (
branchCreated bool // whether the new branch was created
commitHash git.Hash // hash of the commit (if created)
)
defer func() {
if err != nil {
err = errors.Join(err, repo.Checkout(ctx, cmd.Target))
if branchCreated {
return
}

log.Warn("Unable to create branch. Rolling back.",
"branch", cmd.Target)

// Move HEAD to the state just before the commit
// while leaving the index and working tree as-is.
resetErr := repo.Reset(ctx, commitHash.String()+"^", git.ResetOptions{
Mode: git.ResetSoft,
Quiet: true,
})
if resetErr != nil {
log.Warn("Could not reset to parent commit.",
"commit", commitHash,
"error", resetErr)
}

err = errors.Join(err,
repo.Checkout(ctx, cmd.Target))
}()

if err := repo.Commit(ctx, git.CommitRequest{
Expand All @@ -161,6 +188,11 @@ func (cmd *branchCreateCmd) Run(ctx context.Context, log *log.Logger, opts *glob
return fmt.Errorf("commit: %w", err)
}

commitHash, err = repo.Head(ctx)
if err != nil {
return fmt.Errorf("get commit hash: %w", err)
}

if cmd.Name == "" {
// Branch name was not specified.
// Generate one from the commit message.
Expand Down Expand Up @@ -190,6 +222,7 @@ func (cmd *branchCreateCmd) Run(ctx context.Context, log *log.Logger, opts *glob
return fmt.Errorf("create branch: %w", err)
}

branchCreated = true
if err := repo.Checkout(ctx, cmd.Name); err != nil {
return fmt.Errorf("checkout branch: %w", err)
}
Expand Down
34 changes: 34 additions & 0 deletions testdata/script/issue307_branch_create_cannot_create.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 'branch create' does not lose files if a branch cannot be created.
# https://github.com/abhinav/git-spice/issues/307

as 'Test <test@example.com>'
at '2024-03-30T14:59:32Z'

cd repo
git init
git commit --allow-empty -m 'Initial commit'
gs repo init

git checkout -b feature
git add feature1.txt
git commit -m 'Add feature1'

git checkout main
git add feature2.txt

# will be unable to create the branch because
# 'feature' is a file so a directory cannot be created there.
! gs branch create feature/2 -m 'Add feature2'

# should not lose the file.
exists feature2.txt

git status --porcelain
cmp stdout $WORK/golden/status.txt

-- repo/feature1.txt --
feature 1
-- repo/feature2.txt --
feature 2
-- golden/status.txt --
A feature2.txt

0 comments on commit ec843e3

Please sign in to comment.