2

I have replaced the parent of my initial commit with the last commit of another repository. I'd like to push these changes.

What I did, gathering from 1 and 2:

git clone B
cd B
git remote add A URL
git fetch A
git replace --graft b_root_sha a_head_sha
git push origin 'refs/replace/*:refs/replace/*'

This worked fine and the refs were pushed. But I must have understood something wrong. I see no evidence of the replacement in the remote repo. My first commit is still b_root and it has no parents. I'm viewing it on GitHub, if it makes any difference.

Ideally I would have wanted to:

  • Push the commits in A to really precede B in master
  • Have the commit hashes preserved because of references in closed issues and pull requests

Because then the whole history would be in the same repository. Is this combination possible? If not, can the whole history be pushed at the cost of references being destroyed, or at least the parent be shown in the repository? Or at the very least have the code in the repository somewhere just in store for people to fetch the references and see for themselves what the correct parent should be.

Felix
  • 2,548
  • 19
  • 48
  • If you `git clone` the GitHub repository and then (in the new clone) do a `git fetch origin '+refs/replace/*:refs/replace/*'`, does the new clone have follow the replacement? If so, it's just GitHub's display and other mechanisms deliberately *not* following replacements, for whatever reason. – torek Dec 29 '19 at 10:59
  • @torek Indeed it has them. And `git log` even shows the history! So does it exist in my repository now independent of others? Or does it reference a separate remote? – Felix Dec 29 '19 at 11:04
  • OK, so this just means that GitHub *doesn't* follow replacements. You can have your own Git not follow replacements too (`git --no-replace-objects log`), for instance. The fact that GitHub is (apparently) doing this all the time may make it impossible to achieve what you want (this depends on what you *want* of course). – torek Dec 29 '19 at 11:06
  • All of the commits are there in your GitHub repository, with those from repository B on the "main branch line", as it were, and those from repository A reachable from `refs/replace/`. But when cloning, you normally don't get any of those from repository A: you have to explicitly bring over the `refs/replace/*` refs. – torek Dec 29 '19 at 11:07
  • @torek This provides some peace of mind, thank you. So to my other option, is there a way to make these replacements concrete commits on the main line without (or even with) affecting the commits in B? – Felix Dec 29 '19 at 11:12
  • If you run an otherwise no-op `git filter-branch` (no filters listed!) on the clone with the replacement, filter-branch itself obeys the replacing. The result is that commits before the graft are copied to themselves (so their hash IDs remain unchanged) and commits after the graft are copied to new replacements that use the same tree, author, committer, etc., but have different hash IDs due to the different parentage. (The graft stays unchanged, and the new `refs/replace/` points to it, pointlessly.) You can then remove the `refs/replace/*` refs. – torek Dec 29 '19 at 12:09
  • The way to think of this is that filter-branch copies all filtered commits, and since it doesn't know about grafting—unless you run with `--no-replace-objects` that is—it makes all grafts permanent in the process. – torek Dec 29 '19 at 12:11
  • @torek Got it, so it is very much like the graft + filter-branch. Thanks! I would be happy to accept this as an answer. If not, I'll write it myself. – Felix Dec 29 '19 at 12:32
  • You should probably write it up, as I think the key to your question is whether GitHub (or its web interface to view history) follows replacements, and your experiment was the evidence that the answer is "no, it does not". – torek Dec 29 '19 at 20:07

1 Answers1

0

As discussed in the comments above, with great help from torek:

GitHub does not show replacements

In its UI, the replaced root commit has no parents, and the commit count is the number of original commits in the repository. So the replaced, older commits cannot be viewed via GitHub. However, after performing the steps I laid out in the question, they are saved in the repository, not dependent of any other remotes.

How to view replacements

Replacements need to be fetched from the repository in order to view them. As tags and the like, they are not downloaded by default when cloning.

git fetch origin '+refs/replace/*:refs/replace/*'

Then, for example git log --oneline shows the full history. The parent relationship is faked, so the hashes of more recent commits are preserved. If one wishes to truly apply the replacement, git filter-branch with no arguments can be used. Though, at least with graft I had some issues with pushing the changes back (index-pack, expected object not found).

Felix
  • 2,548
  • 19
  • 48