Question

Suppose I have a file that I had accidentally forgotten to track in git, such as Foo.sh. After developing for a while, I realize that I need this file to be tracked by git.

However, since the file has up until now been untracked, if I add it to a particular branch Branch1 and make a commit, then it will disappear when I check out another branch.

My current solution to this problem is

  1. Commit Foo.sh into Branch1.
  2. Check out each branch other than Branch1, and do a git checkout Branch1 -- Foo.sh.
  3. Perform git commit -m "Added Foo.sh" on each branch other than Branch1.

Is there a more direct way to solve this problem, assuming that I don't mind if the commit message is the same on every branch?

Was it helpful?

Solution

Vlad Nikitin's method will work, although you can simplify it by identifying the commit to cherry-pick by the branch name:

$ git checkout branch1
... add and commit file ...
... at this point, "git show" will show that there's just the one change,
    which is to add Foo.sh ...
$ for b in branch2 branch3 branch4; do
> git checkout $b && git cherry-pick branch1
> done

Since this is a new file not present on any of the listed branches, there should be no conflicts here.


If (this is a very big if) you have not published any of the new commits, though, you might want to "rewrite your history" a bit, making it look like you put Foo.sh in earlier, before the branches diverged:

A---B---C---D   <-- branch1
     |\
     |  E       <-- branch2
      \
        F---G   <-- branch3

If you could just modify commit B to contain the new Foo.sh, then all commits "after" B will also contain Foo.sh.

You can't actually modify B, but you can make a new commit, B', that's "just like B except also adding Foo.sh". Then you can copy C through G to new commits C' through G', basing them on B'. To do this, I'd check out commit B by hash-ID to get a "detached HEAD", add the file, and do a git commit --amend:

$ git checkout 1234567
... git warns about detached HEAD ...
... create Foo.sh, make sure it's right ...
$ git add Foo.sh
$ git commit --amend
... editor ...
[detached HEAD 278c036] ...
 N files changed, NN insertions(+)
 create mode 100755 Foo.sh
 create mode 100644 blah.txt
 ...

Now for convenience and protection I'd like a temporary name for this new B' commit, so:

$ git tag temp_new_base

and I've just realized that I'd like to not type out the hash for B either, so:

$ git tag temp_old_base 1234567

(I should have done this before git commit --amend, but I forgot, so I do it here instead by giving the hash-ID again.)

Here's what I have now as a picture:

     ____----temp_old_base
    .
A---B---C---D   <-- branch1
|    |\
|    |  E       <-- branch2
 \    \
  \     F---G   <-- branch3
   \
    B'
     .____
          ----temp_new_base

Now all1 I need to do is rebase each "after B" chain in each of the three branches, onto the new B' commit. This is where the tags I just added come in:

$ git checkout branch1
$ git rebase --onto temp_new_base temp_old_base..

This copies C and D to make commits C' and D', appending them to commit B'. The two tricks here are using --onto temp_new_base, telling rebase to grow the new commit-chain starting at commit B'; and using temp_old_base.., which means temp_old_base..HEAD, which means "everything on HEAD that's not also on temp_old_base", which means commits C and D in this case.

When that rebase finishes, I just need to repeat for the remaining two branches:

$ git checkout branch2
$ git rebase --onto temp_new_base temp_old_base..

This time, HEAD is branch2 which identifies commit E, so temp_old_base.. means to rebase just commit E.

Last, I have to repeat this for branch3, after which I can git tag -d the two temporary tags.

(In fact, since all three original branches grow from the same point—commit B, which I labeled temp_old_base—and the rebase command is the same for all three, I can just do the same for b in branch1 branch2 branch3; do ... loop—with the rebase command instead, this time—as for cherry-picking Foo.sh onto the end of each branch. This all depends on the fact that I know that all the branches "come off of B".)


1"Hah! All! All, he says!" Seriously though, this is actually the easy part. The hard part is identifying the point at which to "slide in" Foo.sh, and to make sure none of the commits from there on are published. If they are published, don't do this. (Well, don't do it unless you really know what you are doing, and all of the above is obvious to you, because you'll have to help everyone who took the published commits, recover from this.)

OTHER TIPS

You need chery-pick command

$ git add file
$ git commit -m 'file added'

make git log to see the hash of the commit

$ git checkout another_branch
$ git cherry-pick 'hash'
$ git checkout another_branch2
$ git cherry-pick 'hash'
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top