0

Let's say I maintain a fork of a project.

I want to perdiodically rebase downstream onto upstream while preserving all the commit SHAs of the changes. That is, I want to rebase each individual merge commit, but I want all the change commits to keep their own parent.

Here's a simplified example, with only one downstream patch.

Initial state:

DOWNSTREAM:
*   d9ce4cb Merge branch 'pr1' into downstream   # <- our merge commit for the downstream change
|\
| * dcc0f5e (pr1) Downstream commit 1       # <- our change downstream
|/
* ee6faa1 Upstream commit 2
* c57fe05 Upstream commit 1
UPSTREAM:
* 732985e Upstream commit 3       # <- one NEW COMMIT upstream
* ee6faa1 Upstream commit 2
* c57fe05 Upstream commit 1

Now, I run:

$ git rebase --rebase-merges=no-rebase-cousins upstream

I end up with:

ACTUAL DOWNSTREAM:
*   e7574a0 Merge branch 'pr1' into downstream
|\
| * 2dc0049 (pr1) Downstream commit 1       # <-- BAD: The downstream commit has changed parent
|/
* 732985e Upstream commit 3
* ee6faa1 Upstream commit 2
* c57fe05 Upstream commit 1

Instead, this is I want (notice that "Downstream commit 1" still has "Upstream commit 2" as a parent, hence it keeps its commit ID):

DESIRED NEW DOWNSTREAM:
*   e7574a0 Merge branch 'pr1' into downstream      # <- The merge commit has moved
|\
* | 732985e Upstream commit 3
| * dcc0f5e (pr1) Downstream commit 1       # <- GOOD: The downstream commit is preserved
|/
* ee6faa1 Upstream commit 2
* c57fe05 Upstream commit 1

Is there a magic trick to end up in the desired state? (one that ideally works when there are N downstream merge commits to rebase)

Pierre Prinetti
  • 9,092
  • 6
  • 33
  • 49
  • 2
    A commit id is [computed using the content of the commit, which includes its parents](https://stackoverflow.com/questions/35430584/how-is-the-git-hash-calculated). If you perform a rebase and change the parents of a commit, then the commit id will change. That is fundamental to how git works. – larsks Apr 09 '21 at 16:11
  • yup, and the game here is really that all the "change commits" keep their original parent. Only the merge commits change parent, and it's fine for them to change commit ID. – Pierre Prinetti Apr 09 '21 at 16:13
  • short answer: you can't. Slightly longer answer: If you could, then it would have BIG security implications as a lot of things rest on these hashes not colliding ever (there are sha1 collisions out there and that's why git is moving to sha256, for example). – eftshift0 Apr 09 '21 at 16:14
  • @PierrePrinetti The contents of a commit also includes timestamps, so a rebase by its nature will change the ids even if nothing else changes. – Code-Apprentice Apr 09 '21 at 16:14
  • @Code-Apprentice and eftshift0 In this hypothetical rebase, the only commits being changed are the merge commits. For the rest: content stay the same, parent stays the same, hence no reason for the SHA to change. It's not a collision: it's really the same item which conserves its ID. – Pierre Prinetti Apr 09 '21 at 16:18
  • @PierrePrinetti So why not perform a merge instead of a rebase? – Code-Apprentice Apr 09 '21 at 16:19
  • One point of clarification: are "upstream" and "downstream" branches or remotes? – Code-Apprentice Apr 09 '21 at 16:22
  • are "upstream" and "downstream" branches or remotes: I don't see a difference. If you find a solution by first checking out locally, that's fine for me :) – Pierre Prinetti Apr 09 '21 at 16:24
  • @PierrePrinetti A branch and a remote are fundamentally different things. The exact command to use depends on whether you are working with a single repo with two branches or two repositories where one has a remote that points at the other. – Code-Apprentice Apr 09 '21 at 16:26
  • a remote is a branch you haven't checked out yet :) However as I mentioned, if you find one way of achieving that, I don't mind if it's against a remote branch or a local branch. If that's what you are asking: the two branches have a common ancestor – Pierre Prinetti Apr 09 '21 at 16:31
  • More correctly a remote is a URL to another repo. You are probably thinking of a so-called **remote tracking branch** which is a local copy of a branch from a remote (whether you have checked out a local copy of that branch or not). – Code-Apprentice Apr 09 '21 at 16:32

1 Answers1

2

I want to perdiodically rebase downstream onto upstream while preserving all the commit SHAs of the changes.

This is not possible. The SHA hash of a commit is computed from the contents of the commit which include its parents. If you rebase the downstream branch onto an upstream branch, the parent of the first rebase changes which creates a new commit with a new hash. Then the next commit must be rebased onto this new parent so its hash changes, etc.

DESIRED NEW DOWNSTREAM:
*   e7574a0 Merge branch 'pr1' into downstream      # <- The merge commit has moved
|\
* | 732985e Upstream commit 3
| * dcc0f5e (pr1) Downstream commit 1       # <- GOOD: The downstream commit is preserved
|/
* ee6faa1 Upstream commit 2
* c57fe05 Upstream commit 1

From this diagram, it seems like you need to do a merge, not a rebase. For example:

git checkout downstream
git merge 732985e
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268