Skip to content

git: worktree, Don't delete local untracked files when resetting worktree#1540

Merged
pjbgf merged 4 commits intogo-git:mainfrom
rilldata:reset_fix
Jun 3, 2025
Merged

git: worktree, Don't delete local untracked files when resetting worktree#1540
pjbgf merged 4 commits intogo-git:mainfrom
rilldata:reset_fix

Conversation

@k-anshul
Copy link
Contributor

@k-anshul k-anshul commented May 2, 2025

Fixes #53

Copilot AI review requested due to automatic review settings May 2, 2025 09:25
@k-anshul k-anshul changed the title git: worktree, Don't delete local untracked files when resetting work… git: worktree, Don't delete local untracked files when resetting worktree May 2, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refines the worktree reset functionality so that local untracked files are preserved when resetting the worktree, addressing issue #53.

  • Updates the test in worktree_test.go to verify that untracked files (e.g. "foo") remain unaltered.
  • Modifies the ResetSparsely, resetIndex, and resetWorktree functions in worktree.go to track removed files and adjust the file checkout logic based on untracked status.

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
worktree_test.go Replaces a simple IsClean assertion with a loop to validate specific file statuses.
worktree.go Returns removed file information from resetIndex and uses it in resetWorktree to decide which files to check out.

worktree.go Outdated
// only checkout an untracked file if it is in the list of files
// a reset should leave untracked files alone
file := nameFromAction(&ch)
if status.File(file).Worktree != Untracked || inFiles(files, file) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use ch instead of status? I think something like

action, err := ch.Action()
if err != nil {
	return err
}

if action != merkletrie.Insert {
    // checkout the file
}

Would work since "Untracked" would mean that the file appears in worktree but not in staging.

For reference, Status() also works this way internally.

go-git/worktree_status.go

Lines 93 to 118 in 93ed29d

right, err := w.diffStagingWithWorktree(false, true)
if err != nil {
return nil, err
}
for _, ch := range right {
a, err := ch.Action()
if err != nil {
return nil, err
}
fs := s.File(nameFromAction(&ch))
if fs.Staging == Untracked {
fs.Staging = Unmodified
}
switch a {
case merkletrie.Delete:
fs.Worktree = Deleted
case merkletrie.Insert:
fs.Worktree = Untracked
fs.Staging = Untracked
case merkletrie.Modify:
fs.Worktree = Modified
}
}

This way we can avoid re-doing computations that we already have done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @onee-only

Thanks for the reviews. I just realised that I don't need the check at all and I can directly do checkout.
The files being passed is always going to just include the files which need changes and no other files.

worktree.go Outdated
// only checkout an untracked file if it is in the list of files
// a reset should leave untracked files alone
file := nameFromAction(&ch)
if status.File(file).Worktree != Untracked || inFiles(files, file) {
Copy link
Contributor

@onee-only onee-only May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any specific reason that we check inFiles here? I think it is already done in block above.
I wasn't paying attention to the comment. Sorry for the confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey

You were right actually. I just removed the complete condition.

@k-anshul k-anshul requested a review from onee-only May 5, 2025 06:42
@UnseenBook
Copy link
Contributor

I have been looking for a solution to the same problem. I have tried these changes and they work in most cases, but when checking out to a branch with the same commit, so no files change, it still deletes all untracked files (including files that are in the gitignore).
From brief manual testing I think it's because when no tracked files change the new list of removedFiles is empty, so it is not checked. If I understand correctly, the list of files passed to resetWorktree are supposed to be the only files that need to be reset in the worktree. (I have been trying to piece together the logic behind all this and find it very difficult to figure out why certain things are done the way they are. Not that there is anything wrong with that, just saying that I might be completely wrong.)
If my assumption is correct then the logic in resetWorktree has to change slightly. If the list of files passed to it is empty the for loop can be skipped, because there is no work to be done. I don't know if the whole function can be skipped, because there is also some index writing done, but I don't know if that needs to stay or not.
There might be other solutions, but from my brief testing this solved the issue of deleted untracked files. Tomorrow I will do some more testing.

@k-anshul
Copy link
Contributor Author

k-anshul commented May 8, 2025

Hey @UnseenBook

Thanks for looking into this and yes it does fail when reseting on to same commit. I am also discovering some more edge cases. I will take a look at it later again.

@UnseenBook
Copy link
Contributor

@k-anshul I have created a PR to your branch with changes that I believe are what we need to solve this issue.
I realized that a hard reset does need to remove untracked and ignored files. So I made it only skip resetWorktree if the removedFiles slice is empty AND when mode is set to MergeReset, so that HardReset still uses the list of files provided in the ResetOptions.

The tests run successfully with this change. I hope this helps.

@k-anshul
Copy link
Contributor Author

k-anshul commented May 9, 2025

@k-anshul I have created a PR to your branch with changes that I believe are what we need to solve this issue. I realized that a hard reset does need to remove untracked and ignored files. So I made it only skip resetWorktree if the removedFiles slice is empty AND when mode is set to MergeReset, so that HardReset still uses the list of files provided in the ResetOptions.

The tests run successfully with this change. I hope this helps.

Sounds good @UnseenBook. Just merged your changes in my PR. Thanks for looking into this.

@UnseenBook
Copy link
Contributor

Thanks @k-anshul !
What do you expect there is left to fully fix this issue? You mentioned you encountered more edge cases, might they be fixed with my changes, or are they very different? I would love to help :)

@k-anshul
Copy link
Contributor Author

Thanks @k-anshul ! What do you expect there is left to fully fix this issue? You mentioned you encountered more edge cases, might they be fixed with my changes, or are they very different? I would love to help :)

I could not reproduce the issues after your fix :)

@jacksoncalvert
Copy link

@k-anshul can you adjust the commit message to get this merged please?

@k-anshul
Copy link
Contributor Author

@k-anshul can you adjust the commit message to get this merged please?

Done @jacksoncalvert

@jacksoncalvert
Copy link

@onee-only we are seeing this problem in multiple instances. can we get this merged please

@onee-only
Copy link
Contributor

@onee-only we are seeing this problem in multiple instances. can we get this merged please

Unfortunately I'm not the maintainer of this project. Let's wait for maintainers to review this PR, though they appear to be inactive on go-git at the moment.

@jacksoncalvert
Copy link

@pjbgf Hi Paulo, can we please get this merged in. My team is using this package and this issue is hugely blocking for some of our tasks.

Cheers

@UnseenBook
Copy link
Contributor

If it's desired to have a test for this fix, I created a PR to add one: rilldata#4

@k-anshul If you merge the PR by using the rebase option, it should not create a merge commit that would fail the commit message check.

Copy link
Member

@pjbgf pjbgf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@k-anshul thanks for working on this. 🙇

@pjbgf pjbgf merged commit 89fc507 into go-git:main Jun 3, 2025
16 checks passed
@pjbgf pjbgf mentioned this pull request Jun 4, 2025
@Ch00k
Copy link
Contributor

Ch00k commented Jul 11, 2025

Is there any chance this change could be incorporated in one of the upcoming v5.x.x releases?

Ch00k pushed a commit to Ch00k/go-git that referenced this pull request Jul 12, 2025
git: worktree, Don't delete local untracked files when resetting worktree
Ch00k pushed a commit to Ch00k/go-git that referenced this pull request Sep 20, 2025
git: worktree, Don't delete local untracked files when resetting worktree
@pjbgf
Copy link
Member

pjbgf commented Dec 12, 2025

@Ch00k feel free to create a back-port targeting the v5 release branch and tagging me for a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pull removes untracked files locally

7 participants