Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

How do I customize merge behavior for a shared git repo?

+7
−0

I often find it useful to arrange things so that each commit on master's first-parent is a discrete change. It allows git log --first-parent --oneline to be used as a concise, automatically-generated changelog.

There are various ways this can get screwed up. The one I'm most aware of arises from rhombus topologies, where master was merged into a topic branch, perhaps to resolve merge conflicts ahead of submitting a PR:

A---B---D <----master
 \       \
  E---F---G <---topic

Here, when merging topic back into master, you get a fast-forward merge by default, and the resulting parents are backwards.

On the other hand, if you avoid fast-forward merges, you get useless merge bubbles instead. That's not ideal, either:

A---B---C <----master
 \ / \ /
  d   e

I would like to set things up so that, by default, parent swapping doesn't happen and merge-bubbles are avoided. I'd like this policy to "travel with the repo", so my teammates don't have to worry about whether they're doing the right thing.

Is this possible? As I understand it, git has hooks for controlling its behavior, but I don't know if this is a thing they can do.

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

1 answer

+3
−0

Frame challenge, the "bubbles" aren't useless

First off, I'll challenge the premise of the question: the "merge bubbles" are not useless, because they represent meaningful development history. In particular, given the toplogy below:

A -- B -- C -- H <- master
 \         \  /
  D -- E -- F <- topic

Commit F may include non-trivial resolutions to merge-conflicts, and commit H will be a trivial merge. There's an argument to be made that these conflict resolutions belong in the topic branch, and not in master's first-parent ancestors. In any case, master's first-parent ancestors will still contain exactly one commit (H) neatly representing all the changes from the topic branch.

The most obvious alternative topology is a "trapezoid" topology:

          v- master
A -- B -- C -- F <- topic
 \            /
  D -- E ----/

In this case, when topic is merged into master, master will fast-forward to F, and C will be master's first parent, but this topology has some downsides. For instance, if you create a new commit (G) on master in the meantime, you will end up with an even stranger topology when topic is merged back to master:

A -- B -- C -- G -- H <- master
 \         \       /
  \         \---- F <- topic
   \             / 
    \----- D -- E 

Since master can no longer be fast-forwarded, a new merge commit (H) needs to be created.

On the other hand if you used a "rhombus" topology, you would end up with the nicer looking:

A -- B -- C -- G -- H <- master
 \         \       /
  D -- E -- F ----/ <- topic

But if you still don't want bubbles

Avoid making them

You can avoid "rhombus" topologies and "useless bubbles" by merging the topic into the commit pointed to by master instead of merging master into the topic branch. Then when master gets fast-forwarded, its first parent will be the commit previously pointed to by master.

This isn't a common workflow, so while there are a few ways to perform this "reverse" merge, none of them are straightforward. One method would be:

git switch --detach master
git merge topic
git update-ref refs/heads/topic HEAD
git switch topic

In English, that process is:

  1. Switch to master, but with a detached HEAD so we don't later change which commit master points to
  2. Merge in topic. Now we have a commit whose first parent is master and whose second parent is topic.
  3. Update topic to point to the new commit
  4. Switch back to topic.

You might consider configuring (and distributing) a Git alias to make this process easier.

Avoid taking them

You can have the server reject such bubbles by creating an "update" server-side Git hook. For example, you may use a shell script like below for the hook:

#!/usr/bin/sh
ref="$1"
old_commit="$2"
new_commit="$3"

grandparent=$(git rev-parse --verify --quiet "$new_commit^2^2" || :)
if [ "$grandparent" = "$old_commit" ]; then
    printf "%s\n" \
        'Useless bubble detected' \
        'Please `git reset --hard HEAD^` then "reverse" merge'
    exit 1
fi

If anyone tries to push commit G, the server will reject their push, and they'll receive a message like:

remote: Useless bubble detected
remote: Please `git reset --hard HEAD^` then "reverse" merge
remote: error: hook declined to update refs/heads/master
To /tmp/git-remote
 ! [remote rejected] master -> master (hook declined)
error: failed to push some refs to 'user@example.com:example.git'
History
Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »