67

I had one Git repository (A) which contains the development of a project until a certain point. Then I lost the USB stick this repo A was on. Luckily I had a backup of the latest commit, so I could create a new repository (B) later where I imported the latest project's state and continue development. Now I recovered that lost USB stick, so I have two Git repositories.

I think I just have to rebase repo B onto repo A somehow, but I have no idea how to do that, maybe using fetch/pull and rebase?

Elnur Abdurrakhimov
  • 44,533
  • 10
  • 148
  • 133
kroimon
  • 2,062
  • 3
  • 19
  • 23

4 Answers4

70

If A and B are not the same repo (you created B by using the latest working copy you had), you have to use a graft to pretend that they have common history.

Let’s assume you’ve added A as a remote for B as per VonC’s answer, and the repo looks like this1:

~/B$ git tnylog 
* 6506232 (HEAD, master) Latest work on B
* 799d6ae Imported backup from USB stick
~/B$ git tnylog A/master
* 33b5b16 (A/master) Head of A
* 6092517 Initial commit

Create a graft telling the root of B that its parent is the head of A:

echo '799d6aeb41095a8469d0a12167de8b45db02459c 33b5b16dde3af6f5592c2ca6a1a51d2e97357060' \
 >> .git/info/grafts

Now the two histories above will appear as one when you request the history for B. Making the graft permanent is a simple git filter-branch with no arguments. After the filter-branch, though, you aren’t on any branch, so you should git branch -D master; git checkout -b master.


1 git tnylog = git log --oneline --graph --decorate

Community
  • 1
  • 1
Josh Lee
  • 171,072
  • 38
  • 269
  • 275
  • 2
    +1 for taking care of the "other scenario" were A and B differ completely in commit history. – VonC Mar 11 '10 at 21:25
  • 3
    This was a brilliant help; did absolutely what I wanted with a minimum of fuss. Thank you! – Aidan Steele Feb 18 '11 at 02:41
  • 1
    I just used the subtree-merge approach to stitch several repositories into one. I successively failed at the attempt to rebase them one over the other. Using graft points, I've now been able to stitch them onto a single development timeline, thanks! – Filip Dupanović Apr 19 '11 at 14:55
  • 16
    +1 By the way, I edited your answer to reflect what your `git-tnylog` does (I was puzzled and took me a while until I found it was an alias of yours: ['Pimped tnylog a bit'](https://github.com/jleedev/dotfiles/commit/dc70a0bbef6ab6c7afabd49d6b917adca66e7bf5)) – Alberto May 30 '12 at 16:59
  • How would you push this to a remote? Say I host my B on bitbucket and want to push this. Simply (--force) pushing a new commit to bitbucket doesn't (appear) to add the history of A to the the remote bitbucket. – Pim Jager Oct 13 '15 at 11:50
  • I already tried the command `git filter-branch 33b5b16dde3af6f5592c2ca6a1a51d2e97357060..HEAD` from [link](https://www.kernel.org/pub/software/scm/git/docs/git-filter-branch.html) but then I can't push anymore since I get the error that the remote has unresolved deltas (`remote: fatal: pack has 50 unresolved deltas`). – Pim Jager Oct 13 '15 at 12:27
  • Update: after an hour of googling and using the rebasing option from this [answer](http://stackoverflow.com/questions/13450131/how-to-properly-push-one-git-repository-over-another-on-codebasehq), I managed to create something which could be pushed to a new bare repo. – Pim Jager Oct 13 '15 at 13:45
  • 1
    @PimJager (or anyone else with the same query), see: [How to push a “git replace --graft”](https://stackoverflow.com/questions/42489421/how-to-push-a-git-replace-graft). –  Apr 12 '18 at 01:53
  • 1
    Related: [How do git grafts and replace differ? (Are grafts now deprecated?)](https://stackoverflow.com/questions/6800692/how-do-git-grafts-and-replace-differ-are-grafts-now-deprecated). –  Apr 12 '18 at 01:55
  • I'm getting an error "too many pending edits on SO" so I am going to suggest two changes here. 1. remove mention of author's personal alias "tnylog" and use the standard git command line from the footnote. 2. "Make the graft permanent by running `git filter-branch` with no arguments." As written, the sentence is both confusing and a bit judgmental of those who are unfamiliar with 'filter-branch'. – Ari Lacenski Dec 02 '22 at 19:10
45

If A and B are the same repo (the first SHA1 are common), you can:

  • declare A as a remote for B: git remote add A /path/to/A
  • git fetch A to update all remote A branches on the B repo
  • git checkout dev (on B, where you are developing)
  • git rebase A/devBranch to replay B (i.e. what you develop or re-develop from your backup) on top of A/devBranch (the development you lost). A bit like this SO question.

The last step allows you to sync your dev with the one you lost.
But actually, once you have fetch from A, you are done: B now contains the "all" history (the one you lost and your current work)

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Thanks, seems to work, just have to resolve some conflicts during the merge/rebase now :) – kroimon Mar 11 '10 at 21:13
  • @kroimon: conflicts were inevitable, I suppose, since you were re-developing in B some part of the code committed in A. – VonC Mar 11 '10 at 21:24
  • I ended up in a detached HEAD, and had to reference this: https://stackoverflow.com/a/25670296/1807668 – Alison S Jun 13 '17 at 23:01
  • @AlisonS I agree: the rebase command was incorrect. If you have checked out `dev`, all you need is `git rebase A/devBranch` in order to replay `dev` on top of `A/devBranch`. I have edited the answer. – VonC Jun 14 '17 at 05:56
  • This worked well for me without A and B having a common first SHA1. I did an interactive rebase and dropped some initial commits. – timgeb Mar 01 '21 at 12:30
  • 1
    This is the real answer to the question. – thomasgainant Feb 02 '23 at 08:39
5

2021 Update

git filter-branch has been deprecated since Git 2.24 (Q4 2019). Instead, for the scenario where A and B are not the same repo, you can use git replace --graft <commit> <parent>.

See:

mefryar
  • 199
  • 4
  • 7
2

First of all, start by making a working clone of repo A.

Then simply pull into it from B and merge. You might prefer to create a new branch, pull onto it, then merge the two branches. You might also need a forcing flag; I've done things like this in Mercurial (grafting two apparently-unrelated repositories together) and it needs "-f".

crazyscot
  • 11,819
  • 2
  • 39
  • 40