The possibilities
I can think of a couple of ways this could have happened.
Fast-forward merge. I believe this to be the most likely cause. This is what I believe happened:
release-1.3
was merged into develop
- immediately after,
develop
was fast-forward merged into release-1.3
That's all. release-1.3
was merged to develop
, then before any additional commits were made on release-1.3
someone merged develop
into release-1.3
. When possible, git
does a fast-forward merge by default (a "feature" that does the wrong thing half the time), which is why the graph looks confusing.
Note that a fast-forward merge leaves no direct evidence in the resulting graph. Unlike a regular merge, a fast-forward merge does not create a new commit object, and no existing commit is modified. A fast-forward merge simply adjusts the branch reference to point to an existing commit.
Even though there is no direct evidence of a fast-forward merge, the fact that you can reach that one merge commit by following each branch's first-parent path strongly indicates that this was a result of a fast-forward merge.
Accidental git branch
instead of git checkout
. On a project I was working on, another developer that was new to Git made the mistake of typing git branch foo
in an attempt to switch to branch foo
. This is a perfectly natural mistake, and one that even an expert can make when tired. Anyway, this mistake eventually resulted in something that looked just like a fast-forward merge even though git merge
was never typed. A similar thing could have happened here. This is how the scenario plays out:
- The user has the
develop
branch checked out, and develop
happens to be pointing to the merge commit that is at the center of this mystery.
- The user wishes to switch to the
release-1.3
branch to do some work, but accidentally types git branch release-1.3
instead of git checkout release-1.3
.
- Because it is a fresh clone, there is no local
release-1.3
branch yet (only origin/release-1.3
). Thus, Git happily creates a new local branch named release-1.3
that is pointing at that same merge commit.
- Some time passes while the user edits some files.
- Preparing to commit, the user runs
git status
. Because the current branch is still develop
and not release-1.3
, Git prints "On branch develop".
- The user is caught by surprise, and thinks, "Didn't I switch to release-1.3 a long time ago? Oh well, I must have forgotten to switch branches."
- The user runs
git checkout release-1.3
, this time remembering the correct command.
- The user creates a commit and runs
git push
.
- Git's default push behavior (see
push.default
in git help config
) is matching
, so even though release-1.3
doesn't have a configured upstream branch, git push
chooses the upstream's release-1.3
branch to push to.
- The new version of
release-1.3
is a descendant of the previous release-1.3
, so the remote repository happily accepts the push.
This is all it would take to produce the graph you provided in your question.
Assuming develop
was intentionally merged into release-1.3
If the merge of develop
into release-1.3
was intentional (someone made a conscious decision that develop
was good enough to ship), this is perfectly normal and correct. Despite the visual differences, the blue and black lines are both on the release-1.3
branch; the blue line just happens to also be on the develop
branch.
The only thing wrong is that it's a little awkward for someone reviewing the history to figure out what's going on (you wouldn't have this question otherwise). To prevent this from happening again, follow these rules of thumb:
- If you are merging two differently-named branches (e.g.,
develop
and release-1.3
, then always do git merge --no-ff
.
- If you are merging two versions of the same branch (e.g.,
develop
and origin/develop
), then always do git merge --ff-only
. If that fails because you can't fast-forward, then it's time for git rebase
.
If you had followed the above rules of thumb, then the graph would have looked like this:
* (develop)
| * (release-1.3)
* | Merge...
|\|
| * Added...
| * using ...
* | adding...
| * Hide s...
* | Date ...
* | updati...
* | Candi...
| * Locali...
| * <---- merge commit that would have been created by
|/| 'git merge' had you used the '--no-ff' option
* | Merge...
|\|
| * Un-ign...
| * Added...
* | Merge...
|\|
| * Remov...
| * Move...
* | Fixed...
Notice how that extra merge commit makes the history much more readable.
If the merge of develop
into release-1.3
was a mistake
Doh! Looks like you have some rebasing and force-pushing to do. This will not make the other users of this repository happy.
Here's how you can fix it:
- Run
git checkout release-1.3
.
- Find the sha1 of that middle commit (where the two branches come together). Let's call it
X
.
Run git rebase --onto X^2 X
. The resulting graph will look like this:
* (develop)
| * (release-1.3)
* | Merge...
|\ |
| * | Added...
| | * Added...
| * | using ...
| | * using ...
* | | adding...
| * | Hide s...
| | * Hide s...
* | | Date ...
* | | updati...
* | | Candi...
| * | Locali...
|/ * Locali...
* / Merge...
|\|
| * Un-ign...
| * Added...
* | Merge...
|\|
| * Remov...
| * Move...
* | Fixed...
This fixes the release-1.3
branch, but notice how you now have two versions of the release-1.3
commits. The next steps will remove these duplicates from the develop
branch.
- Run
git checkout develop
.
- Run
git branch temp
to act as a temporary placeholder for this commit.
- Run
git reset --hard HEAD^^
to remove two commits from the develop
branch: the tip develop
commit and the commit merging the old version of release-1.3
into develop
. We'll restore that tip commit later.
- Run
git merge --no-ff release-1.3^
to merge the second commit on the new release-1.3
branch and its ancestors into develop
.
- Run
git cherry-pick temp
to bring back the tip commit that was removed in step #6.
Run git branch -D temp
to get rid of the temporary placeholder branch. Your graph should now look like this:
* (develop)
| * (release-1.3)
* | Merge...
|\|
| * Added...
| * using ...
* | adding...
| * Hide s...
* | Date ...
* | updati...
* | Candi...
| * Locali...
* | Merge...
|\|
| * Un-ign...
| * Added...
* | Merge...
|\|
| * Remov...
| * Move...
* | Fixed...
Run git push -f origin release-1.3 develop
to force-update the upstream branches.
Preventing this from happening again in the future
If you have control over the upstream repository and can install some hooks, you can create a hook that rejects any pushes where the old version of the branch isn't reachable by starting at the new version of the branch and walking the first-parent path. This also has the advantage of rejecting those stupid commits created by git pull
.