2

To reproduce the situation I am in you can do the following.

git checkout -b parent --track
touch check.yml
cat > check.yml <<EOL
1
2
3
EOL
git add --all
git commit -m "parent branch"
git checkout -b child --track
vim check.yml # Change 1 to 11
git add --all
git commit -m child
git checkout parent
vim check.yml # change 3 to 31
git add --all
git commit --amend

Now if I you run git rebase parent child-1 you will get a merge conflict but if you git checkout child-1 && git rebase you will not get a conflict.

Here is a trace of the git commands from running both.

$:~/webroot/test-repo$ GIT_TRACE=1 git rebase parent child-1
trace: exec: 'git-rebase' 'parent' 'child-1'
trace: run_command: 'git-rebase' 'parent' 'child-1'
trace: built-in: git 'rev-parse' '--parseopt' '--' 'parent' 'child-1'
trace: built-in: git 'rev-parse' '--git-dir'
trace: built-in: git 'rev-parse' '--is-bare-repository'
trace: built-in: git 'rev-parse' '--show-toplevel'
trace: built-in: git 'config' '--bool' 'rebase.stat'
trace: built-in: git 'config' '--bool' 'rebase.autostash'
trace: built-in: git 'config' '--bool' 'rebase.autosquash'
trace: built-in: git 'rev-parse' '--verify' 'parent^0'
trace: built-in: git 'rev-parse' '--verify' 'parent^0'
trace: built-in: git 'show-ref' '--verify' '--quiet' '--' 'refs/heads/child-1'
trace: built-in: git 'rev-parse' '-q' '--verify' 'refs/heads/child-1'
trace: built-in: git 'rev-parse' '--verify' 'HEAD'
trace: built-in: git 'update-index' '-q' '--ignore-submodules' '--refresh'
trace: built-in: git 'diff-files' '--quiet' '--ignore-submodules'
trace: built-in: git 'diff-index' '--cached' '--quiet' '--ignore-submodules' 'HEAD' '--'
trace: built-in: git 'merge-base' 'd29fb1b0ab2eefba0f8ef13fd4e37d61acb9ee6f' '75d8979d743ba575de680fd04d517aa74e813819'
First, rewinding head to replay your work on top of it...
trace: built-in: git 'checkout' '-q' 'd29fb1b0ab2eefba0f8ef13fd4e37d61acb9ee6f^0'
trace: built-in: git 'update-ref' 'ORIG_HEAD' '75d8979d743ba575de680fd04d517aa74e813819'
trace: built-in: git 'format-patch' '-k' '--stdout' '--full-index' '--ignore-if-in-upstream' '--src-prefix=a/' '--dst-prefix=b/' '--no-renames' '--no-cover-letter' 'd29fb1b0ab2eefba0f8ef13fd4e37d61acb9ee6f..75d8979d743ba575de680fd04d517aa$
4e813819'
trace: exec: 'git-am' '--rebasing' '--resolvemsg=
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
'
trace: run_command: 'git-am' '--rebasing' '--resolvemsg=
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
'
trace: built-in: git 'rev-parse' '--parseopt' '--' '--rebasing' '--resolvemsg=
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
'
trace: built-in: git 'rev-parse' '--git-dir'
trace: built-in: git 'rev-parse' '--show-prefix'
trace: built-in: git 'rev-parse' '--show-toplevel'
trace: built-in: git 'var' 'GIT_COMMITTER_IDENT'
trace: built-in: git 'rev-parse' '--verify' '-q' 'HEAD'
trace: built-in: git 'config' '--bool' '--get' 'am.keepcr'
trace: built-in: git 'mailsplit' '-d4' '-o/home/schuettm/webroot/test-repo/.git/rebase-apply' '-b' '--'
trace: built-in: git 'update-index' '-q' '--refresh'
trace: built-in: git 'diff-index' '--cached' '--name-only' 'HEAD' '--'
trace: built-in: git 'cat-file' '-t' '5290c010a2c88dc29a019cb8f08ece9162a6482c'
trace: built-in: git 'cat-file' 'commit' '5290c010a2c88dc29a019cb8f08ece9162a6482c'
trace: built-in: git 'config' 'i18n.commitencoding'
trace: built-in: git 'show' '-s' '--pretty=raw' '--encoding=UTF-8' '5290c010a2c88dc29a019cb8f08ece9162a6482c' '--'
trace: built-in: git 'diff-tree' '--root' '--binary' '--full-index' '5290c010a2c88dc29a019cb8f08ece9162a6482c'
Applying: parent
trace: built-in: git 'apply' '--build-fake-ancestor' '/home/schuettm/webroot/test-repo/.git/rebase-apply/patch-merge-tmp-index' '/home/schuettm/webroot/test-repo/.git/rebase-apply/patch'
trace: built-in: git 'write-tree'
Using index info to reconstruct a base tree...
trace: built-in: git 'diff-index' '--cached' '--diff-filter=AM' '--name-status' 'HEAD'
trace: built-in: git 'apply' '--cached'
trace: built-in: git 'write-tree'
Falling back to patching base and 3-way merge...
trace: built-in: git 'merge-recursive' '4b825dc642cb6eb9a060e54bf8d69288fbee4904' '--' 'HEAD' 'bf8853bde213ddd00cb31b3d22906cf746dcc2fd'
Auto-merging check.yml
CONFLICT (add/add): Merge conflict in check.yml
trace: built-in: git 'rerere'
Failed to merge in the changes.
Patch failed at 0001 parent
trace: built-in: git 'config' '--bool' 'advice.amworkdir'
The copy of the patch that failed is found in:
   /home/schuettm/webroot/test-repo/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

trace: built-in: git 'rev-parse' '--verify' '-q' 'HEAD'

and

$:~/webroot/test-repo$ GIT_TRACE=1 git rebase                                                                                                                                                                            
trace: exec: 'git-rebase'
trace: run_command: 'git-rebase'
trace: built-in: git 'rev-parse' '--parseopt' '--'
trace: built-in: git 'rev-parse' '--git-dir'
trace: built-in: git 'rev-parse' '--is-bare-repository'
trace: built-in: git 'rev-parse' '--show-toplevel'
trace: built-in: git 'config' '--bool' 'rebase.stat'
trace: built-in: git 'config' '--bool' 'rebase.autostash'
trace: built-in: git 'config' '--bool' 'rebase.autosquash'
trace: built-in: git 'rev-parse' '--verify' 'refs/heads/parent^0'
trace: built-in: git 'rev-parse' '--verify' 'refs/heads/parent^0'
trace: built-in: git 'symbolic-ref' '-q' 'HEAD'
trace: built-in: git 'rev-parse' '--verify' 'HEAD'
trace: built-in: git 'merge-base' '--fork-point' 'refs/heads/parent' 'HEAD'
trace: built-in: git 'rev-parse' '--verify' 'HEAD'
trace: built-in: git 'update-index' '-q' '--ignore-submodules' '--refresh'
trace: built-in: git 'diff-files' '--quiet' '--ignore-submodules'
trace: built-in: git 'diff-index' '--cached' '--quiet' '--ignore-submodules' 'HEAD' '--'
trace: built-in: git 'merge-base' 'd29fb1b0ab2eefba0f8ef13fd4e37d61acb9ee6f' '75d8979d743ba575de680fd04d517aa74e813819'
First, rewinding head to replay your work on top of it...
trace: built-in: git 'checkout' '-q' 'd29fb1b0ab2eefba0f8ef13fd4e37d61acb9ee6f^0'
trace: built-in: git 'update-ref' 'ORIG_HEAD' '75d8979d743ba575de680fd04d517aa74e813819'
trace: built-in: git 'format-patch' '-k' '--stdout' '--full-index' '--ignore-if-in-upstream' '--src-prefix=a/' '--dst-prefix=b/' '--no-renames' '--no-cover-letter' '5290c010a2c88dc29a019cb8f08ece9162a6482c..75d8979d743ba575de680fd04d517aa7
4e813819'
trace: exec: 'git-am' '--rebasing' '--resolvemsg=
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
'
trace: run_command: 'git-am' '--rebasing' '--resolvemsg=
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
'
trace: built-in: git 'rev-parse' '--parseopt' '--' '--rebasing' '--resolvemsg=
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
'
trace: built-in: git 'rev-parse' '--git-dir'
trace: built-in: git 'rev-parse' '--show-prefix'
trace: built-in: git 'rev-parse' '--show-toplevel'
trace: built-in: git 'var' 'GIT_COMMITTER_IDENT'
trace: built-in: git 'rev-parse' '--verify' '-q' 'HEAD'
trace: built-in: git 'config' '--bool' '--get' 'am.keepcr'
trace: built-in: git 'mailsplit' '-d4' '-o/home/schuettm/webroot/test-repo/.git/rebase-apply' '-b' '--'
trace: built-in: git 'update-index' '-q' '--refresh'
trace: built-in: git 'diff-index' '--cached' '--name-only' 'HEAD' '--'
trace: built-in: git 'cat-file' '-t' '75d8979d743ba575de680fd04d517aa74e813819'
trace: built-in: git 'cat-file' 'commit' '75d8979d743ba575de680fd04d517aa74e813819'
trace: built-in: git 'config' 'i18n.commitencoding'
trace: built-in: git 'show' '-s' '--pretty=raw' '--encoding=UTF-8' '75d8979d743ba575de680fd04d517aa74e813819' '--'
trace: built-in: git 'diff-tree' '--root' '--binary' '--full-index' '75d8979d743ba575de680fd04d517aa74e813819'
Applying: child
trace: built-in: git 'write-tree'
trace: built-in: git 'rev-parse' '--verify' '-q' 'HEAD'
trace: built-in: git 'commit-tree' '4db00e2710f88742114576a8a7d5ee6875f433c3' '-p' 'd29fb1b0ab2eefba0f8ef13fd4e37d61acb9ee6f'
trace: built-in: git 'update-ref' '-m' 'rebase: child' 'HEAD' 'f7d50b6e381e0894904bd122d13c5880f8583916'
trace: built-in: git 'notes' 'copy' '--for-rewrite=rebase'
trace: built-in: git 'rev-parse' 'HEAD'
trace: built-in: git 'update-ref' '-m' 'rebase finished: refs/heads/child-1 onto d29fb1b0ab2eefba0f8ef13fd4e37d61acb9ee6f' 'refs/heads/child-1' 'f7d50b6e381e0894904bd122d13c5880f8583916' '75d8979d743ba575de680fd04d517aa74e813819'
trace: built-in: git 'symbolic-ref' '-m' 'rebase finished: returning to refs/heads/child-1' 'HEAD' 'refs/heads/child-1'
trace: built-in: git 'gc' '--auto'

If you stare at it for a while it looks like the issue is that git rebase uses git 'merge-base' '--fork-point' 'refs/heads/parent' 'HEAD' to determine the merge base which from the man page also uses the reflog to help out and is likely why it picks up the amend and doesn't stop with a conflict.

What magical arguments are getting passed to git rebase under the hood that allows it to have this functionality?

The man page for git rebase says that branch.<name>.remote and branch.<name>.merge are used however when i substitute those values in after looking them up with git config I get an invalid upstream error so i'm guessing they are expanded in some way internally before use that i don't understand.

michael.schuett
  • 4,248
  • 4
  • 28
  • 39

2 Answers2

1

You are correct, it is the --fork-point magic. This is actually documented:

If <upstream> is not specified, the upstream configured in branch.<name>.remote and branch.<name>.merge options will be used (see git-config(1) for details) and the --fork-point option is assumed. If you are currently not on any branch or if the current branch does not have a configured upstream, the rebase will abort.

You can manually add --fork-point to your own rebase command to get the same effect.

The behavior of --fork-point is not the easiest in the world to describe and the rebase documentation links to the git merge-base documentation. It has a nice diagram but still leaves much to be desired in terms of description.

Because --fork-point relies on reflogs, it only works as long as your upstream branch reflog retains key information. Typically this is for 90 days (the expiry for reachable commits) or 30 days (the expiry for unreachable commits). This does not make it any easier to describe. :-) Mentioning its intent may help: it's meant to help with recovering from an upstream rebase, and it does do that.

torek
  • 448,244
  • 59
  • 642
  • 775
  • I was about to feel really really stupid as I though I had read the man page pretty well. The version of git I am using however doesn't document this. Thanks for the better explanation! – michael.schuett Jul 14 '16 at 21:47
  • For a while, fork-point was magic in `git pull` that was not in rebase at all. Then it was moved to `git rebase` but undocumented. You must have one of the transition versions of Git... – torek Jul 14 '16 at 22:13
0

I will leave this open for someone who knows much more about this then me. But after reading over the git-rebase.sh script that is called for rebasing I found that for some reason --fork-point is magically added when you call git rebase with no args. so git rebase is the same as git rebase --fork-point parent-branch.

michael.schuett
  • 4,248
  • 4
  • 28
  • 39