When I started to use Git, one of the first things I learned about it is that Git does not store information as list of file-based changes (patches), but as a stream of snapshots. A commit is a snapshot of all the repository.
Consider we have two branches in a repository Branch_A and Branch_B. No mater the relation between them, it may be very simple of very complicated (merge with others branches...). Just represent them like this:
A <--- ... -- I <----- J <----- K Branch_A
M <------ N <----- O <----- P Branch_B
For some reasons, I want that the next commit of Branch_A be exactly the state (the snapshot) of the commit P in Branch_B.
Is there a git command to do this ?
I can't use a "normal" merge, because I don't want to resolv conflicts between P and K.
I can't use a recursive merge with the option "theirs" because it will keep all the files in K that do not conflicts with P, even if they do not exist in P.
I can do it like this :
cd /path/to/myrepo;
git checkout Branch_B
cp -R * /path/to/save_branch_b_state
git checkout Branch_A
rm -rf *
cp -R /path/to/save_branch_b_state . (In fact, I copy everything but the .git directory)
git add *
git commit
But I imagine there is a git command to do this.
There is no one single git command to do it but you can do it with just a few commands.
The first question, though, is: do you want this to at least resemble a merge (in which case, it will really be a merge)? That is, what do you want the commit graph to look like—should it show some kind of linkage from branch_B
(where commit P
lives), a la:
A <--- ... -- I <----- J <----- K <--- * Branch_A
/
M <------ N <----- O <----- P Branch_B
where *
is your new commit? Or do you want it to be totally independent?
The low-level command git commit-tree
will make a new commit with arbitrary parent-age (you supply the parent IDs with -p
, a message, and an existing "tree" object). It prints the resulting commit-object ID. So to do this with the low level command:
tree=$(git rev-parse B^{tree}) # or similar to find the tree ID
commit=$(git commit-tree -p ... -m message $tree) # or use -F, or stdin
then set Branch_A
to point to $commit
(using git branch
or git update-ref
). This can be turned into a one line shell thing and hence done as a git alias, but it's a bit tricky and probably deserves to be a shell script. Choose just one ID as parent to make your new commit a regular (non-merge) commit, or multiple IDs to make it a merge; if you do make it a merge, choose the parent ID ordering based on how you want --first-parent
to work in the future.
Or, you can do it with somewhat higher level git commands. For instance, if you don't want it to be (and resemble) a merge, you can simply start on Branch_A
, erase everything (remove the entire tree from the index and work-tree), repopulate everything from commit P
(create new index contents and work-tree based on commit P
), and then commit:
$ git checkout Branch_A
$ git rm -rf . # cd to top dir first if needed
$ git checkout Branch_B -- . # index and work tree = commit P
$ git commit
Or, do the above as a real merge: see the answer to this question.
In destination branch:
git cherry-pick <commit id>
In destination branch working directory:
$ rm -rf *
$ git add .
$ git cherry-pick -n -X theirs <COMMIT_ID>
$ git commit --no-edit
The rm
command may need to be additionally run for dot (hidden) files (.*
except .git
). Because in a typical shell the asterisk (*
) symbol in the glob pattern does not expand to dot files.