This is just me experimenting with an awesome project I just found: https://github.com/jlinoff/git2dot
Visualize a git repository using the graphviz dot tool.
Here is an example that shows the PNG file generated by test04 in the test directory.
- [ ] Remove summary?
- [ ] Use pydot to create the dot source
- [ ] Use bats for tests
- [ ] Update –range to be –git-extra-opts or –git-log-extra-opts
- [ ] GitHub Action that runs tests
- [ ] Single squash node
- [ ] Ability to create graphs with simple labels like A, B, C, D (instead of
git commit SHAs)
- Probably a
--msg
flag, which puts the commit message in the node instead of the git hash.
- Probably a
Just have one option for modifying git log: --git-log-opts
. There you can
pass =”–branches”=, =”–tags”=, =”–since”=, … anything that filters the
git log. You can do anything except for changing the log format.
Kinds of nodes:
- Commit Node - A commit.
- Abbrev:
cnode
- Default color: bisque (tan)
- Abbrev:
- Branch Node - A branch.
- Abbrev:
bnode
- Default color: light blue
- Abbrev:
- Merge Node - A Merge commit (a commit that has 2 or more parents).
- Abbrev:
mnode
- Default color: light red
- Abbrev:
- Squash Node - The end-points of a squashed commit chain. Squashed commit
chains do not have any branches, tags, change-ids or merges.
- Abrev:
snode
- Default color: dark red
- Abrev:
- Tag Node - A git tag.
- Abbrev:
tnode
- Default color: light purple
- Abbrev:
Commit, merge, and squash nodes show the short id, commit date, the subject (truncated to 32 characters) and the change-id (if it exists).
Kinds of edges:
- Parent Edge - From a node to its parent.
- Abbrev:
pedge
- Abbrev:
- Squash Edge - Between squash nodes.
- Abbrev:
sedge
- Abbrev:
- Branch Edge - From a branch node to a commit node.
- Abbrev:
bedge
- Abbrev:
- Tag Edge - From a tag node.
- Abbrev:
tedge
- Abbrev:
Here is an example run:
git2dot --png git.dot
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.
echo 'A' > README
git add README
git commit -m 'master - first'
echo 'B' >> README
git add README
git commit -m 'master - second' -m 'Change-Id: I001'
# 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'
echo 'B' >> README
git add README
git commit -m 'branchA - second' -m 'Change-Id: I001'
git checkout master
git checkout -b branchB
echo 'E' >> README
git add README
git commit -m 'branchB - first'
echo 'F' >> README
git add README
git commit -m 'branchB - second'
echo 'B' >> README
git add README
git commit -m 'branchB - third' -m 'Change-Id: I001'
echo 'H' >> README
git add README
git commit -m 'branchB - fourth' -m 'Change-Id: I002'
echo 'I' >> README
git add README
git commit -m 'branchB - fifth'
echo 'J' >> README
git add README
git commit -m 'branchB - sixth'
echo 'K' >> README
git add README
git commit -m 'branchB - seventh'
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
.
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
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.
<img src=”https://cloud.githubusercontent.com/assets/2991242/22431235/b585cf7e-e6c5-11e6-8f17-6b99847bfe51.png” width=”1100” alt=”example”>
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.
<img src=”https://cloud.githubusercontent.com/assets/2991242/22431252/c5077344-e6c5-11e6-95b0-54cd02d11aa2.png” width=”1100” alt=”example1”>
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 --crunch --squash --png --svg --html example1.html example1.dot
When you browse to http://localhost:8090/example2.html and you will see this.
<img src=”https://cloud.githubusercontent.com/assets/2991242/22431258/c89d7e7c-e6c5-11e6-826e-cf7450b9f125.png” width=”1100” alt=”example2”>
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
Create the graph without pruning.
git2dot \
--graph-label 'graph[label="example2 - compressed initial state"]' \
--crunch --squash --png --svg \
--html example2-2.html \
example2-2.dot
<img src=”https://cloud.githubusercontent.com/assets/2991242/22488086/0d34a592-e7c5-11e6-91d8-720f21e357f6.png” width=”1100” alt=”example2-2”>
Create the graph with pruning.
git2dot \
--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
<img src=”https://cloud.githubusercontent.com/assets/2991242/22488091/11ae8912-e7c5-11e6-9818-1c8e9c607182.png” width=”1100” alt=”example2-4”>
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.
<img src=”https://cloud.githubusercontent.com/assets/2991242/22603307/b1538d68-e9fb-11e6-859b-7c0387e9b972.png” width=”1100” alt=”dog food”>
It was generated with this command.
git2dot \
-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 -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.
<img src=”https://cloud.githubusercontent.com/assets/2991242/22622763/0b8e6ea8-eaf9-11e6-98b0-94869f7b0f30.png” width=”1100” alt=”dog food 1”>
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.
<img src=”https://cloud.githubusercontent.com/assets/2991242/22622765/193a16b0-eaf9-11e6-81ba-950ff26fc13b.png” width=”1100” alt=”dog food zoom”>
- 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.