Visualize a git repository using the graphviz dot tool optionally with pan and zoom.
It is useful for understanding how git works in detail. You can use it to analyze repositories before and after operations like merge and rebase to really get a feeling for what happens. It can also be used for looking at subsets of history on live repositories.
I have created a docker image to allow git2dot to be used without installing graphviz on the target system. See https://github.com/jlinoff/docker-images/tree/master/git2dot for details.
Here is an example that shows the PNG file generated by test04 in the test directory.
Here is a quick rundown of what you are seeing:
- The bisque (tan) nodes are commits. Each commit has the short id, the commit date, the subject (truncated to 32 characters) and the change-id (if it exists). The fields are the same for merged and squashed nodes as well.
- The light red nodes are merged nodes. They are commits that resulted from a merge (they have 2 or more children).
- The dark red nodes are squashed nodes. They are the end-points of squashed commit chains. Squashed commit chains do not have any branches, tags, change-ids or merges. They are just a long chain of work. If you turn off squashing, you will see 6 additional commit nodes and the two read squashed nodes will disappear.
- The bluish boxes underneath commit and merge nodes are the branches associated with the commit. There is one box for each branch.
- The light purple boxes above the commit and merge nodes are the tags associated with the commit. There is one box for each tag.
- The arrows on the edges show the back (parent) pointer relationships from the repo. Git does not have child references.
- The yellow box at the bottom is an optional graph label with custom text.
It works by running over the .git repository in the current directory and generating a commit relationship DAG that has both parent and child relationships.
The generated graph shows commits, tags and branches as nodes. Commits are further broken down into simple commits and merged commits where merged commits are commits with 2 or more children. There is an additional option that allows you to squash long chains of simple commits with no branch or tag data.
It has a number of different options for customizing the nodes, using your own custom git command to generate the data, keeping the generated data for re-use, customizing dot directly and generating graphical output like PNG, SVG or even HTML files.
Here is an example run:
$ cd SANDBOX
$ git2dot.py --png git.dot
$ open -a Preview git.dot.png # on Mac OS X
$ display git.dot.png # linux
If you want to create a simple HTML page that allows panning and zooming of the generated SVG then use the --html option like this.
$ cd SANDBOX
$ git2dot.py --svg --html ~/web/index.html ~/web/git.dot
$ $ ls ~/web
git.dot git.dot.svg git.html svg-pan-zoom.js
$ cd ~/web
$ python -m SimpleHTTPServer 8090 # start server
$ # Browse to http://localhost:8090/git.dot.svg
It assumes that existence of svg-pan-zoom.js from the https://github.com/ariutta/svg-pan-zoom package.
The output is pretty customizable. For example, to add the subject and
commit date to the commit node names use -l '%s|%cr'
. The items come
from the git format placeholders or variables that you define using
-D
. The | separator is used to define the end of a line. The maximum
width of each line can be specified by -w
. Variables are defined by -D
and come from text in the commit message. See -D
for more details.
You can customize the attributes of the different types of nodes and edges in the graph using the -?node and -?edge attributes. The table below briefly describes the different node types:
Node Type | Brief Description |
---|---|
bedge | Edge connecting to a bnode. |
bnode | Branch node associated with a commit. |
cnode | Commit node (simple commit node). |
mnode | Merge node. A commit node with multiple children. |
snode | Squashed node. End point of a sequence of squashed nodes. |
tedge | Edge connecting to a tnode. |
tnode | Tag node associated with a commit. |
If you have long chains of single commits use the --squash
option to
squash out the middle ones. That is generally helpful for filtering
out extraneous commit details for moderately sized repos.
If you find that dot is placing your bnode and tnode nodes in odd
places, use the --crunch
option to collapse the bnode nodes into
a single node and the tnodes into a single node for each commit.
If you want to limit the analysis to commits between certain dates,
use the --since
and --until
options.
If you want to limit the analysis to commits in a certain range use
the --range
option.
If you want to limit the analysis to a small set of branches or tags
you can use the --choose-branch
and --choose-tag
options. These options
prune the graph so that only parents of commits with the choose branch
or tag ids are included in the graph. This gives you more detail
controlled that the git options allowed in the --range command. It
is very useful for determining where branches occurred.
You can choose to keep the git output to re-use multiple times with
different display options or to share by specifying the -k
(--keep
)
option.
Use the -h
option to get detailed information about the available options.
The following example shows how to use git2dot by creating a git repository from scratch and then running the tool to create images.
First we create a repository with a bunch of commits and branches.
git init
echo 'A' >README
git add README
git commit -m 'master - first'
sleep 1
echo 'B' >>README
git add README
git commit -m 'master - second' -m 'Change-Id: I001'
sleep 1
# tag the basis for all of the branches
git tag -a 'v1.0' -m 'Initial version.'
git tag -a 'v1.0a' -m 'Another version.'
git checkout -b branchX1
git checkout master
git checkout -b branchX2
git checkout master
git checkout -b branchA
echo 'C' >> README
git add README
git commit -m 'branchA - first'
sleep 1
echo 'B' >> README
git add README
git commit -m 'branchA - second' -m 'Change-Id: I001'
sleep 1
git checkout master
git checkout -b branchB
echo 'E' >> README
git add README
git commit -m 'branchB - first'
sleep 1
echo 'F' >> README
git add README
git commit -m 'branchB - second'
sleep 1
echo 'B' >> README
git add README
git commit -m 'branchB - third' -m 'Change-Id: I001'
sleep 1
echo 'H' >> README
git add README
git commit -m 'branchB - fourth' -m 'Change-Id: I002'
sleep 1
echo 'I' >> README
git add README
git commit -m 'branchB - fifth'
sleep 1
echo 'J' >> README
git add README
git commit -m 'branchB - sixth'
sleep 1
echo 'K' >> README
git add README
git commit -m 'branchB - seventh'
sleep 1
git checkout master
echo 'L' >> README
git add README
git commit -m 'master - third'
You can verify the repo structure using something like git log
.
$ git log --graph --oneline --decorate --all
* da0165b (HEAD -> master) master - third
| * 8e3cf50 (branchB) branchB - seventh
| * e0420c1 branchB - sixth
| * f51497b branchB - fifth
| * cee652e branchB - fourth
| * 2fc95e6 branchB - third
| * 9c654d8 branchB - second
| * 33fbc07 branchB - first
|/
| * 20ea3d2 (branchA) branchA - second
| * 71a0d0c branchA - first
|/
* ecdc7dc (tag: v1.0a, tag: v1.0, branchX2, branchX1) master - second
* c8ae444 master - first
Now run the git2dot tool to generate PNG, HTML and SVG files.
$ git2dot.py --png --svg --html example.html example.dot
$ ls -1 example.*
example.dot
example.dot.png
example.dot.svg
example.html
You can now view the PNG and SVG files using local tools.
$ open -a Preview example.dot.png # on Mac
$ display example.dot.png # on Linux
To view the generated SVG file with pan and zoom you must download the svg-pan-zoom.min.js file from https://github.com/ariutta/svg-pan-zoom and copy into the current directory.
$ cp ~/work/svg-pan-zoom-3.4.1/dist/svg-pan-zoom.min.js .
$ ls -1 example* svg*
example.dot
example.dot.png
example.dot.svg
example.html
svg-pan-zoom.min.js
Now you need to start a server.
$ python -m SimpleHTTPServer 8090
After that you can browse to http://localhost:8090/example.html and you will see this.
As you can see, there is a long chain of commits, to run it again using the --squash
option.
$ git2dot.py --squash --png --svg --html example1.html example1.dot
And browse to http://localhost:8090/example1.html and you will see this.
Which is a cleaner view of the overall structure.
You will also note that there are two branches and two tags on ecdc7dc. They can be collapsed using the --crunch
option like this.
$ git2dot.py --crunch --squash --png --svg --html example1.html example1.dot
When you browse to http://localhost:8090/example2.html and you will see this.
For such a small graph the crunch operation doesn't really make things simpler but for larger graphs where dot may move the branch and tag information around, it can be a much cleaner view.
There are two more options you will want to think about for making large graphs
more readable: --choose-branch
and --choose-tag
. As described earlier,
they prune the graph so that it only considers the parent chains of the
specified branches or tags. This can be very useful to determining where
branches occurred.
This example shows how it works.
First you create a repository like this.
git init
echo 'A' >example2.txt
git add example2.txt
git commit -m 'master - first'
sleep 1
echo 'B' >>example2.txt
git add example2.txt
git commit -m 'master - second'
sleep 1
# tag the basis for all of the branches
git tag -a 'v1.0' -m 'Initial version.'
git tag -a 'v1.0a' -m 'Another version.'
git checkout -b branchX1
git checkout master
git checkout -b branchX2
git checkout master
git checkout -b branchA
echo 'C' >> example2.txt
git add example2.txt
git commit -m 'branchA - first'
sleep 1
echo 'D' >> example2.txt
git add example2.txt
git commit -m 'branchA - second'
sleep 1
echo 'E' >> example2.txt
git add example2.txt
git commit -m 'branchA - third'
sleep 1
echo 'F' >> example2.txt
git add example2.txt
git commit -m 'branchA - fourth'
sleep 1
git checkout master
git checkout -b branchB
echo 'G' >> example2.txt
git add example2.txt
git commit -m 'branchB - first'
sleep 1
echo 'H' >> example2.txt
git add example2.txt
git commit -m 'branchB - second'
sleep 1
echo 'I' >> example2.txt
git add example2.txt
git commit -m 'branchB - third'
sleep 1
echo 'J' >> example2.txt
git add example2.txt
git commit -m 'branchB - fourth'
sleep 1
git tag -a 'v2.0a' -m 'Initial version.'
echo 'K' >> example2.txt
git add example2.txt
git commit -m 'branchB - fifth'
sleep 1
echo 'L' >> example2.txt
git add example2.txt
git commit -m 'branchB - sixth'
sleep 1
echo 'M' >> example2.txt
git add example2.txt
git commit -m 'branchB - seventh'
sleep 1
git checkout master
echo 'N' >> example2.txt
git add example2.txt
git commit -m 'master - third'
sleep 1
echo 'O' >> example2.txt
git add example2.txt
git commit -m 'master - fourth'
You can confirm its layout like this.
$ $ git log --graph --oneline --decorate --all --topo-order
* e4bb699 (HEAD -> master, origin/master, origin/HEAD) Add --topo-order to the default range
* 01bb6de Update comments
* c0bf31e Update comments
* f6f32ac (tag: v0.4) Update to describe --choose-* functionality
* 81fc41c v0.4 - added --choose-* support
* c50cded (tag: v0.3) Merge branch 'master' of https://github.com/jlinoff/git2dot
|\
| * 3c52eae Update README.md
* | be89609 Add example
|/
* 680b2e5 Update documentation
* 0b7fed3 Update README.txt
* 47f1430 Initial load
* b4c73c8 Update README.md
* 54632ac Change bedge/tedge color defaults
* 0136d78 Add support for --crunch
* 10eaf83 Update README.md
* 736b75a Fix bug in --align-by-date handling
* 4fac1b8 Fix bug in --align-by-date handling
* fd20bac Fix bug in --align-by-date handling
* e15199f Update README.md
* a4a6fa8 Initial load
* da4e1d3 Update README.md
* ba50fcf Update README.md
* 2a8038c Update README.md
* 800700f Update README.md
* a3a4ae0 Initial commit
Create the graph without pruning.
$ ../git2dot.py --graph-label 'graph[label="example2 - compressed initial state"]' --crunch --squash --png --svg --html example2-2.html example2-2.dot
Create the graph with pruning.
$ ../git2dot.py --graph-label 'graph[label="example2 - compressed pruned state"]' --choose-branch 'branchA' --choose-tag 'tag: v2.0a' --crunch --squash --png --svg --html example2-4.html example2-4.dot
As you can see, branchB has been completely removed in the second one.
Here is the generated image of the git2dot development tree for v0.6.
It was generated with this command.
$ ./git2dot.py -s -c --png --graph-label 'graph[label="git2dot v0.6", fontsize="18"]' git.dot
Here is how I created a pannable and zoomable version of the "eat your own dog food" graph.
First I created the HTML and SVG files in an example directory. I also created a PNG file for local testing. Note that I ran the git2dot.py
command in the git2dot repo and directed the output to the example directory.
$ mkdir ~/work/git2dot-zoom-example
$ cd ~/work/git2dot # the repo
$ git2dot.py -s -c -L 'graph[label="\ngit2dot v0.6", fontsize="24"]' --png --svg --html ~/work/git2dot-zoom-example/git.html --choose-tag 'tag: v0.6' ~/work/git2dot-zoom-example/git.dot
$ open -a Preview ~/work/git2dot-zoom-example/git.png
I then copied over the svg-pan-zoom.min.js file. Without it, panning and zooming cannot work.
$ cd ~/work/git2dot-zoom-example
$ cp ~/work/svg-pan-zoom/dist/svg-pan-zoom.min.js .
Once the files were in place, I started a simple HTTP server in the same directory that I created the HTML and SVG files.
$ cd ~/work/git2dot-zoom-example
$ python -m SimpleHTTPServer 8081
I then navigated to http://localhost:8081/git.html
in a browser and saw this.
After that I panned to the left (left-mouse-button-down and drag) and zoomed in using the mousewheel to see the most recent tag.
- For large graphs consider using the
--squash
option. - For large graphs consider using the svg-pan-zoom zoom() function when the data is loaded to make the nodes visible.
- For graphs that have multiple branches and tags on the same commits consider using the
--crunch
option. - If you only want to see the combined history of a few branches or tags (like release branches) consider using the
--choose-branch
and--choose-tag
options to prune the graph. - Use the
--since
option if you don't care about ancient history. - The
--graph-label
option can be useful and can be very simple:--graph-label 'graph[label="MY LABEL"]'
. - Read the program help:
-h
or--help
, there is a lot of useful information there.
The generated dot file has summary fields at the end that can be useful for post processing.
The fields are written as dot comments like this.
// summary:num_graph_commit_nodes 5
// summary:num_graph_merge_nodes 1
// summary:num_graph_squash_nodes 2
// summary:total_commits 12
// summary:total_graph_commit_nodes 8
They are described in the table below.
Field | Description |
---|---|
// summary:num_graph_commit_nodes INT |
The total number of simple commit nodes in the graph. |
// summary:num_graph_merge_nodes INT |
The total nummber of merge commit nodes in the graph. |
// summary:num_graph_squash_nodes INT |
The total number of squash commit nodes in the graph. |
// summary:total_commits INT |
The total number of commits (incuding merges) with no squashing. |
// summary:total_graph_commit_nodes INT |
The number of actual commit nodes in the graph. |
Note that total_commits and total_graph_commit_nodes will be the same if squashing is not specified.