Extreme Tech Seminar

nerds learning together

Meeting: Collapsing Git Graphs

This meeting, we saw how commits in a git repository form a distributed acyclic graph that can be visualized with graphviz.

The article discussed was Drawing Git Graphs with Graphviz and Org-Mode.

Slides

Exercise

After going through the article, we attempted to build out the history collapsing feature proposed in it.

Example graph

The following graph mimics the git repo built in the blog post:

(setq example-graph
      (git-graph/group-topo
       (list (git-graph/make-node 1 '(7 2) '((label . master)))
             (git-graph/make-node 2 '(5 3) '((label . develop)))
             (git-graph/make-node 3 '(4) '((label . feature-1)))
             (git-graph/make-node 4 '(5))
             (git-graph/make-node 5 '(6))
             (git-graph/make-node 6 '(7))
             (git-graph/make-node 7 '(8))
             (git-graph/make-node 8 nil))))

(git-graph/to-graphviz-pretty
 "autogroups"
 example-graph)
autogroups 1 master 2 develop 2->1 3 feature-1 3->2 4 4->3 5 5->2 5->4 6 6->5 7 7->1 7->6 8 8->7

Filtering out branches

We first create a basic filtering function:

(defun git-graph/filter (predicate graph)
  (-filter predicate graph))

Then, we'll filter out the feature branch. Since graph nodes are grouped by id, not label, we filter out nodes grouped under the feature branch's id, which is 3.

(git-graph/to-graphviz-pretty
 "git"
 (git-graph/filter
  (lambda (node)
    (not (eq 3 (git-graph/node-group node))))
  example-graph))

To avoid leaving a dangling node, the graph generation functions needed to be updated to not draw an edge to a parent not present in the graph:

@@ -120,7 +120,9 @@ nodes are defined first, followed by the edges between them.
        (-map #'git-graph/to-graphviz-node nodes)
        "\n")
        (string-join
-        (-uniq (-flatten (-map #'git-graph/to-graphviz-edges nodes)))
+        (-uniq (-flatten (-map
+                          (lambda (node) (git-graph/to-graphviz-edges node nodes))
+                          nodes)))
         "\n")
         "}")
      "\n"))
@@ -165,11 +167,13 @@ parents.

 #+name: git-graph/to-graphviz-edges
 #+begin_src emacs-lisp
-  (defun git-graph/to-graphviz-edges (node)
+  (defun git-graph/to-graphviz-edges (node &optional nodelist)
     (let ((node-id (git-graph/node-id node))
-          (parents (git-graph/node-parents node)))
+          (parents (git-graph/node-parents node))
+          (node-ids (-map 'git-graph/node-id nodelist)))
       (-map (lambda (parent)
-              (git-graph/to-graphviz-edge node-id parent))
+              (unless (and nodelist (not (member parent node-ids)))
+                (git-graph/to-graphviz-edge node-id parent)))
             parents)))

   (defun git-graph/to-graphviz-edge (from to)
git 1 master 2 develop 2->1 5 5->2 6 6->5 7 7->1 7->6 8 8->7

Filtering out intermediate commits

The next goal is to filter out unnecessary commits, defined as any commit that isn't a branch or merge point. Given the graph above, the intent is to filter out the initial commit, and the middle commit on the develop branch.

We'll filter out single-parent nodes that are not branch points (the first node in a new group):

(defun git-graph/intermediate-commit-p (node)
  (and (> 2 (length (git-graph/node-parents node)))
       (not (eq (git-graph/node-id node)
                (git-graph/node-group node)))))

(git-graph/to-graphviz-pretty
 "git"
 (git-graph/filter
  (lambda (node)
    (not (git-graph/intermediate-commit-p node)))
  (git-graph/filter
   (lambda (node)
     (not (eq 3 (git-graph/node-group node))))
   example-graph)))
git 1 master 2 develop 2->1

It turns out our criteria were too broad; we've lost the merge-base and merge-base child commit that started the develop branch. Unfortunately, we ran out of time before getting any further. If anyone would like to take a crack at this, feel free to post your solutions to the google group.