简体   繁体   中英

How to tell Git that two commits are actually the same in a disjoint history?

I have a Git repo that is clone from another Git repo that is a clone from a giant SVN repo. We can update the other Git repo from SVN and commit from it to SVN, but with another user.

I want to publish my local commits to the SVN repo directly, with my own user.

This is what I tried: I manually created the git-svn branch, adding this to .git/config

[branch "master"]
[svn-remote "svn"]
        url = https://url/to/svn/trunk/repo_name
        fetch = :refs/remotes/git-svn

Then git svn fetch , downloaded 32k commits.

But I ended up with two disjoint histories:

  1. One from Git:
    1. Starting at commit 674c35a that represents the state of the SVN repo at the point of the original clone.
    2. The branch master is always synchronized with SVN (through the other Git repo).
    3. The branch dev have our current developments, that should be merged to master (usually squashed), and then committed back to the SVN repo.
  2. One for the SVN repo:
    1. Starts with the proper initial commit.
    2. It has 30k+ commits.
    3. The commit that Git originally cloned from SVN [ fb1b9c7 ] (but only with diff from the previous commits, not the entire repo as a start commit).
    4. Then the same commits (100+) that should be common to git/origin/master .

How can I tell Git that the starting commit ( 674c35a ) is actually the same as a SVN commit ( fb1b9c7 )? So Git can somehow understand that the commits in master , after the clone, are actually the same as git-svn ?

I have tried to rebase with --onto , as explained here , but that wasn't really what I wanted (the commits are not in top of the branch).

I believe I have a method which should work for this in my blog post Grafting History with Git which for the purposes of complying with site rules I'll summarize here :

Current state :

  • Two git repos with diverging histories but one origin

Target state :

  • One git repo with two branches representing those diverging histories

  • Convert both histories into Git branches (I think you've already got this)
  • Find the point of divergence by comparing logs and seeing when the tree hash changes
    • Get a list of the tree-ids and commit-ids on each branch using git log --reverse --format="%T %H" and save this to a file
    • Compare the lists to see where the tree hash diverges (I use a diff program that can do columns, you could just use awk and diff though)
  • Check out the point of divergence on the target repo branch and make a new branch
  • Fetch all the objects from source into target
  • For each revision in the original source, create a new revision on target that
    • Is a checkout of the tree of the foreign revision using git read-tree -um $TREEID
    • Uses the original commit message using git commit --reuse-message $ORIGINAL_COMMIT

Or ... cat your list of tree-id + commit-id into this script.

#!/bin/bash
# graft script
while read TREE COMMIT
do
 git read-tree -um $TREE
 git commit --reuse-message $COMMIT
done

Your history probably looks something like this:

*--*--*--fb1b9c7--*--*--* [git-svn] (version of master from SVN)

         674c35a--*--*--* [master] (version of master from Git)
                         \
                          *--*--*--* [dev]

It looks like you have two issues: your Git history doesn't contain the 30k+ commits from the full SVN history, and dev is based off the (incomplete) Git history instead of the SVN version.

Since fb1b9c7 == 674c35a , you should be able to do:

git rebase --onto git-svn master dev

My experiments indicate that git rebase doesn't care that the target branch ( git-svn ) doesn't have the same history as the base branch ( master ). It also shouldn't matter that SVN only stores diffs, because git-svn will have converted those diffs into real Git commits during the fetch.

Since the histories contain the same contents, you should even be able to do:

git checkout dev
git rebase git-svn

After that, you can delete master and let that tree get garbage-collected later. (If you still need a separate master branch, then do git checkout -b master git-svn after deleting the original master .)

When you said:

(the commits are not in top of the branch)

I assume that you are referring to commits fb1b9c7 and 674c35a . However, it doesn't matter that those commits are buried in the history, because we will choose one ( fb1b9c7 ) to use, and let the other be garbage-collected, as mentioned above.

If you want to replace one commit by another commit, just use git replace , like git replace 674c35a fb1b9c7 .
If you then want to make this permanent, use git rebase .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM