Visualizing branch topology in Git

Viewed 680742

I'm playing with Git in isolation on my own machine, and I find it difficult to maintain a mental model of all my branches and commits. I know I can do a git log to see the commit history from where I am, but is there a way to see the entire branch topography, something like these ASCII maps that seem to be used everywhere for explaining branches?

      .-A---M---N---O---P
     /     /   /   /   /
    I     B   C   D   E
     \   /   /   /   /
      `-------------'

It just feels like someone coming along and trying to pick up my repository would have difficulty working out exactly what was going on.

I guess I'm influenced by AccuRev's stream browser...

33 Answers

Gitg is a great tool for Linux, similar to Gitx for OS X. Just run 'gitg' on the command line from somewhere inside your repository's tree structure (same with gitx).

For Mac users, check out (no pun intended) the free, open source tool GitUp.

I like the way the graphs are displayed. It's clearer than some of the other tools I've seen.

The project is at GitHub.

GitUp screenshot

I have this git log alias in ~/.gitconfig to view the graph history:

[alias]
l = log --all --graph --pretty=format:'%C(auto)%h%C(auto)%d %s %C(dim white)(%aN, %ar)'

With the alias in place, git l will show something like this:

enter image description here

In Git 2.12+ you can even customize the line colors of the graph using the log.graphColors configuration option.

As for the logs' format, it's similar to --oneline, with the addition of the author name (respecting .mailmap) and the relative author date. Note that the %C(auto) syntax, which tells Git to use the default colors for commit hash, etc. is supported in Git >= 1.8.3.

I'm using the Visual Studio Code editor, and I found myself very pleased with the Git Graph extension of it, made by mhutchie. (And I'm not alone, there are 1 million users of the extension!).

Enter image description here

If you happened to like the editor, then just go to the extension tab (the cubicles on the left middle) and type in "Git Graph" and install

Enter image description here

To use it, go to the Git control tab and press View Git Graph button

Enter image description here

I found it incredible that among the many answers it is not mentioned Gitviz, available for Window/Linux/Mac
In addition to providing a 2-D view of the branches and commit, it listens your git commands and it modify by itself the graph.

enter image description here

The most rated answers are showing git log commands as favorite solutions.

If you need a tablelike, say column-like output, you can use your awesome git log commands with slight modifications and some limitations with the .gitconfig alias.tably snippet below.

Modifications:

  • you have to use %><(<N>[,ltrunc|mtrunc|trunc]) before every commit placeholder
  • add a unique delimiter as column separator
  • add --color option for colored output

Limitations:

  • you can place the CPU graph at every column as long as you do not use non-empty newlines %n...

  • the last commit placeholder of any newline can be used without %><(<N>[,trunc])

  • if extra characters are needed for decoration like (committer: , < and >) in

    ...%C(dim white)(committer: %cn% <%ce>)%C(reset)...

    to get a tablelike output they must be written directly before and after the commit placeholder

    ...%C(dim white)%<(25,trunc)(committer: %cn%<(25,trunc) <%ce>)%C(reset)...

  • if the --format=format: option is not the last one close it with %C(reset) as mostly done

  • compared to normal git log output this one is slow, but nice

Example taken from this site:

thompson1     = log --all --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(auto)%d%C(reset)'

will be with ^ as delimiter and without added characters

thompson1-new = log --all --graph --color --abbrev-commit --decorate --format=format:'^%C(bold blue)%<(7,trunc)%h%C(reset)^%C(bold green)%<(21,trunc)%ar%C(reset)^%C(white)%<(40,trunc)%s%C(reset)^%C(dim white)%<(25,trunc)%an%C(reset)^%C(auto)%d%C(reset)'

which compares like

Enter image description here

Enter image description here

Or with moving the graph to column 5:

Enter image description here

To achieve this, add the following to your .gitconfig file and call your log alias with git tably YourLogAlias:

[color "decorate"]
    HEAD = bold blink italic 196
    branch = 214
    tag = bold 222

[alias]

    # delimiter used as column seperator
    delim = ^
    # example thompson1
    thompson1     = log --all --graph         --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(auto)%d%C(reset)'
    # modified thompson1 example
    thompson1-new = log --all --graph --color --abbrev-commit --decorate --format=format:'^%C(bold blue)%<(7,trunc)%h%C(reset)^%C(bold green)%<(21,trunc)%ar%C(reset)^%C(white)%<(40,trunc)%s%C(reset)^%C(dim white)%<(25,trunc)%an%C(reset)^%C(auto)%d%C(reset)'
    # set a column for the graph
    thompson1-new-col = 1

tably     = !bash -c '"                                                                                                              \
              declare -A col_length;                                                                                                 \
              delim=$(git config alias.delim);                                                                                       \
              git_log_cmd=$(git config alias.$1);                                                                                    \
              git_tre_col=${2:-$(git config alias.$1-col)};                                                                          \
                                                                                                                                     \
              i=0;                                                                                                                   \
              n=0;                                                                                                                   \
              while IFS= read -r line; do                                                                                            \
                ((n++));                                                                                                             \
                while read -d\"$delim\" -r col_info;do                                                                               \
                  ((i++));                                                                                                           \
                  [[ -z \"$col_info\" ]] && col_length[\"$n:$i\"]=${col_length[\"${last[$i]:-1}:$i\"]} && ((i--)) && continue;       \
                  [[ $i -gt ${i_max:-0} ]] && i_max=$i;                                                                              \
                  col_length[\"$n:$i\"]=$(grep -Eo \"\\([0-9]*,[lm]*trunc\\)\" <<< \"$col_info\" | grep -Eo \"[0-9]*\" | head -n 1); \
                  [[ -n \"${col_length[\"$n:$i\"]}\" ]] && last[$i]=$n;                                                              \
                  chars_extra=$(grep -Eo \"\\trunc\\).*\" <<< \"$col_info\");                                                        \
                  chars_extra=${chars_extra#trunc)};                                                                                 \
                  chars_begin=${chars_extra%%\\%*};                                                                                  \
                  chars_extra=${chars_extra#*\\%};                                                                                   \
                  case \" ad aD ae aE ai aI al aL an aN ar as at b B cd cD ce cE ci cI cl cL cn cN cr                                \
                          cs ct d D e f G? gd gD ge gE GF GG GK gn gN GP gs GS GT h H N p P s S t T \" in                            \
                   *\" ${chars_extra:0:2} \"*)                                                                                       \
                     chars_extra=${chars_extra:2};                                                                                   \
                     chars_after=${chars_extra%%\\%*};                                                                               \
                     ;;                                                                                                              \
                   *\" ${chars_extra:0:1} \"*)                                                                                       \
                     chars_extra=${chars_extra:1};                                                                                   \
                     chars_after=${chars_extra%%\\%*};                                                                               \
                     ;;                                                                                                              \
                   *)                                                                                                                \
                     echo \"No Placeholder found. Probably no tablelike output.\";                                                   \
                     continue;                                                                                                       \
                     ;;                                                                                                              \
                  esac ;                                                                                                             \
                  if [[ -n \"$chars_begin$chars_after\" ]];then                                                                      \
                    len_extra=$(echo \"$chars_begin$chars_after\" | wc -m);                                                          \
                    col_length["$n:$i"]=$((${col_length["$n:$i"]}+$len_extra-1));                                                    \
                  fi;                                                                                                                \
                                                                                                                                     \
                done <<< \"${line#*=format:}$delim\";                                                                                \
                i=1;                                                                                                                 \
              done <<< \"$(echo -e \"${git_log_cmd//\\%n/\\\\n}\")\";                                                                \
                                                                                                                                     \
              while IFS= read -r graph;do                                                                                            \
                chars_count=$(sed -nl1000 \"l\" <<< \"$graph\" | grep -Eo \"\\\\\\\\\\\\\\\\|\\||\\/|\\ |\\*|_\" | wc -l);           \
                [[ ${chars_count:-0} -gt ${col_length["1:1"]:-0} ]] && col_length["1:1"]=$chars_count;                               \
              done < <([[ -n \"$(grep -F graph <<< \"$git_log_cmd\")\" ]] && git log --all --graph --pretty=format:\" \" && echo);   \
                                                                                                                                     \
              l=0;                                                                                                                   \
              while IFS= read -r line;do                                                                                             \
                c=0;                                                                                                                 \
                ((l++));                                                                                                             \
                [[ $l -gt $n ]] && l=1;                                                                                              \
                while IFS= read -d\"$delim\" -r col_content;do                                                                       \
                  ((c++));                                                                                                           \
                  if [[ $c -eq 1 ]];then                                                                                             \
                    [[ -n \"$(grep -F \"*\" <<< \"$col_content\")\" ]] || l=2;                                                       \
                    chars=$(sed -nl1000 \"l\" <<< \"$col_content\" | grep -Eo \"\\\\\\\\\\\\\\\\|\\||\\/|\\ |\\*|_\" | wc -l);       \
                    whitespaces=$((${col_length["1:1"]}-$chars));                                                                    \
                    whitespaces=$(seq -s\" \" $whitespaces|tr -d \"[:digit:]\");                                                     \
                    col_content[1]=\"${col_content[1]}$col_content$whitespaces\n\";                                                  \
                  else                                                                                                               \
                    col_content[$c]=\"${col_content[$c]}$(printf \"%-${col_length[\"$l:$c\"]}s\" \"${col_content:-\"\"}\")\n\";      \
                  fi;                                                                                                                \
                done <<< \"$line$delim\";                                                                                            \
                for ((k=$c+1;k<=$i_max;k++));do                                                                                      \
                  empty_content=\"$(printf \"%-${col_length[\"$l:$k\"]:-${col_length[\"${last[$k]:-1}:$k\"]:-0}}s\" \"\")\";         \
                  col_content[$k]=\"${col_content[$k]}$empty_content\n\";                                                            \
                done;                                                                                                                \
              done < <(git $1 && echo);                                                                                              \
                                                                                                                                     \
              while read col_num;do                                                                                                  \
                if [[ -z \"$cont_all\" ]];then                                                                                       \
                  cont_all=${col_content[$col_num]};                                                                                 \
                else                                                                                                                 \
                  cont_all=$(paste -d\" \" <(echo -e \"$cont_all\") <(echo -e \"${col_content[$col_num]}\"));                        \
                fi;                                                                                                                  \
              done <<< $(seq 2 1 ${git_tre_col:-1};seq 1;seq $((${git_tre_col:-1}+1)) 1 $i_max);                                     \
              echo -e \"$cont_all\";                                                                                                 \
              "' "git-tably"

This is more or less only a part of my answer https://stackoverflow.com/a/61487052/8006273 where you can find deeper explanations, but nicely fits to this question here too.

If there are problems with your git log commands leave a comment.

I want to share my compact preset for git log command:
(green is my default console color)
Preset preview

It's designed to be as compact and table-like as possible (without adding any excess spaces) while still being informative and easy to read. This is basically a compact version of medium format Git uses by default.

Features:

  • Fixed item positions;
  • Default colors of commit hash and ref names;
  • Commit author date is in local time zone;
  • Commit message is wrapped at 128 characters and indented;
  • Extended commit message is also shown (if any) with any trailing newlines removed.

You can add it into your config file using these commands:
(note that they will change the date format for all git log formats!)

$ git config --global log.date 'format-local:%d %b %Y %H:%M'
$ git config --global pretty.compact '%C(auto)%h %C(cyan)%<(17,trunc)%an%C(auto) %D%n        %C(cyan)%ad%C(auto) %w(128,0,26)%s%w(0,0,26)%+b%-(trailers:key=FAKE)'

... and then use it like so (with --graph, --all or any other options):

$ git log --graph --pretty=compact

If you'd also like to make it default you can do so with this command:

$ git config --global format.pretty compact

Or if you like aliases:

$ git config --global alias.logc "log --date=format-local:'%d %b %Y %H:%M' --pretty='%C(auto)%h %C(cyan)%<(17,trunc)%an%C(auto) %D%n        %C(cyan)%ad%C(auto) %w(128,0,26)%s%w(0,0,26)%+b%-(trailers:key=FAKE)'"

If you'd like to make any changes, see the PRETTY FORMATS section of git log reference.

Git built-in tools (no add-ons) with date-time formatting

None of the existing answers show how to change the date-time format using the built-in git log tooling. Since the docs are a little obtuse with the feature, I've added the two aliases I use for this as example.

git tree – Timestamped log of all commits

# Tools for analyzing the merge history of a repo using tree-like graphics
[alias]
    tree = log --no-show-signature --graph --date=format-local:%H:%M:%S --all \
        --pretty="'%C(#ffe97b ul)%h%C(reset) %C(#568ea6)%cs %C(#305f72)%cd%C(reset)%C(auto)%d%C(reset) %s %C(yellow)(%C(reset)%C(#1abc9c)%an%C(reset)%C(yellow),%C(reset) %C(#007055)%cr%C(reset)%C(yellow))%C(reset)'"

git tree

git tree.branches – Timestamped log of all branch/tag commits

# Some refinements to normal 'git tree' output for alternative perspectives.
[alias "tree"]
    branches = tree --simplify-by-decoration

git tree.branches

Color code

Spec Color Style
Commit ID Yellow Underline
Commit date Dark Blue
Commit time Light Blue
Commit message White
Commit Author Green
Commit relative date Dark Green
Remote branches Red
Local branches Purple
Tags Pink Underline

Git's official site enlisted some third-party platform specific GUI tools. Open Git GUI Tools for Linux Platform.

I have used gitg and GitKraken for the Linux platform. Both are good for understanding the commit tree.

For those of you that work in VIM anyway, gv.vim may be interesting. That's what I use directly from within VIM that also enables to conveniently browse through the commit history and check the specific changes.

This is the screenshot directly from gv.vim's Github page:

enter image description here

The specific style depends on the utilized color theme.

Related