Why did Git just perform a forced update without the --force flag?

Viewed 86

We had a really bizarre incident at work yesterday. I performed some changes to a repository, committed, and invoked git push, just like I always do. Here's what happened:

user@host:~/path/to/repo$ git add file_a.h 
user@host:~/path/to/repo$ git add file_b.h
user@host:~/path/to/repo$ git add file_c
user@host:~/path/to/repo$ git add file_d.h
user@host:~/path/to/repo$ git commit
[master a9c4595] Perform change requested by customer.
 4 files changed, 4 insertions(+), 4 deletions(-)
user@host:~/path/to/repo$ git push
Enumerating objects: 17, done.
Counting objects: 100% (17/17), done.
Delta compression using up to 4 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 845 bytes | 845.00 KiB/s, done.
Total 9 (delta 8), reused 0 (delta 0)
To ssh://git.corp.com:port/project/repo.git
 + 38a09ed...a9c4595 master -> master (forced update)
 + 38a09ed...a9c4595 38 -> 38 (forced update)
user@host:~/path/to/repo$ git log

Specifically observe that (forced update) notice there. My coworker had pushed changes to this repository just before me, and in my ~7 years of Git usage, every single time this scenario has happened, Git has warned me that the remote has changes I don't have, and asks me to pull before I push. Why did this not happen now? Git instead erased my coworker's commit on the remote, and replaced it with my own HEAD^. How would I go about preventing this in the future?

Additional details: Our Git workflow, in terms of integration with other projects, relies on tags, so we have a post-commit hook that looks like this:

#!/bin/bash

tag_ver="$(git describe --tags --match 0 | cut -d'-' -f2)"

git rev-list HEAD | head -n1 | xargs git tag $tag_ver

So every time a commit is made, it just increments the tag by one.

1 Answers

As you said in a comment:

I might have just found the culprit. git config -l revealed the following line: remote.origin.push=+refs/heads/*

This is indeed the reason. As the git push documentation says:

When the command line does not specify what to push with <refspec>... arguments or --all, --mirror, --tags options, the command finds the default <refspec> by consulting remote.*.push configuration, and if it is not found, honors push.default configuration to decide what to push (See git-config(1) for the meaning of push.default).

A refspec that omits the colon, for git push purposes, effectively repeats the <src> part, so your remote.origin.push makes git push origin act like git push origin +refs/heads/*:refs/heads/*. The leading + sign sets the force flag for this refspec.

Related