Skip to content

Instantly share code, notes, and snippets.

@TerraTech
Forked from datagrok/git-branch-simplify.md
Created August 6, 2017 15:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TerraTech/85c27a9978828ee5c9a9277229d063c4 to your computer and use it in GitHub Desktop.
Save TerraTech/85c27a9978828ee5c9a9277229d063c4 to your computer and use it in GitHub Desktop.
How to simplify the graph produced by git log --graph

Ideas for improvements to git log --graph

I will maybe someday get around to dusting off my C and making these changes myself unless someone else does it first.

Make the graph for --topo-order less wiggly

Imagine a long-running development branch periodically merges from master. The git log --graph --all --topo-order is not as simple as it could be, as of git version 1.7.10.4.

It doesn't seem like a big deal in this example, but when you're trying to follow the history trails in ASCII and you've got several different branches displayed at once, it gets difficult quickly.

To simulate:

git init example
cd example
git commit --allow-empty -m "initial empty commit"
git branch dev
for x in `seq 4`; do
    git commit --allow-empty -m "commit"
    git checkout dev
    git merge --no-ff --no-edit master
    git checkout master
done

The result:

$ git log --graph --topo-order --decorate --oneline --all
*   f19e46d (dev) Merge branch 'master' into dev
|\
| * 600a33f (HEAD, master) commit
* |   c9ccbf1 Merge branch 'master' into dev
|\ \
| |/
| * 4c1fb52 commit
* |   dfdcb8a Merge branch 'master' into dev
|\ \
| |/
| * 42b1c24 commit
* |   7714be3 Merge branch 'master' into dev
|\ \
| |/
| * f7eae2d commit
|/
* 885700a initial empty commit

What I would like it to display:

$ git log --graph --topo-order --decorate --oneline --all
*   f19e46d (dev) Merge branch 'master' into dev
|\
| * 600a33f (HEAD, master) commit
* |   c9ccbf1 Merge branch 'master' into dev
|\|
| * 4c1fb52 commit
* |   dfdcb8a Merge branch 'master' into dev
|\|
| * 42b1c24 commit
* |   7714be3 Merge branch 'master' into dev
|\|
| * f7eae2d commit
|/
* 885700a initial empty commit

--boundary makes mountains

The previous example wasn't so bad. But we had the luxury of having only two branches in the repo, with not many commits on each, allowing us to specify --all.

Let's now assume there are many branches. A way to ask for the graph of a long-running parallel development branch that merges frequently from master might be to show the log for master..branch, but also to use --boundary. But, that produces this strange mountainous result:

$ git log --graph --topo-order --decorate --oneline --boundary master..dev
*   f19e46d (dev) Merge branch 'master' into dev
|\
* \   c9ccbf1 Merge branch 'master' into dev
|\ \
* \ \   dfdcb8a Merge branch 'master' into dev
|\ \ \
* \ \ \   7714be3 Merge branch 'master' into dev
|\ \ \ \
| | | | o 600a33f (HEAD, master) commit
| | | |/
| | | o 4c1fb52 commit
| | |/
| | o 42b1c24 commit
| |/
| o f7eae2d commit
|/
o 885700a initial empty commit

That's a big diagram for only 5 commits and 5 merges! As with the previous example, I think this should output:

$ git log --graph --topo-order --decorate --oneline --boundary master..dev
*   f19e46d (dev) Merge branch 'master' into dev
|\
| o 600a33f (HEAD, master) commit
* |   c9ccbf1 Merge branch 'master' into dev
|\|
| o 4c1fb52 commit
* |   dfdcb8a Merge branch 'master' into dev
|\|
| o 42b1c24 commit
* |   7714be3 Merge branch 'master' into dev
|\|
| o f7eae2d commit
|/
* 885700a initial empty commit

Let me specify which branches sink to the left.

Sometimes I'm faced with a very complex mess of merges and branches. I'd like to be able to ask git log to show me the graph, but to always set commits from specific branches into specific columns in the output. This might make for more line-drawing to connect the commits, but that's okay in this situation.

Using the very same repo above, I'd like to say "make left-most columns for master and dev, in that order" and see:

$ git log --graph --all --topo-order --decorate --oneline --boundary --force-branch-columns=master,dev

.-- master
| .-- dev
v v

  * a9f8d93 (dev) Merge branch 'master' into dev
 /|
* | 5f32650 (HEAD, master) commit
| * b511501 Merge branch 'master' into dev
|/|
* | 4e6810e commit
| * 2cd55b4 Merge branch 'master' into dev
|/|
* | 4f74695 commit
| * 372799e Merge branch 'master' into dev
|/|
* | 076669f commit
 \|
  * 7382440 initial empty commit

Maybe that doesn't seem like it's of much value, but let's throw another branch into the mix and see what it might look like.

Branch from somewhere in the middle of dev, create two commits, then merge from master:

git checkout -b newbranch dev~3
echo 2 >> 2.txt; git add 2.txt; git commit -m "commit"
echo 2 >> 2.txt; git add 2.txt; git commit -m "commit"
git merge --no-ff --no-edit master

The unmodified result:

$ git log --graph --all --topo-order --decorate --oneline --boundary
*   f745bf5 (HEAD, newbranch) Merge branch 'master' into newbranch
|\
* | 7031537 commit
* | 416ab2c commit
| | *   a9f8d93 (dev) Merge branch 'master' into dev
| | |\
| | |/
| |/|
| * | 5f32650 (master) commit
| | *   b511501 Merge branch 'master' into dev
| | |\
| | |/
| |/|
| * | 4e6810e commit
| | *   2cd55b4 Merge branch 'master' into dev
| | |\
| |/ /
|/| /
| |/
| * 4f74695 commit
* |   372799e Merge branch 'master' into dev
|\ \
| |/
| * 076669f commit
|/
* 7382440 initial empty commit

What's most striking to me about the as-is unmodified output is the frequency with which we see this cross-legged pattern:

  *   b511501 Merge branch 'master' into dev
  |\
  |/
 /|
* | 4e6810e commit
  *   2cd55b4 Merge branch 'master' into dev

Is the output perhaps intentionally like this to show which parent of a merge commit is the "first" parent? If so, that may be useful sometimes, so I wouldn't want to see that style completely removed in favor of my suggestion.

With different line-drawing logic and forced columns, it might look like this:

$ git log --graph --all --topo-order --decorate --oneline --boundary --force-branch-columns=master,dev

.-- master
| .-- dev
v v

    * f745bf5 (HEAD, newbranch) Merge branch 'master' into newbranch
   /|
  / * 7031537 commit
 /  * 416ab2c commit
| * | a9f8d93 (dev) Merge branch 'master' into dev
|/| |
* | | 5f32650 (master) commit
| * | b511501 Merge branch 'master' into dev
|/| |
* | | 4e6810e commit
| * | 2cd55b4 Merge branch 'master' into dev
|/|/
| /
|/|
* | 4f74695 commit
| * 372799e Merge branch 'master' into dev
|/|
* | 076669f commit
 \|
  * 7382440 initial empty commit

I think that it is much easier with that visualization to track which commits are in master and dev, and see the points at which other branches forked from and merged with them.

Elide "branch tails" to avoid "mountains"

Consider a workflow where feature branches diverge from some integration branch like "master," are worked on for some time, and are then merged back into that upstream branch. They may be worked on concurrently, and they may or may not be merged in the same order that they were begun.

They often have long tails that show where the branch was started, which is information I don't really need. When too many of these tails collect in the output, they become impossible to visually trace, forming an ugly "mountain."

To simulate:

git init example
cd example
git commit --allow-empty -m "initial empty commit"
for b in 1 2 3 4 5 6; do
    git checkout master
    git commit --allow-empty -m "start of feature-$b"
    git branch feature-$b
done
for x in `seq 3`; do
    for b in 1 2 3 4 5 6; do
        git checkout feature-$b
        git commit --allow-empty -m "commit $b-1.$x"
    done
done
git checkout master
for b in 6 5 4 1 2 3; do
    git checkout master
    git merge --no-ff --no-edit feature-$b
    git branch -d feature-$b
done

The result:

*   85a4880 (HEAD, master) Merge branch 'feature-3'
|\
| * 6057a0a commit 3-1.3
| * aa2d7e7 commit 3-1.2
| * c5130d2 commit 3-1.1
* |   a6f44d9 Merge branch 'feature-2'
|\ \
| * | 97612ed commit 2-1.3
| * | f3e66fc commit 2-1.2
| * | 2a5af7a commit 2-1.1
* | |   02bfff3 Merge branch 'feature-1'
|\ \ \
| * | | 50a334e commit 1-1.3
| * | | 29d5386 commit 1-1.2
| * | | eccd08a commit 1-1.1
* | | |   d185b26 Merge branch 'feature-4'
|\ \ \ \
| * | | | 6c044c8 commit 4-1.3
| * | | | 3f9cdd9 commit 4-1.2
| * | | | 5da9916 commit 4-1.1
* | | | |   bcc9b74 Merge branch 'feature-5'
|\ \ \ \ \
| * | | | | 56ef96c commit 5-1.3
| * | | | | 22d50d7 commit 5-1.2
| * | | | | 5d539a3 commit 5-1.1
* | | | | |   84b1d36 Merge branch 'feature-6'
|\ \ \ \ \ \
| * | | | | | b5f5ef6 commit 6-1.3
| * | | | | | 691a8f6 commit 6-1.2
| * | | | | | 50dc2ae commit 6-1.1
|/ / / / / /
* | | | | | 989fe62 start of feature-6
|/ / / / /
* | | | | 22fc09d start of feature-5
|/ / / /
* | | | 7b9ac41 start of feature-4
| |_|/
|/| |
* | | 4fbc57e start of feature-3
| |/
|/|
* | e4c2da5 start of feature-2
|/
* 9f0e3b1 start of feature-1
* f7d3186 initial empty commit

I'd like to be able to elide branch tails, maybe only when they split from the same branch that they will eventually merge into:

*   85a4880 (HEAD, master) Merge branch 'feature-3'
|\
| * 6057a0a commit 3-1.3
| * aa2d7e7 commit 3-1.2
| * c5130d2 commit 3-1.1
| :
* a6f44d9 Merge branch 'feature-2'
|\
| * 97612ed commit 2-1.3
| * f3e66fc commit 2-1.2
| * 2a5af7a commit 2-1.1
| :
* 02bfff3 Merge branch 'feature-1'
|\
| * 50a334e commit 1-1.3
| * 29d5386 commit 1-1.2
| * eccd08a commit 1-1.1
| :
* d185b26 Merge branch 'feature-4'
|\
| * 6c044c8 commit 4-1.3
| * 3f9cdd9 commit 4-1.2
| * 5da9916 commit 4-1.1
| :
* bcc9b74 Merge branch 'feature-5'
|\
| * 56ef96c commit 5-1.3
| * 22d50d7 commit 5-1.2
| * 5d539a3 commit 5-1.1
| :
* 84b1d36 Merge branch 'feature-6'
|\
| * b5f5ef6 commit 6-1.3
| * 691a8f6 commit 6-1.2
| * 50dc2ae commit 6-1.1
|/
* 989fe62 start of feature-6
* 22fc09d start of feature-5
* 7b9ac41 start of feature-4
* 4fbc57e start of feature-3
* e4c2da5 start of feature-2
* 9f0e3b1 start of feature-1
* f7d3186 initial empty commit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment