0

I would like to understand why rebase is changing the order and the commits ID. Consider the following commit tree:

 F1 -- F2 feature / M1 -- M2 -- M3 main 

There is another team commiting changes to main and pushing to remote and I need to catch up latest changes from main to feature, so I decided to use rebase as following:

myrepo git:(main) git pull origin main 

Now my local main branch contains the latest changes from remote (M3). Now I need to go to feature and do a git rebase:

➜ myrepo git:(main) git checkout feature Switched to branch 'feature' Your branch is up to date with 'origin/feature'. ➜ myrepo git:(feature) git rebase main Successfully rebased and updated refs/heads/feature. 

Checking my commits history:

d78c8d5 (HEAD -> feature) F3 775e06c F2 1cc4843 F1 31a5870 (origin/main, origin/HEAD, main) M3 9fd7263 M2 756c88d M1 eec81ba Initial commit 

The result is expected since rebase put all feature commits "after" main commits. The question is, if I run rebase without any arguments as following:

➜ myrepo git:(feature) git rebase warning: skipped previously applied commit 1cc4843 warning: skipped previously applied commit 775e06c hint: use --reapply-cherry-picks to include skipped commits hint: Disable this message with "git config advice.skippedCherryPicks false" Successfully rebased and updated refs/heads/feature. 

Besides the warning messages saying it will skip previously applied commits, it really changed the order and commits ID:

65ed33d (HEAD -> feature) F3 2b7e517 M3 7cea741 (origin/feature) F2 9c267ef F1 9fd7263 M2 756c88d M1 eec81ba Initial commit 

It seems something related to upstream, because now it is possible to run a push without the --force option.

➜ myrepo git:(feature) git push Enumerating objects: 11, done. Counting objects: 100% (11/11), done. Delta compression using up to 10 threads Compressing objects: 100% (4/4), done. Writing objects: 100% (7/7), 641 bytes | 641.00 KiB/s, done. Total 7 (delta 1), reused 0 (delta 0), pack-reused 0 remote: remote: To create a merge request for feature, visit: 

What exactly is this "second time rebase" without argument doing? Why is it changing commits?"

1
  • 2
    Plain git rebase is documented to use your current branch's configured base, here that's origin/feature as usual. So that's what it did, it rebased your checked-out history on the default origin/feature, and mentioned that some of the commits look like they were already cherrypicked there so it skipped them. Commented Jun 17, 2022 at 22:35

1 Answer 1

3

Each rebase operation will take whatever commits are not already present on the destination and recreate them there. I say recreate, and not move, because a commit is something immutable, with its fixed set of changes, parents, message and other meta-data. A rebase is creating a new commit with the same message, changes, meta-data but different parents.

Now I will add origin/feature to your original graph, pointing to F2 because that's where your final log says it is.

I will also add F3 to your original graph, and assume feature pointed to it. The logs you show after the rebases are impossible unless that was the case. (Or did you create F3 after the first rebase? In any case, you're showing it in the results of the rebase, so it has to have gotten created either initially or before the second rebase, but origin/feature never pointed to it according to your last log output.)

Updated initial state:

 F3 feature / F1 -- F2 origin/feature / M1 -- M2 -- M3 main, origin/main 

The first rebase created F1', F2' and F3' and set feature to point to F3', but left origin/feature alone, still pointing at F2:

 F1 -- F2 origin/feature / / F1' -- F2' -- F3' feature / / M1 -- M2 -- M3 main, origin/main 

The second rebase says take feature and rebase it on top of origin/feature. That means take M3, F1', F2' and F3' and add them after F2. M2 is in the shared history, so it does not need to be considered. Now, by default that might create M1 - M2 - F1 - F2 - M3' - F1'' - F2'' - F3'' as the new history for feature, except that the rebase notices that F1' has the same change set as F1, so it skips it (as the log message says), and ditto for F2'. The final results you get are as expected once you skip creating F1'' and F2'':

 M3' -- F3'' -- feature / F1 -- F2 origin/feature / / F1' -- F2' -- F3' (nothing points here anymore, but it still exists) / / M1 -- M2 -- M3 main, origin/main 

(The original F3 also still exists, with F2 as its parent but no branch pointing to it. My graph is getting too crowded to display it, though.)

Now, if you had run your second rebase with --keep-empty, which tells the rebase to "Keep the commits that do not change anything from its parents in the result," you would have indeed had F1 - F2 - M3' - F1'' - F2'' - F3'', but F1'' and F2'' would have been empty commits, since they were trying to reproduce F1' and F2' which are bringing in changes that have already been applied earlier in the history leading to M3'.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.