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.

Post History

71%
+3 −0
Q&A How do I customize merge behavior for a shared git repo?

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 ...

posted 1y ago by GrantMoyer‭

Answer
#1: Initial revision by user avatar GrantMoyer‭ · 2023-09-14T02:34:32Z (over 1 year ago)
### 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:

```text
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:

```text
          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:

```text
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:

```text
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:

```sh
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][1] (and distributing) a [Git alias][2] to make this process easier.

#### Avoid taking them 

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

```sh
#!/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:

```text
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'
```

[1]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-alias
[2]: https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases
[3]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks