Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git : Move staged changes to different or new branch

Tags:

git

I can currently do it by following these three steps

  1. Unstage by using git reset HEAD <file>...
  2. checkout to the branch git checkout [-b] <branchname>
  3. Stage again git add <file>...

Is there a better way of moving the staged changes?

like image 238
zero Avatar asked May 24 '17 15:05

zero


2 Answers

Edmundo's answer is the right one, but it could use some details about why it's the right one.

There are two cases of interest here:

  • Making a new branch that starts from the commit where you are now is trivially easy:

    $ git checkout -b newbranch
    ... continue working; git add and/or git commit as usual ...
    

    The git checkout -b step never fails as long as the new branch itself is OK (has a valid name that does not collide with an existing name).

  • Moving to some other, already-existing branch may be trivially easy:

    $ git checkout otherbranch
    ... continue working ...
    

    If that fails, you will want/need to make at least one commit.

    You can make this commit as an ordinary commit, that you then copy to the new branch and remove from the current branch; or you can use git stash, which actually makes two commits.1 It's just that these two commits are not on any branch, which makes it more obvious that git stash apply can re-apply those commits anywhere. (Using git stash pop is just saying git stash apply && git stash drop, i.e., apply and then, if Git thinks that went OK—whether or not it really did go well—drop the stash. I recommend splitting the two so that you can check whether it went well, though usually it does and in the end this is usually quite minor.)


1These two commits are to save (a) the index/staging area, and (b) corresponding files (i.e., all tracked files) in the work-tree. If you use git stash save --untracked or git stash save --all, it actually makes three commits. You probably don't want the three-commit form, which is substantially trickier.


What's going on

When you have made changes, these changes are stored in files. These files appear in one or both of two places:

  • your work tree, which is where you have your files in ordinary-file-form, so that you can work with them (hence the name "work tree"); or
  • your staging area, which Git also calls the index.

The index or staging area is where you build the next commit you will make. Normally, it already has in it—it starts out with—all the files from your current commit, in the same form they have in the current commit (which, incidentally, is always known as HEAD). That is, you run:

git checkout <branch-name-or-commit-hash-or-similar>

and both your work tree and your index/staging-area get filled in so that they match the commit you just checked out, which now known as HEAD. (If you check out a branch name, the commit you're checking out is the latest, or tip, commit on that branch.) Let's say, for instance, that you did git checkout master.

If you don't touch the index and work tree at this point, you can, obviously, git checkout any other commit, e.g., git checkout develop. It's fine for Git to throw away the versions in your index and work-tree, because they match the commit you had checked out. If it tosses out a README file, or even your entire working set of files, so what? They're still in that other commit, the tip commit of master. You can just git checkout master again when you want them back.

But that's not the case you care about here. Here, you have modified some files. You may even have run git add to stage them for the next commit. But all git add README does is to copy the file from the work-tree into the index. This means that the index and the work-tree versions of README match each other. They both don't match the HEAD (current) commit.

Let's say that HEAD is the tip of develop and you should have been on feature instead. You'd like to git checkout feature. But what will happen to README? You made a change. You maybe even staged it, copying it into the index.

Well, the fact is, Git is lazy. If you now run git checkout feature to move the HEAD commit to the tip of branch feature, Git will have to do some work. That might include removing the current README file and replacing it with the one from the tip of feature.

But maybe—just maybe—the tip commit of feature has the same README file as the tip commit of develop. If so, Git can be lazy! Git can simply not bother ripping out the existing README from the index/staging-area and/or work-tree. It can leave the modified README in whichever places it is now. And that's precisely what Git does.

If Git can't be lazy about the files, it checks. If you the files you have in your index and/or work-tree don't match the ones in the HEAD commit you are trying to move off of, then git checkout feature fails. (See the git read-tree documentation and its list of—yikes!—21 possible cases for a "two tree merge", to see exactly which ones succeed and which ones fail. Mostly, though, you don't have to care about all of this: if it succeeds, you're good, if it fails, you have to commit or stash.)

This is why git checkout -b newbranch always succeeds: it creates the new branch with that branch-name pointing to the current (HEAD) commit. Then it switches from the HEAD commit to ... well, itself. There's nothing to switch. The laziness step applies to every file: no file must be changed, so no file is changed, so git checkout -b just works.

And, this is why git checkout -b otherbranch sometimes succeeds: it needs to switch from the HEAD commit to the tip of otherbranch. If that commit has some files that are different, Git will have to rip out the ones that are in the index/staging-area and work-tree right now, and replace them with the versions from the tip of otherbranch. This is only allowed if the versions that are in the index/staging-area and work-tree right now match the versions in the HEAD commit.

like image 192
torek Avatar answered Oct 23 '22 13:10

torek


Just checking out where you want to move them should be enough. Git performs a check to see if the staged changes can be applied on the point you are checking out without conflicts. Another easy technique you can use is to stash the changes, then checkout the branch and then stash pop.

like image 7
eftshift0 Avatar answered Oct 23 '22 13:10

eftshift0