Why doesn't git checkout FILE reapply uncommitted changes?












2















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...)










share|improve this question



























    2















    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...)










    share|improve this question

























      2












      2








      2


      1






      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...)










      share|improve this question














      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






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 28 '18 at 2:16









      WoofasWoofas

      530322




      530322
























          2 Answers
          2






          active

          oldest

          votes


















          3















          git checkout commit switches your current state to that pointed by commit ...




          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) of file and "forces" the state of file to that in commit.




          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 to file 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 and git reset file also leaves room for improvement in consistency...)




          OK, maybe you did! :-)






          share|improve this answer































            1














            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.






            share|improve this answer























              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
              });


              }
              });














              draft saved

              draft discarded


















              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









              3















              git checkout commit switches your current state to that pointed by commit ...




              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) of file and "forces" the state of file to that in commit.




              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 to file 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 and git reset file also leaves room for improvement in consistency...)




              OK, maybe you did! :-)






              share|improve this answer




























                3















                git checkout commit switches your current state to that pointed by commit ...




                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) of file and "forces" the state of file to that in commit.




                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 to file 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 and git reset file also leaves room for improvement in consistency...)




                OK, maybe you did! :-)






                share|improve this answer


























                  3












                  3








                  3








                  git checkout commit switches your current state to that pointed by commit ...




                  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) of file and "forces" the state of file to that in commit.




                  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 to file 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 and git reset file also leaves room for improvement in consistency...)




                  OK, maybe you did! :-)






                  share|improve this answer














                  git checkout commit switches your current state to that pointed by commit ...




                  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) of file and "forces" the state of file to that in commit.




                  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 to file 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 and git reset file also leaves room for improvement in consistency...)




                  OK, maybe you did! :-)







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 28 '18 at 2:48









                  torektorek

                  195k18243322




                  195k18243322

























                      1














                      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.






                      share|improve this answer




























                        1














                        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.






                        share|improve this answer


























                          1












                          1








                          1







                          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.






                          share|improve this answer













                          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.







                          share|improve this answer












                          share|improve this answer



                          share|improve this answer










                          answered Nov 28 '18 at 2:46









                          Mark AdelsbergerMark Adelsberger

                          21.6k11321




                          21.6k11321






























                              draft saved

                              draft discarded




















































                              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.




                              draft saved


                              draft discarded














                              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





















































                              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







                              Popular posts from this blog

                              A CLEAN and SIMPLE way to add appendices to Table of Contents and bookmarks

                              Calculate evaluation metrics using cross_val_predict sklearn

                              Insert data from modal to MySQL (multiple modal on website)