0

I have two terminals, each focused on a separate copy of the same git repository, both on HEAD of the 'master' branch

In terminal 1, I make a change, commit it and push it.

In terminal 2, I perform a pull. This actually performs a merge (even though I have not changed anything in this instance).

If I now do a 'git status' in terminal 2, it says that it is ahead of origin/master by one commit and that I should perform a push to 'publish your local commits'.

I have two questions:

  1. Why does it perform a merge? Why does it not just retrieve the latest version?

  2. In my small brain, the instance in terminal 2 is now up to date with master. It is not ahead; it is the same. Why does Git consider this to be ahead? It seems (at best) counter-intuitive and (at worse) just plain wrong.

As a side note, if I then go on to push terminal 2's instance (after the pull), then this will add another commit to the repository history, and (according to 'log --graph' the commit from terminal 1 seems to now be treated as a branch of some sort). This all seems, frankly, plain wrong.

Edited to show example;-

Terminal 1

> cd ~/t1
> git clone git@server:test/test-repository-1.git
> cd test-repository-1

Terminal 2

> cd ~/t2/
> git clone git@server:test/test-repository-1.git
> cd test-repository-1

Terminal 1

> vim rich1 [...edit an exiting file 'rich1'...]
> git add -u
> git commit
> git push

[Note: No odd/unexpected output from any of the above]

Terminal 2

> git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From server:test-repository-1
   75ec9a3..ef01990  master     -> origin/master
Merge made by the 'recursive' strategy.
 rich1 | 1 +
 1 file changed, 1 insertion(+)

> git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working directory clean

If I then continue to perform a push from Terminal 2, I get this;-

> git log --graph
*   826660c (HEAD -> master, origin/master, origin/HEAD) Merge branch 'master' of server:test-repository-1
|\  [this is the commit from Terminal 2]
| * ef01990 comment [this was the commit from Terminal 1]
|/
|   M   rich1
<snip>

To answer the request by eftshift0 in the comments, I repeated the above and...;-

Terminal 1

<as previous>
> git commit
[master 3f513fe] comment
 1 file changed, 1 deletion(-)

Terminal 2

> git fetch -a; git log --graph --oneline HEAD @{u}
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From server:test-repository-1
   826660c..3f513fe  master     -> origin/master
* 3f513fe comment
*   826660c Merge branch 'master' of server1:test-repository-1
|\  
| * ef01990 comment
|/  
* 75ec9a3 added text to rich1
* 66b1c1b Modified README and added rich1
* a2af0fd comment 1
* 867b6fd mmreadme added
* db4b74f added empty readme file

> git pull
Merge made by the 'recursive' strategy.
 rich1 | 1 -
 1 file changed, 1 deletion(-)

> git log --graph --oneline HEAD @{u}
*   2597513 Merge branch 'master' of spc-git-dev01:git-training/test-repository-1
|\  
| * 3f513fe comment
|/  
*   826660c Merge branch 'master' of spc-git-dev01:git-training/test-repository-1
|\  
| * ef01990 comment
|/  
* 75ec9a3 added text to rich1
* 66b1c1b Modified README and added rich1
* a2af0fd comment 1
* 867b6fd mmreadme added
* db4b74f added empty readme file

Further edit....

> git config -l
user.email=<snip>
user.name=<snip>
url.https:<snip>
color.ui=true
color.status=true
color.brnch=true
alias.co=checkout
alias.br=branch
alias.dt=difftool
alias.rem=remote
merge.conflictstyle=diff3
merge.tool=vimdiff
merge.ff=false
mergetool.diffconflicts.cmd=nvim -c DiffConflictsWithHistory "$MERGED" "$BASE" "$LOCAL" "$REMOTE"
mergetool.diffconflicts.trustexitcode=true
mergetool.keepbackup=true
mergetool.vimdiff.cmd=nvim -d $MERGED $LOCAL $BASE $REMOTE -c 'wincmd J'
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=<snip>:test-repository-1.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master

> git config --global -l
user.email=<snip>
user.name=<snip>
url.https:<snip>
color.ui=true
color.status=true
color.brnch=true
alias.co=checkout
alias.br=branch
alias.dt=difftool
alias.rem=remote
merge.conflictstyle=diff3
merge.tool=vimdiff
merge.ff=false
mergetool.diffconflicts.cmd=nvim -c DiffConflictsWithHistory "$MERGED" "$BASE" "$LOCAL" "$REMOTE"
mergetool.diffconflicts.trustexitcode=true
mergetool.keepbackup=true
mergetool.vimdiff.cmd=nvim -d $MERGED $LOCAL $BASE $REMOTE -c 'wincmd J'
Rich
  • 185
  • 1
  • 6
  • One possible explanation is that while the HEAD was on the master branch in both copies, the tip of the master branch was not the identical commit in both copies. – mkrieger1 Jul 19 '23 at 10:35
  • 1
    If you create a new commit (a merge commit), you are ahead of the remote because that merge commit does not exist on the remote yet. – mkrieger1 Jul 19 '23 at 10:37
  • 2
    do not say _"on `HEAD` of the `master` branch"_. It is a misuse of the concept of `HEAD` _in git_. What you mean to say is _"I am **at the tip** of `master` branch"_. In git, `HEAD` is **always** where you are standing (which is not **necessarily** at _the tip_ of a branch). – eftshift0 Jul 19 '23 at 10:39
  • Before running the pull of step 2, run `git fetch -a; git log --graph --oneline HEAD @{u}` and put it in the question so that we are able to see what commits are there for the branch that you are on and what is in the upstream branch. – eftshift0 Jul 19 '23 at 10:42
  • 1
    What you have shown is entirely normal. It's unfortunate that `git pull` creates a merge commit by default. You can use `--ff-only` or `--rebase` to prevent it. – mkrieger1 Jul 19 '23 at 12:06
  • If I understand correctly, your question is essentially this: https://stackoverflow.com/questions/8509396/git-pull-results-in-extraneous-merge-branch-messages-in-commit-log – mkrieger1 Jul 19 '23 at 12:08
  • 1
    Does this answer your question? [Git pull results in extraneous "Merge branch" messages in commit log](https://stackoverflow.com/questions/8509396/git-pull-results-in-extraneous-merge-branch-messages-in-commit-log) – Toby Speight Jul 19 '23 at 12:52
  • That _looks_ like it should have done a `ff` instead of a 3-way merge (assuming you were on `826660c` locally when you did the pull). What is the output of `git config -l ` and `git config --global -l` (add them to the question too)? – eftshift0 Jul 19 '23 at 12:53
  • 1
    @mkrieger1 that is _not_ normal.... if there is no config that says otherwise, git pull should do a ff if HEAD is on a commit that is in the history of the branch that is being merged... that's why I am asking for OP's config to know if there is something to that effect. – eftshift0 Jul 19 '23 at 12:54
  • @mkrieger1 - You may be right! Still checking. It seems like broken default behaviour to me though - why would anyone want it to work the way I describe? – Rich Jul 19 '23 at 12:55
  • Yeah seems weird because fast-forward should be the default for pulling. On top of your configs could you write which version of git you are using? – Kim Jul 19 '23 at 12:55
  • Didn't Git change the default for pull to create an explicit merge commit to make it visible when you integrated upstream? I might be wrong, I never bothered looking into the details – knittl Jul 19 '23 at 12:56
  • Thought while at it. If you work on multiple copies of the same repo try out the command git-worktree. It does so you don't have to push your changes to the internet just to pull them down again :) https://git-scm.com/docs/git-worktree – Kim Jul 19 '23 at 12:57
  • 1
    `merge.ff=false` in your config always creates a merge commit and never fast-forwards. – knittl Jul 19 '23 at 13:32
  • `merge.ff=false` is killing you, that's the problem. – eftshift0 Jul 19 '23 at 13:33
  • Re `merge ff=false` - see comment I added below to knittl's answer – Rich Jul 19 '23 at 14:30

3 Answers3

1

Your config contains merge.ff=false, which means that git merge will by default create a merge commit and never tries to fast-forward (unless explicitly overridden with git merge --ff). This is not the default value and had to be set by you (or another user on your system) manually to a non-default value.

Remember that git pull is basically git fetch && git merge, so merge config options apply. Consequently, running git pull will always create a merge commit, even if a fast-forward were possible.

You can override the default merge behavior by configuring pull.ff and setting it to only (only allow fast-forward merges during pull). If your local branches have diverged with this option enabled, you will need to manually specify git pull --no-ff (manual options always override config options)

knittl
  • 246,190
  • 53
  • 318
  • 364
  • I added the `merge ff=false` so that the branch history makes sense when merging between branches - ie, to avoid the problem described here - https://stackoverflow.com/questions/68411828/why-git-merge-happens-but-git-log-graph-does-not-show-the-branching I find it very confusing with the 'ff' option enabled - it destroys the logical branching record – Rich Jul 19 '23 at 14:28
  • @Rich so you are saying "I didn't want fast-forwards when merging, so I forced Git to _always_ create merge commits and never fast-forward" and at the same time asking "Why is Git creating additional merge commits during pull when it could simply fast-forward?" – knittl Jul 19 '23 at 14:38
  • I didn't expect this merge setting to effect the pull. I am now a little more enlightened. I still think this behaviour is broken and non-sensical but that's just my opinion. If I use `--rebase` with the pull then it seems to behave in a sane way. The whole 'fast forward' thing seems to be unique to git - I've never encountered it in any other source control system. From the searching I've done, it seems that it confuses quite a lot of people; it certainly screws up the log history (makes `log --graph` almost useless). But (again) that's just my opinion – Rich Jul 19 '23 at 15:04
  • I guess the trick here is to realize that `pull` is just 2 steps in 1: `git fetch` followed to `git merge` or `git rebase`. – eftshift0 Jul 19 '23 at 15:09
0

It seems that the question referenced by mkrieger1 is what I am seeing -

Git pull results in extraneous "Merge branch" messages in commit log

If I add --rebase to the pull then it seems to work as I would expect. I still don't understand why anyone would want it to work any other way though; it seems like completely nonsensical behaviour to me

Thank you to everyone that helped

Rich
  • 185
  • 1
  • 6
0

As said before, this is the normal behavior when your options are default.

The git pull command is a shorthand for 'git fetch' and 'git merge' commands. This produces a new commit to 'join' the separated workspaces (remote and local):

BEFORE:

                 A---B---C master on origin
                /
          D---E---F---G master
              ^
              origin/master in your repository 

AFTER:

                 A---B---C origin/master
                /         \
           D---E---F---G---H master (H is a new commit)

To avoid this behavior, you can use options as --no-ff and --no-commit, for example. The HEAD commit is only a pointer to the most recent commit in your workspace only, not in all workspaces, remote, etc. If the local and remote workspaces are sync, your HEAD commits should be the same.

  • 1
    First, it's not default behavior. If you run `git merge A` and `HEAD` is an ancestor of `A`, git will default to do a `ff`... unless you have options setup that override this (in this case, the OP has `merge.ff=false`, that's the culprit). Second, `HEAD` is _not_ a pointer to the most recent commit... it is _usually_ that but in reality it is a pointer to the branch/commit where you are working on _which_ is not necessarily the last commit that was made. You might do `git checkout main~100` and then `HEAD` would be 100 commits _behind_ the last commit on that branch. – eftshift0 Jul 19 '23 at 13:37