Create a gist now

Instantly share code, notes, and snippets.

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
@gsscoder
gsscoder commented Mar 3, 2013

It would be cool 👍 !

@nvie
nvie commented Mar 4, 2013

👍

Somewhat two years ago, I had an idea for a pet project that resembled this feature a lot. I never pursued it, though :(

I wanted to use that for git-flow based projects, where you typically have two "fixed" branches (develop and master), and the rest of the branches lives somewhere in between, or "to the left of" develop. Basically, I would love to use this option:

--force-branch-columns="*,develop,release*,hotfix*,master"

This would basically draw git logs laid out as the branching model's main picture.

I think having such a feature would tremendously simplify reading your git logs, as it renders branches the way you would love to think about them.

@MiroRadenovic

yes,that would be great feature

@vidia
vidia commented Dec 16, 2013

Maybe I am resurrecting a dead thread, but I love this idea. Just throwing that out there.

@pkoch
pkoch commented Jan 9, 2014

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.

I'm not really sure on how this would work. The branch is a pointer to a commit, not more. While it's definitely possible to order the "lane position" of the current commit of a branch, git has no way to tell if a commit "belongs to a branch" further past in history.

@staltz
staltz commented Jan 24, 2014

I would love to see this too, and I've been wondering how to develop it (let's call it "git flog"? Flow + log). pkoch pointed out the main problem, and probably to accomplish "git flog", one would need to modify git further.

@mdeady
mdeady commented Jun 19, 2014

Now I'm definitely resurrecting an old thread...

@pkoch:

git has no way to tell if a commit "belongs to a branch" further past in history

I think you could base this off of the parent order. For merge commits, the first parent would stay in the same column. Ambiguity arises from deciding which column to place a 'branching' commit (a commit that is the first parent of multiple commits). You could probably just have those commits aligned into the left-most column of all the branches that are involved. So in the last snippet in OP's post, 7382440 initial empty commit would instead be in the left column as master is the left-most column of the two columns that split from it.

@drachlyznardh

It may be late to post this, but I'm very interested in the matter.

git has no way to tell if a commit "belongs to a branch" further past in history

Wouldn't it be possible to force the columns using tags instead of branches? Tags don't move, so they can mark a point in history as needed, and their name can be conventionally prefixed to show their desired column.

Something like --force-tag-columns="hotfix-*,release-*,*" could turn

*   9e8966d (HEAD, tag: release-0.b, master) Merge branch 'devel'
|\  
| * 53c17b8 (devel) A devel commit.
| * 1a7201b A devel commit.
| * f709752 A devel commit.
| *   c54a11c Merge branch 'master' into devel
| |\  
| |/  
|/|   
* |   42a126d (tag: release-0.a-000) Merge branch 'hotfix'
|\ \  
| * | 5a13312 (tag: hotfix-000, hotfix) Fixing bug #000
|/ /  
* |   8ff7de1 (tag: release-0.a) Merge branch 'devel'
|\ \  
| |/  
| * 6984067 A devel commit.
| * f1213d4 A devel commit.
| * 3c075a4 A devel commit.
|/  
* 3ffe4fe (tag: release-base) First commit.

into

  *   9e8966d (HEAD, tag: release-0.b, master) Merge branch 'devel'
  |\  
  | * 53c17b8 (devel) A devel commit.
  | * 1a7201b A devel commit.
  | * f709752 A devel commit.
  | * c54a11c Merge branch 'master' into devel
  |/|   
  * | 42a126d (tag: release-0.a-000) Merge branch 'hotfix'
 /| |  
* | | 5a13312 (tag: hotfix-000, hotfix) Fixing bug #000
 \| |  
  * | 8ff7de1 (tag: release-0.a) Merge branch 'devel'
  |\|  
  | * 6984067 A devel commit.
  | * f1213d4 A devel commit.
  | * 3c075a4 A devel commit.
  |/  
  *   3ffe4fe (tag: release-base) First commit.
@martinda

Anyone knows how difficult this is to implement?

@datagrok
Owner
datagrok commented Jun 6, 2015

Shrug, it's not a dead thread until somebody gets around to implementing it. If/when I do get a chance to work on it I'll first re-read all the comments left here.

@expelledboy

+1

@arthur-bauer

I would love that. It would make it so much easier to follow a project's history. And it would help new users to understand the git branch concept in general.

@Numeri
Numeri commented Mar 3, 2016

Just a question, but I assumed the reason for the odd 'cross-leg' pattern was that every merge has the character \ and every branch (creation) has the character / before/after it (respectively). Am I just imagining a pattern where non exists?

If that is the case, I would still love something like your suggestions, that would disable that mode!

@porglezomp

@Numeri that looks true to me.

@tklepzig

+1

@Akuukis
Akuukis commented Jul 18, 2016

+1

@datagrok
Owner

@numeri, I think you're right. I'd like to be certain but the graph-drawing code in C, even with its embedded comments and associated docs, is still pretty opaque to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment