How to create copy of a snapshot (commit) in a bra

2020-07-26 19:02发布

问题:

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.

回答1:

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.



回答2:

In destination branch: git cherry-pick <commit id>



回答3:

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.



标签: git