23

I used git-svn to create a git mirror of an SVN repository. The structure inside the SVN was a little off-standard, so git created a branch that has no common commit with the master branch.

      A---B---C topic

D---E---F---G master

I know that commit A is based off commit E and I'm pretty positive that I've fixed the issues causing git not to recognize that fact (using filter-branch). What I want to do is re-attach topic to the master branch, setting E as the parent of A:

      A---B---C topic
     /
D---E---F---G master

git-rebase doesn't seem to work for me because the diff for commit A lists the creation of a whole lot of files that already exist in master, resulting in a huge number of conflicts.
From my understanding of git just setting E as the parent of A should be enough to solve all problems.
Is this possible? If it is, how can I do it?

Marvin Killing
  • 2,495
  • 2
  • 19
  • 17
  • Any chance to re-init the git mirror of the svn pointing "branches" to the right directory? Or fixing the svn structure first? – stefanw Nov 12 '10 at 12:30
  • actually the repo did use the standard trunk/tags/branches layout. However the branch that I was trying to fix was created by copying only a subpath of trunk - guess that was a little bit too much for git-svn to handle. – Marvin Killing Nov 25 '10 at 16:05
  • rebase has a `root` option. Use that with the `onto` and `preserve-merges` if you need. – Adam Dymitruk Oct 23 '12 at 18:26
  • See also: http://stackoverflow.com/questions/3810348/setting-git-parent-pointer-to-a-different-parent – Alexander Bird Mar 11 '14 at 15:21

3 Answers3

29

Have a look at grafts (the graft file can be found in .git/info/grafts). The format is pretty simple:

<commit sha1> <parent1 sha1> <parent2 sha1> … <parentN sha1>

This makes git believe that a commit has different parents than it actually has. Use filter-branch to make grafts permanent (so the grafts file can be removed):

git filter-branch --tag-name-filter cat -- --all

Note that this rewrites history of the repository, so should not be used on shared repos!


If you only want to rewrite the history of the commits that are being grafted onto the master branch, for example, use this command:

git filter-branch --tag-name-filter cat -- master..
Flimm
  • 136,138
  • 45
  • 251
  • 267
knittl
  • 246,190
  • 53
  • 318
  • 364
  • don't use grafts or filter branch if you don't have to. – Adam Dymitruk Nov 24 '10 at 07:02
  • 3
    Grafts are incredibly useful is you need them, but like some other features in git they should be used before you share the repo (or you'll need to get everyone else to re-clone). As the answer says, simply create the grafts file (hash-of-A, space, hash-of-E, newline) and use "git filter-branch --tag-name-filter cat -- --all" to rewrite history without changing other commit data – JodaStephen Jun 24 '11 at 11:47
  • @JodaStephen you should add that to the answer. Do you know if it's ok to do that if the repository is published, but the grafts you're messing with are only touching new, unpublished commits? – naught101 Jun 22 '12 at 14:44
  • TIL git grafts. Thanks! – ulidtko Jan 13 '14 at 10:45
  • For some reason a recent merge commit was missing a parent in my repo. This saved the day. Thanks! – CrouZ Mar 12 '14 at 17:17
  • In my case I wanted to turn this into a straight line: A-B-C-D-E-F-G. I was able to do this using the grafts file (which git now complains about being deprecated) and then using your first filter-branch example to make the changes permanent. – kwill Dec 14 '18 at 16:18
8

Based on your diagrams (although I'm concerned about what you mean by "I'm pretty positive that I've fixed the issues causing git not to recognize that fact (using filter-branch)."), you should be able to do something like the following.

# checkout A
git checkout A

# Reset the branch pointer to E so that E is the parent of the next commit
# --soft ensures that the index stays the same
git reset --soft E

# Remake the commit with the E as the parent, re-using the old commit metadata
git commit -C HEAD@{1}

# Rebase the topic branch onto the modified A commit (current HEAD)
git rebase --onto HEAD A topic
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
7

All you need is this:

git rebase --root --onto master^^ topic^^ topic

the root option lets you include A.

UPDATE:

Add the --preserve-merges option if you want to retain the branching and merging of the part that you are rebasing.

Adam Dymitruk
  • 124,556
  • 26
  • 146
  • 141
  • 1
    This works great if you have a linear history, if it has merge commits, you have to use `--preserve-merges`, and you have to re-resolve merge conflicts manually. – Flimm Jan 07 '14 at 11:37
  • Yes. I'll update the answer. Another time that I answered the same question, I included that option. – Adam Dymitruk Jan 07 '14 at 20:04
  • 1
    @AdamDymitruk: a question regarding your answer: are you using Windows? The "^^" seems odd to me. Is that the parent of the parent, or is the first "^" a DOS escape for the second "^"? (PS. if its the latter, then `git rebase --root --onto "master^" "topic^" topic` might be better syntax.) On the other hand perhaps it is indeed intended to be "grandparent" because of the use of `--root`. Thanks. – Rhubbarb Apr 20 '15 at 15:35
  • in bash it does 1st parent of 1st parent – Adam Dymitruk May 12 '15 at 22:34