質問

I want to "undo" all local changes to one specific file, i.e. return to this file's state at origin/master:

  • if it existed and was modified => revert to old version.
  • if it was added => delete file.
  • if it was removed => re-add file.
  • if it was renamed => rename back to old name.
  • etc.

git checkout origin/master -- path/to/file.txt works for modified files but fails notably for files that were added and thus did not exist at origin/master ("No such file or directory").

If need be I can rely on commits only ever changing one file. (Then perhaps I could parse the SHAs from git log --follow --oneline --decorate origin/master.. path/to/file.txt and then revert (or rebase/drop) those commits. But this seems manual and hacky and I'd prefer a more declarative solution...)

UPDATE: To clarify, the local changes have been committed. There may be multiple commits with changes to the file.

役に立ちましたか?

解決

Except for the rename issue, the general approach (git checkout <rev> -- <path>) you suggested first is the obvious right way to go. If that fails, the file must have been added, so you can simply take "failure" as "indication to remove".

Except, again, for the rename issue.

Here, you could use the git log --follow approach you suggested. Follow, watching for rename ops, and collect them up. You can use --name-status to observe renames, e.g.:

$ git log --follow --oneline --name-status -- builtin/var.c
f9bc573 ident: rename IDENT_ERROR_ON_NO_NAME to IDENT_STRICT
M       builtin/var.c
c2e86ad Fix sparse warnings
M       builtin/var.c
2bc8c1a var: run setup_git_directory_gently() sooner
M       builtin/var.c
81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory
R100    builtin-var.c   builtin/var.c
64778d2 Make 'git var GIT_PAGER' always print the configured pager
M       builtin-var.c
9fabb6d Fix 'git var' usage synopsis
M       builtin-var.c
55b6745 make "git var" a built-in
R096    var.c   builtin-var.c
6361824 Teach git var about GIT_PAGER
M       var.c
[snip]

(I ran the above on the git sources themselves). There's a fairly big hiccup here, as you must know the new name of the file, not the old one, but perhaps that's the case in question.

Having followed the renames, you can then git checkout <rev> -- <path-by-name-at-the-time>. The --name-status part will also show if the file was created in a rev preceding the historical rev (the status will be A, added), and you will know to remove the file.

That leaves out the "file was removed" case, which conflicts with the basic idea that you know the new name of the file. When file foo is removed, it has only an old name in the first place.

There is, as far as I can tell, no good way to handle this last case. You can assume that if foo existed in the "old" commit and you have no new name for it, it must have been removed; but if the idea of following renames is to start with an old name, this does not work in git: the --follow option only works when starting with recent history and moving back in time.

他のヒント

I'm assuming you've already made the commit with all your changes locally...

I'd do:

git reset HEAD~

git checkout path/to/file.txt

git add -A

git commit -m "commit message"

That could easily be added to a bash / other shell as a script too.

If there are multiple commits with changes to the file, you could do:

git log -p path/to/file.txt

Then find the commit you want to revert the file to. Then, revert just that file:

git checkout <sha-from-above> path/to/file.txt

See: Reset or revert a specific file to a specific revision using Git?

I managed to get my intended result with these commands:

$ git reset origin/master path/to/file.txt
$ git commit -m "undo changes to path/to/file.txt"
[master ab4553d] undo changes to path/to/file.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 path/to/file.txt
$ git reset --hard
HEAD is now at ab4553d undo changes to path/to/file.txt

After cursory testing, this works as expected for added/removed/modified files. For renamed files, the git reset needs to be done for both the old and new names.

Is there a downside to using git reset rather than git checkout?

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top