2

I would like to take a commit and copy it somewhere else in my repository.

Current situation:

A--B--C--D (branch1)
    \
     E--F (branch2)

Desired situation:

A--B--C--D--F'(branch1)
    \
     E--F (branch2)

It is important that F remains in the repository. I don't want to rewrite history because F might be public.

The state of the repository (source code) represented F and F' should be exactly the same. Yes, what was introduced C and D might be lost.

I do not want to replay E and F on top of D. I have looked into cherry-pick and rebase, but it seems that they delete the original commits or simply replay the changes on top of the target.

jub0bs
  • 60,866
  • 25
  • 183
  • 186
Marc-François
  • 3,900
  • 3
  • 28
  • 47

3 Answers3

5

This point is a key item, which I missed when I first commented:

The state of the repository (source code) represented [by] F and F' should be exactly the same. Yes, what was introduced C and D might be lost.

In this case, starting from git checkout branch2, you can use Stephen Newell's answer, but there's one that is a little simpler, and one that is even simpler still:

$ git rm -r .
$ git checkout <hash-of-F> -- .
$ git commit

which works by deleting everything from the index (and removing it from the work-tree) to make sure that no stray files from earlier commits remain, then extracting everything from commit F into the index and work-tree, ready for committing.

You can shorten this one more step using:

$ git read-tree -m -u <hash-of-F>
$ git commit

since this git read-tree does the equivalent of the remove-and-replace.

You can use the name branch1 instead of the hash of commit F until branch1 itself moves and no longer points to commit F.

torek
  • 448,244
  • 59
  • 642
  • 775
2

Assuming you have D checked out.

$ git rm -r .
$ git archive F | tar xf -
$ git add .
$ git commit -m "Duplicate tree of commit F"

The first step will delete all current files, then you'll take an archive (git archive defaults to tar, h/t to Marc-François) and immediately drop it into your working copy. Add everything and commit.

This worked in a very small test on my system, so test thoroughly.

Stephen Newell
  • 7,330
  • 1
  • 24
  • 28
2

One solution

I'm a bit rusty, but I believe that the following would do the trick:

git checkout branch2
git update-ref --no-deref HEAD branch1 # make HEAD to point at the tip of branch1, but keep the working tree from branch2
git checkout branch1 # reattach HEAD to branch1
git commit -m "Some commit message for F'"

In retrospect, I think torek's approach is simpler and safer. In particular, update-ref can easily be misused.

Minimum working example

# setting things up (output omitted)
mkdir mwe && cd mwe
git init
echo A > README
git add README
git commit -m A
git branch -m branch1
echo B >> README 
git commit -a -m B
echo C >> README 
git commit -a -m C
echo D >> README 
git commit -a -m D
git checkout -b branch2 branch1~2
echo E >> README 
git commit -a -m E
echo F >> README 
git commit -a -m F

# now...
$ git update-ref --no-deref HEAD branch1
$ git checkout branch1
M   README
Switched to branch 'branch1'
$ git commit -a -m "F'"
[branch1 36b29b2] F'
 1 file changed, 2 insertions(+), 2 deletions(-)
$ git show
commit 36b29b21cc014ee1ec73ac5dd80a4ad834ee282a (HEAD -> branch1)
Author: xxxxxxxxxxxx
Date:   xxxxxxxxxxxxx

    F'

diff --git a/README b/README
index 8422d40..04bc76d 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
 A
 B
-C
-D
+E
+F
jub0bs
  • 60,866
  • 25
  • 183
  • 186