git merging only recent changes from one repository to another which is an unrelated snapshot (not a clone)

76 Views Asked by At

I have two git repositories: internal and external. The internal repo has our full history. The external repo has one commit of a snapshot of the internal repository as of about 3 weeks ago. Graphically it looks something like this:

A01
 |
A02
 |
A03
 |
A04  --> snapshot to B01
 |
A05
 |
A06 - A07
 |     |
A08    |
 |    /
A09  /
 |  /
 | /
 +
 |
A10
 |
A11
 |
A12

My question is how to best merge commits A05 through A12 to a local copy repository B? (I'll squash them internally before pushing them to our public-facing repo)

A and B are completely unrelated repositories (B was not created as a clone of A; we took a checkout copy of repo A as of commit A04 and checked them into a new repo B)


The twist in all of this (and if it weren't true, I would just continue to use snapshots) is that I have file renaming. Repo A contains refactoring commits where files were renamed and moved. If I just take a snapshot of A12 and commit to B01, then I somehow have to tell Git how to relate files before and after the move (like hg rename -A in Mercurial); this information is already in Repo A's history and I do not want to have to recreate it.

3

There are 3 best solutions below

2
On

Once you have started a separate repo by creating a snapshot to check in (e.g. for releases only or something), you should plan on doing this process whenever you want to update that separate repo, in order to properly track file removal/rename/etc. situations:

  1. Create a snapshot in your real repo (git archive is good for this)
  2. In your "release" repo, remove everything from the working copy (leaving behind just .git/ and possibly .gitignore, .gitattributes, etc.) - but, note, do not commit these changes yet:
$ git clean -fdx
$ git rm -r *
  1. Unpack your snapshot
  2. Commit the new version
$ git add -A .
$ git commit

I have a number of packages that I do exactly this on (e.g. gcc) to keep a release history - the resulting repository is considerably smaller than keeping all the release archives around.

Alternatively, you could possibly figure out some convoluted way using git bundle, git merge, git cherry-pick, git rebase and maybe some git format-patch / git am / git apply trickery to do something similar, but many of those rely on having some approximation of common history between the two repositories, which you don't have. The complexity of such a procedure would likely outweigh any possible benefit you might be able to imagine.

0
On

Assuming you have made no changes to B since taking the snapshot, what you really want to do is make B look like a mirror copy of A. Since you mention that they are "totally unrelated repositories," I assume you have two directories on your local machine (one for each repo). Since you plan on squashing these changes into one commit, I would suggest you simply copy the contents of your local directory that points to A (minus the .git directory) to the directory that points to B. Do a git add --all ., commit, then push.

0
On

Other people are suggesting manually copying a checked out version to repository B. What you really want to do is squash commits A01, A02, and A03 onto B01. This ensures that you don't make careless omissions when you commit to repo B.

Now, this would be simple if A and B were in the same repo, but they're not. Fortunately, you can achieve something very similar by just adding B as a remote in a copy of A:

~/A $ git remote add external ssh://path/to/B
~/A $ git fetch external

B has been set up as the remote external. It has a completely separate DAG, but they are in the same repository, so now you can squash the commits. Unfortunately, I think you'll have to know which commit you created B01 from. There's probably some arcane Git command that could tell you, but it's easy enough to just figure it out yourself. For this example, the commit is A04. (Obviously it will be a shorthash in reality.) Now squash:

~/A $ git rebase --interactive A04 --onto external/master

Here, external/master refers to B01 in your DAG.

In the editor that appears when you run the above command, change the action on every commit but the first to squash. (In Vim, this is easy to do with block mode.) It should look like this:

pick A03
squash A02
squash A01

Exit the editor and allow Git to apply the changes. Now you can push your changes to external to the remote repository.