Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GIT: commit changes to old/safe branch while in new/dirty/dev branch without checking out or losing unstaged data

I created a new Branch before I started dev on something experimental. I usually forget that (which isn't a problem), but now I did it beforehand.
Since then I have updated 3 files.

  • In 2 are only experimental changes that I DON'T want committed to the safe branch.
  • In 1 are only safe (minor) changes that I definitely DO want committed to the safe branch. I'm fine with these last changes to be committed to the new branch as well (but rather not).

Is it possible - I'm sure it is - to (quickly) commit a few unstaged, uncommitted changes from my (dirty) working dir to an old, safe branch?

The only thing I can think of is switching branches (without checkout), commit the changes in 1 file and switch back, but I don't know what will have happened to the changes when switched back to the dirty branch (are they still there or did they 'vanish' due to the commit?)...

I'm sure GIT has something beautiful for this, but GIT has so much, I can't find the exact same thing.
(I've been using this 'manual' for help but I'm not sure the same exact thing is in there. If it is (and you're willing to scan the thing), please let me know, so I know next time to look harder.)

Thanks! For now I'll just keep a piece of paper handy with changes 'to commit to safe branch later'.

like image 823
Rudie Avatar asked Sep 19 '10 13:09

Rudie


2 Answers

There is no way to add a commit to an alternate branch with git commit. There are ways of using the low level “plumbing” commands to do exactly what you described, but the interface formed by those commands is not designed for interactive use1. There are certainly ways to do what you want; depending on the details of your changes and the contents of the branches involved it can be quite simple.

The Easy Case: Just Use git checkout

When switching branches, git checkout will preserve uncommitted modifications or refuse to switch (unless you use --force, --merge, or --conflict). So, as long as your uncommitted changes only touch files that are the same in both HEAD (the current branch) and your destination branch, git checkout will leave those changes in the index and/or the working tree while switching branches. If your uncommitted changes satisfy this condition then you can do this:

git checkout safe-branch
git add -- files-with-safe-changes
git commit
git checkout -

You can also use git add --patch to stage and commit only some of the changes in the files.

After this, your “safe changes” will be a part of ‘safe‑branch’; switching back to your original branch will “leave them behind” (remember, git checkout only preserves uncommitted changes when switching branches).

If your other changes depend on the “safe changes” you may need to merge ‘safe‑branch’ into your working branch (or, depending on your workflow, rebase your working branch onto the new tip of ‘safe‑branch’). To do this you will have to stash your uncommitted changes (since both merge and rebase will refuse to function if there are uncommitted changes).

git stash save
git merge safe-branch
git stash pop --index

If your other changes do not depend on the “safe changes”, then you should probably not bother with a merge or rebase. Eventually you will merge these branches together (e.g. by merging them both into a ‘qa’ branch for pre-release testing), but there is no reason to merge them prematurely.

Still Easy, but a Bit Risky: git checkout -m

If the first git checkout complains “You have local changes to some‑file; not switching branches.”, it means you have uncomitted changes to some-file and that the file is different in the tips of ‘safe‑branch’ and your current branch; you will need a different approach.

If you are confident that the changes would apply cleanly to the version of some‑file that is in ‘safe‑branch’, then you can use the -m/--merge option to tell git checkout to try to adapt the changes so that they apply to the files in ‘safe‑branch’. If the merge can not be done cleanly, then you will end up with a merge conflict and it may be difficult to recover your original changes (this is why I call it “risky”).

Safe: git stash + git checkout -m

Since you really only want to move a subset of the changes back to ‘safe‑branch’, it may be better to focus on just those changes. One method is to use git stash to temporarily save your current changes so you do not have to drag all of them back to the ‘safe‑branch’ (and later drag some/most of them back to your working branch).

git stash save
git checkout stash -- files-with-save-changes
git checkout -m safe-branch
git commit
git checkout -
git stash pop --index

Other variations are possible. You can use git checkout -p stash -- files to pick out only some of the changes in those files. If there are no staged changes in the index, then you could first stage the “safe changes”, git add -- files (again, optionally with -p) , use git stash save --keep-index, switch branches (with merge), and then commit (i.e. replace the git checkout stash -- files with pre-staged “safe changes” and git stash --keep-index).

In this situation I consider git checkout -m to be safe because we used git stash to preserve a copy of the current changes; if the three-way merge attempt results in a hopeless mess, then you can easily abandon the idea of putting the “safe changes” on ‘safe‑branch’ and get back to work: switch back to your original branch and pop the stash (git checkout -f - && git stash pop).

Again, if your other changes depend on the “safe changes” then you will need to merge or rebase. You might as well do this before popping the stash (since you need a clean index and working tree to do the merge/rebase).

If you are not going to merge your working branch with (or rebase it onto) the ‘safe‑branch’ right away, then you will probably want to undo the “safe changes” after you pop the stash (the “safe changes” were saved in the original stash and you probably do not want to end up with commits that make the same changes from scratch in two different branches2). Once you have popped the stash, use git checkout -- files-with-safe-changes to revert those files back to versions at the tip of the working branch.


1 The “plumbing” interface is designed for use in scripts. It would be cumbersome to use them directly on the command line. Early versions of git commit (and most other Git commands) were shell scripts that were based on this interface. They could still be written as shell scripts today, but the C versions are generally much faster. The steps required to commit to an alternate branch are:
setup an alternate index based on the tree at the tip of the “safe branch”,
update the index with the “safe” changes (what if the changes can not be applied cleanly? it is nice to have a working tree to let the user resolve the conflicts),
write the index out as a tree object,
make a new commit object that points to the new tree and has the current tip of “safe branch” as its parent,
update the “safe branch” ref to point to the new commit.

2 There is nothing technically wrong with committing your “safe changes” in two branches, but it is usually a good idea to make sure each change originates from only a single place.

like image 52
Chris Johnsen Avatar answered Nov 13 '22 17:11

Chris Johnsen


So your situation is

x--x--x (safe)
       \
        <a,b,c> (3 private evolutions in exp branch)

And you want to go to

x--x--x--a (a committed only in safe banch)
       \
        b,c (b and c commited in exp branch)

You could:

git add a
git commit -m        # in exp branch, gasp!
git stash save       # save the rest of the exp work in progress
git checkout master
git merge exp        # fast-forward merge

x--x--x--a (safe,exp)
          \
           [b,c] (stashed)

git branch -f exp HEAD~1  # reset branch exp to before 'a'
git checkout exp
git stash pop
git add -A
git commit -m "..."

x--x--x--a (a committed only in safe banch)
      \
        b,c (b and c commited in exp branch)
like image 29
VonC Avatar answered Nov 13 '22 15:11

VonC