Why doesn't git checkout FILE reapply uncommitted changes?
git checkout COMMIT
switches your current state to that pointed by COMMIT
and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the COMMIT
you want to switch to.
This functions in stark contrast to git checkout COMMIT FILE
which just blindly discards any uncommitted changes (without any warning) of FILE
and "forces" the state of FILE
to that in COMMIT
.
My question is, why the inconsistency? (perhaps with regards to historic / design decisions)
Surely it could have been made such that git checkout COMMIT FILE
attempted to reapply uncommitted changes to FILE
and failed gracefully if that failed. Especially since git checkout
is often marketed as a "safe" operation. If anything, the current behavior of git checkout COMMIT FILE
should be the behavior of git reset --hard COMMIT FILE
. (On a side note, the behavior of git reset COMMIT
and git reset FILE
also leaves room for improvement in consistency...)
git
add a comment |
git checkout COMMIT
switches your current state to that pointed by COMMIT
and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the COMMIT
you want to switch to.
This functions in stark contrast to git checkout COMMIT FILE
which just blindly discards any uncommitted changes (without any warning) of FILE
and "forces" the state of FILE
to that in COMMIT
.
My question is, why the inconsistency? (perhaps with regards to historic / design decisions)
Surely it could have been made such that git checkout COMMIT FILE
attempted to reapply uncommitted changes to FILE
and failed gracefully if that failed. Especially since git checkout
is often marketed as a "safe" operation. If anything, the current behavior of git checkout COMMIT FILE
should be the behavior of git reset --hard COMMIT FILE
. (On a side note, the behavior of git reset COMMIT
and git reset FILE
also leaves room for improvement in consistency...)
git
add a comment |
git checkout COMMIT
switches your current state to that pointed by COMMIT
and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the COMMIT
you want to switch to.
This functions in stark contrast to git checkout COMMIT FILE
which just blindly discards any uncommitted changes (without any warning) of FILE
and "forces" the state of FILE
to that in COMMIT
.
My question is, why the inconsistency? (perhaps with regards to historic / design decisions)
Surely it could have been made such that git checkout COMMIT FILE
attempted to reapply uncommitted changes to FILE
and failed gracefully if that failed. Especially since git checkout
is often marketed as a "safe" operation. If anything, the current behavior of git checkout COMMIT FILE
should be the behavior of git reset --hard COMMIT FILE
. (On a side note, the behavior of git reset COMMIT
and git reset FILE
also leaves room for improvement in consistency...)
git
git checkout COMMIT
switches your current state to that pointed by COMMIT
and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the COMMIT
you want to switch to.
This functions in stark contrast to git checkout COMMIT FILE
which just blindly discards any uncommitted changes (without any warning) of FILE
and "forces" the state of FILE
to that in COMMIT
.
My question is, why the inconsistency? (perhaps with regards to historic / design decisions)
Surely it could have been made such that git checkout COMMIT FILE
attempted to reapply uncommitted changes to FILE
and failed gracefully if that failed. Especially since git checkout
is often marketed as a "safe" operation. If anything, the current behavior of git checkout COMMIT FILE
should be the behavior of git reset --hard COMMIT FILE
. (On a side note, the behavior of git reset COMMIT
and git reset FILE
also leaves room for improvement in consistency...)
git
git
asked Nov 28 '18 at 2:16
WoofasWoofas
530322
530322
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
git checkout commit
switches your current state to that pointed bycommit
...
Yes, sort of. It's actually substantially more complicated under the hood.
... and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the
commit
you want to switch to).
It really doesn't do that first part (but does do the second), and that's the complication: what git checkout
is doing is a subset of what the git read-tree
documentation calls a two tree merge. If you follow this link, you'll see that this has 21 separate cases listed (one, case #3, has two sub-cases, and I'm ignoring case #0 which cannot happen, so if you count the sub-cases and case #0 you get 23 different cases). I won't list them all here, but they kind of boil down to this idea: If Git doesn't have to touch the index and work-tree when switching commits, it doesn't. That's what keeps your uncommitted changes in place: Git is not re-applying anything, it's just not ripping out anything either. It just gently tiptoes around the uncommitted changes.
That, in turn, sort of (but not entirely) explains this radically different behavior, that some—including me—might go so far as to call evil:
This functions in stark contrast to
git checkout commit file
which just blindly discards any uncommitted changes (without any warning) offile
and "forces" the state offile
to that incommit
.
Right, and this is because Git views your command as a request to replace the index copy of file
with that from commit
. The updated index copy is copied out to the work-tree as well.
I would argue (and have in the past) that this should be a separate Git command. It happens to be implemented by the same source code simply because that source code is full of "extract file from commit, write to index and work-tree" code. But so is git reset
, and git reset
is a separate command.1 So this is at best an excuse of the Hysterical Raisins form.
Surely it could have been made such that
git checkout commit file
attempted to reapply uncommitted changes tofile
and failed gracefully if that failed.
That, too, is available in git checkout
, under the -m
option ... sort of. However, it's not on a per file basis, but rather on a whole commit basis only. That is, you can git checkout -m commit
, which merges your work-tree with the target commit (as a three-tree merge), but not git checkout -m commit file
. To get that effect, extract the file from the commit, choose a merge base version to extract, and use git merge-file
on the three files:
git show other:file > file.other
git show base:file > file.base
git merge-file file file.base file.other
1Git's reset
also crams too many unrelated bits of functionality into one user-facing command, if you ask me, but you didn't.
(On a side note, the behavior of
git reset commit
andgit reset file
also leaves room for improvement in consistency...)
OK, maybe you did! :-)
add a comment |
When you check out a commit, git does not "reapply" changes. It leaves modified work tree and index copies unchanged if and only if the checkout would not affect the file - i.e. the version of the file in the commit you're checking out is the same as the version of the file in the pre-checkout HEAD commit. But if it has to touch a file at all, and the index or working tree copy of that file has modifications, then the checkout aborts.
When you checkout
a specific path, that path is clobbered without warning because that is the definition and purpose of checking out a specific path. You say git checkout -- myfile.txt
specifically because you want to revert myfile.txt
, and in fact that's the command git status
would recommend using if you want to revert myfile.txt
. Keep in mind that for this to be consistent with the "checkout commit" behavior, it still wouldn't try to reapply (merge) the local changes; it would just refuse to do the checkout on any modified file. (And it's fair to say that wouldn't make much sense.)
The seeming inconsistency is one of several symptoms of the unfortunate choice to use the same keyword for two entirely different operations. While either of those operations could reasonably be named "checkout", it is one of the few decisions I really dislike about git that it uses that name for both.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53511125%2fwhy-doesnt-git-checkout-file-reapply-uncommitted-changes%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
git checkout commit
switches your current state to that pointed bycommit
...
Yes, sort of. It's actually substantially more complicated under the hood.
... and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the
commit
you want to switch to).
It really doesn't do that first part (but does do the second), and that's the complication: what git checkout
is doing is a subset of what the git read-tree
documentation calls a two tree merge. If you follow this link, you'll see that this has 21 separate cases listed (one, case #3, has two sub-cases, and I'm ignoring case #0 which cannot happen, so if you count the sub-cases and case #0 you get 23 different cases). I won't list them all here, but they kind of boil down to this idea: If Git doesn't have to touch the index and work-tree when switching commits, it doesn't. That's what keeps your uncommitted changes in place: Git is not re-applying anything, it's just not ripping out anything either. It just gently tiptoes around the uncommitted changes.
That, in turn, sort of (but not entirely) explains this radically different behavior, that some—including me—might go so far as to call evil:
This functions in stark contrast to
git checkout commit file
which just blindly discards any uncommitted changes (without any warning) offile
and "forces" the state offile
to that incommit
.
Right, and this is because Git views your command as a request to replace the index copy of file
with that from commit
. The updated index copy is copied out to the work-tree as well.
I would argue (and have in the past) that this should be a separate Git command. It happens to be implemented by the same source code simply because that source code is full of "extract file from commit, write to index and work-tree" code. But so is git reset
, and git reset
is a separate command.1 So this is at best an excuse of the Hysterical Raisins form.
Surely it could have been made such that
git checkout commit file
attempted to reapply uncommitted changes tofile
and failed gracefully if that failed.
That, too, is available in git checkout
, under the -m
option ... sort of. However, it's not on a per file basis, but rather on a whole commit basis only. That is, you can git checkout -m commit
, which merges your work-tree with the target commit (as a three-tree merge), but not git checkout -m commit file
. To get that effect, extract the file from the commit, choose a merge base version to extract, and use git merge-file
on the three files:
git show other:file > file.other
git show base:file > file.base
git merge-file file file.base file.other
1Git's reset
also crams too many unrelated bits of functionality into one user-facing command, if you ask me, but you didn't.
(On a side note, the behavior of
git reset commit
andgit reset file
also leaves room for improvement in consistency...)
OK, maybe you did! :-)
add a comment |
git checkout commit
switches your current state to that pointed bycommit
...
Yes, sort of. It's actually substantially more complicated under the hood.
... and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the
commit
you want to switch to).
It really doesn't do that first part (but does do the second), and that's the complication: what git checkout
is doing is a subset of what the git read-tree
documentation calls a two tree merge. If you follow this link, you'll see that this has 21 separate cases listed (one, case #3, has two sub-cases, and I'm ignoring case #0 which cannot happen, so if you count the sub-cases and case #0 you get 23 different cases). I won't list them all here, but they kind of boil down to this idea: If Git doesn't have to touch the index and work-tree when switching commits, it doesn't. That's what keeps your uncommitted changes in place: Git is not re-applying anything, it's just not ripping out anything either. It just gently tiptoes around the uncommitted changes.
That, in turn, sort of (but not entirely) explains this radically different behavior, that some—including me—might go so far as to call evil:
This functions in stark contrast to
git checkout commit file
which just blindly discards any uncommitted changes (without any warning) offile
and "forces" the state offile
to that incommit
.
Right, and this is because Git views your command as a request to replace the index copy of file
with that from commit
. The updated index copy is copied out to the work-tree as well.
I would argue (and have in the past) that this should be a separate Git command. It happens to be implemented by the same source code simply because that source code is full of "extract file from commit, write to index and work-tree" code. But so is git reset
, and git reset
is a separate command.1 So this is at best an excuse of the Hysterical Raisins form.
Surely it could have been made such that
git checkout commit file
attempted to reapply uncommitted changes tofile
and failed gracefully if that failed.
That, too, is available in git checkout
, under the -m
option ... sort of. However, it's not on a per file basis, but rather on a whole commit basis only. That is, you can git checkout -m commit
, which merges your work-tree with the target commit (as a three-tree merge), but not git checkout -m commit file
. To get that effect, extract the file from the commit, choose a merge base version to extract, and use git merge-file
on the three files:
git show other:file > file.other
git show base:file > file.base
git merge-file file file.base file.other
1Git's reset
also crams too many unrelated bits of functionality into one user-facing command, if you ask me, but you didn't.
(On a side note, the behavior of
git reset commit
andgit reset file
also leaves room for improvement in consistency...)
OK, maybe you did! :-)
add a comment |
git checkout commit
switches your current state to that pointed bycommit
...
Yes, sort of. It's actually substantially more complicated under the hood.
... and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the
commit
you want to switch to).
It really doesn't do that first part (but does do the second), and that's the complication: what git checkout
is doing is a subset of what the git read-tree
documentation calls a two tree merge. If you follow this link, you'll see that this has 21 separate cases listed (one, case #3, has two sub-cases, and I'm ignoring case #0 which cannot happen, so if you count the sub-cases and case #0 you get 23 different cases). I won't list them all here, but they kind of boil down to this idea: If Git doesn't have to touch the index and work-tree when switching commits, it doesn't. That's what keeps your uncommitted changes in place: Git is not re-applying anything, it's just not ripping out anything either. It just gently tiptoes around the uncommitted changes.
That, in turn, sort of (but not entirely) explains this radically different behavior, that some—including me—might go so far as to call evil:
This functions in stark contrast to
git checkout commit file
which just blindly discards any uncommitted changes (without any warning) offile
and "forces" the state offile
to that incommit
.
Right, and this is because Git views your command as a request to replace the index copy of file
with that from commit
. The updated index copy is copied out to the work-tree as well.
I would argue (and have in the past) that this should be a separate Git command. It happens to be implemented by the same source code simply because that source code is full of "extract file from commit, write to index and work-tree" code. But so is git reset
, and git reset
is a separate command.1 So this is at best an excuse of the Hysterical Raisins form.
Surely it could have been made such that
git checkout commit file
attempted to reapply uncommitted changes tofile
and failed gracefully if that failed.
That, too, is available in git checkout
, under the -m
option ... sort of. However, it's not on a per file basis, but rather on a whole commit basis only. That is, you can git checkout -m commit
, which merges your work-tree with the target commit (as a three-tree merge), but not git checkout -m commit file
. To get that effect, extract the file from the commit, choose a merge base version to extract, and use git merge-file
on the three files:
git show other:file > file.other
git show base:file > file.base
git merge-file file file.base file.other
1Git's reset
also crams too many unrelated bits of functionality into one user-facing command, if you ask me, but you didn't.
(On a side note, the behavior of
git reset commit
andgit reset file
also leaves room for improvement in consistency...)
OK, maybe you did! :-)
git checkout commit
switches your current state to that pointed bycommit
...
Yes, sort of. It's actually substantially more complicated under the hood.
... and reapplies any uncommitted changes (it even fails nicely if your uncommitted changes are incompatible with the
commit
you want to switch to).
It really doesn't do that first part (but does do the second), and that's the complication: what git checkout
is doing is a subset of what the git read-tree
documentation calls a two tree merge. If you follow this link, you'll see that this has 21 separate cases listed (one, case #3, has two sub-cases, and I'm ignoring case #0 which cannot happen, so if you count the sub-cases and case #0 you get 23 different cases). I won't list them all here, but they kind of boil down to this idea: If Git doesn't have to touch the index and work-tree when switching commits, it doesn't. That's what keeps your uncommitted changes in place: Git is not re-applying anything, it's just not ripping out anything either. It just gently tiptoes around the uncommitted changes.
That, in turn, sort of (but not entirely) explains this radically different behavior, that some—including me—might go so far as to call evil:
This functions in stark contrast to
git checkout commit file
which just blindly discards any uncommitted changes (without any warning) offile
and "forces" the state offile
to that incommit
.
Right, and this is because Git views your command as a request to replace the index copy of file
with that from commit
. The updated index copy is copied out to the work-tree as well.
I would argue (and have in the past) that this should be a separate Git command. It happens to be implemented by the same source code simply because that source code is full of "extract file from commit, write to index and work-tree" code. But so is git reset
, and git reset
is a separate command.1 So this is at best an excuse of the Hysterical Raisins form.
Surely it could have been made such that
git checkout commit file
attempted to reapply uncommitted changes tofile
and failed gracefully if that failed.
That, too, is available in git checkout
, under the -m
option ... sort of. However, it's not on a per file basis, but rather on a whole commit basis only. That is, you can git checkout -m commit
, which merges your work-tree with the target commit (as a three-tree merge), but not git checkout -m commit file
. To get that effect, extract the file from the commit, choose a merge base version to extract, and use git merge-file
on the three files:
git show other:file > file.other
git show base:file > file.base
git merge-file file file.base file.other
1Git's reset
also crams too many unrelated bits of functionality into one user-facing command, if you ask me, but you didn't.
(On a side note, the behavior of
git reset commit
andgit reset file
also leaves room for improvement in consistency...)
OK, maybe you did! :-)
answered Nov 28 '18 at 2:48
torektorek
195k18243322
195k18243322
add a comment |
add a comment |
When you check out a commit, git does not "reapply" changes. It leaves modified work tree and index copies unchanged if and only if the checkout would not affect the file - i.e. the version of the file in the commit you're checking out is the same as the version of the file in the pre-checkout HEAD commit. But if it has to touch a file at all, and the index or working tree copy of that file has modifications, then the checkout aborts.
When you checkout
a specific path, that path is clobbered without warning because that is the definition and purpose of checking out a specific path. You say git checkout -- myfile.txt
specifically because you want to revert myfile.txt
, and in fact that's the command git status
would recommend using if you want to revert myfile.txt
. Keep in mind that for this to be consistent with the "checkout commit" behavior, it still wouldn't try to reapply (merge) the local changes; it would just refuse to do the checkout on any modified file. (And it's fair to say that wouldn't make much sense.)
The seeming inconsistency is one of several symptoms of the unfortunate choice to use the same keyword for two entirely different operations. While either of those operations could reasonably be named "checkout", it is one of the few decisions I really dislike about git that it uses that name for both.
add a comment |
When you check out a commit, git does not "reapply" changes. It leaves modified work tree and index copies unchanged if and only if the checkout would not affect the file - i.e. the version of the file in the commit you're checking out is the same as the version of the file in the pre-checkout HEAD commit. But if it has to touch a file at all, and the index or working tree copy of that file has modifications, then the checkout aborts.
When you checkout
a specific path, that path is clobbered without warning because that is the definition and purpose of checking out a specific path. You say git checkout -- myfile.txt
specifically because you want to revert myfile.txt
, and in fact that's the command git status
would recommend using if you want to revert myfile.txt
. Keep in mind that for this to be consistent with the "checkout commit" behavior, it still wouldn't try to reapply (merge) the local changes; it would just refuse to do the checkout on any modified file. (And it's fair to say that wouldn't make much sense.)
The seeming inconsistency is one of several symptoms of the unfortunate choice to use the same keyword for two entirely different operations. While either of those operations could reasonably be named "checkout", it is one of the few decisions I really dislike about git that it uses that name for both.
add a comment |
When you check out a commit, git does not "reapply" changes. It leaves modified work tree and index copies unchanged if and only if the checkout would not affect the file - i.e. the version of the file in the commit you're checking out is the same as the version of the file in the pre-checkout HEAD commit. But if it has to touch a file at all, and the index or working tree copy of that file has modifications, then the checkout aborts.
When you checkout
a specific path, that path is clobbered without warning because that is the definition and purpose of checking out a specific path. You say git checkout -- myfile.txt
specifically because you want to revert myfile.txt
, and in fact that's the command git status
would recommend using if you want to revert myfile.txt
. Keep in mind that for this to be consistent with the "checkout commit" behavior, it still wouldn't try to reapply (merge) the local changes; it would just refuse to do the checkout on any modified file. (And it's fair to say that wouldn't make much sense.)
The seeming inconsistency is one of several symptoms of the unfortunate choice to use the same keyword for two entirely different operations. While either of those operations could reasonably be named "checkout", it is one of the few decisions I really dislike about git that it uses that name for both.
When you check out a commit, git does not "reapply" changes. It leaves modified work tree and index copies unchanged if and only if the checkout would not affect the file - i.e. the version of the file in the commit you're checking out is the same as the version of the file in the pre-checkout HEAD commit. But if it has to touch a file at all, and the index or working tree copy of that file has modifications, then the checkout aborts.
When you checkout
a specific path, that path is clobbered without warning because that is the definition and purpose of checking out a specific path. You say git checkout -- myfile.txt
specifically because you want to revert myfile.txt
, and in fact that's the command git status
would recommend using if you want to revert myfile.txt
. Keep in mind that for this to be consistent with the "checkout commit" behavior, it still wouldn't try to reapply (merge) the local changes; it would just refuse to do the checkout on any modified file. (And it's fair to say that wouldn't make much sense.)
The seeming inconsistency is one of several symptoms of the unfortunate choice to use the same keyword for two entirely different operations. While either of those operations could reasonably be named "checkout", it is one of the few decisions I really dislike about git that it uses that name for both.
answered Nov 28 '18 at 2:46
Mark AdelsbergerMark Adelsberger
21.6k11321
21.6k11321
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53511125%2fwhy-doesnt-git-checkout-file-reapply-uncommitted-changes%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown