2016-12-18 01:59:20 +00:00
|
|
|
#!/usr/bin/gawk -f
|
2016-10-30 23:17:54 +00:00
|
|
|
#
|
|
|
|
# Grawkit — The Awksome Git Graph Generator.
|
|
|
|
# ==========================================
|
|
|
|
#
|
|
|
|
# Grawkit is a tool that helps build SVG graphs from Git command-line descriptions.
|
|
|
|
#
|
|
|
|
# This documentation is built using Markdown syntax, and can be parsed out by
|
|
|
|
# running `make doc` in the project root. Please check the project's README file
|
|
|
|
# for additional information.
|
|
|
|
#
|
|
|
|
# Built-in Functions
|
|
|
|
# ------------------
|
|
|
|
#
|
|
|
|
# This section contains global helper functions, used across different rules, as
|
|
|
|
# defined in the next section below.
|
|
|
|
|
2016-12-18 01:59:20 +00:00
|
|
|
# > Function `addbranch` adds a new, empty branch to the internal list of branches
|
|
|
|
# > to render.
|
|
|
|
function addbranch(name) {
|
|
|
|
branches[len["branches"],"name"] = name
|
|
|
|
branches[len["branches"],"refs"] = ""
|
|
|
|
branches[len["branches"],"merges"] = state["branch"] "|" state["HEAD"]
|
|
|
|
|
|
|
|
len["branches"] += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# > Function `addcommit` adds a new commit, with a specific type and message, to
|
|
|
|
# > the internal list of commits to render.
|
|
|
|
function addcommit(type, message) {
|
|
|
|
# Add commit information.
|
|
|
|
commits[len["commits"],"branch"] = state["branch"]
|
|
|
|
commits[len["commits"],"type"] = type
|
|
|
|
commits[len["commits"],"message"] = msg
|
|
|
|
|
|
|
|
# Update commit references.
|
|
|
|
if (branches[state["branch"],"refs"] == "") {
|
|
|
|
branches[state["branch"],"refs"] = len["commits"]
|
|
|
|
} else {
|
|
|
|
branches[state["branch"],"refs"] = branches[state["branch"],"refs"] "," len["commits"]
|
|
|
|
}
|
|
|
|
|
|
|
|
state["HEAD"] = len["commits"]
|
|
|
|
len["commits"] += 1
|
2016-10-30 23:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# > Function `branch` renders pre-defined branch under a specific name to its
|
|
|
|
# > SVG representation.
|
2016-12-18 01:59:20 +00:00
|
|
|
function branch(idx, _, buf, tmp, refs, merge, m, i, bspc, cspc) {
|
|
|
|
# Do not render branch with no commits.
|
|
|
|
if (branches[idx,"refs"] == "") {
|
|
|
|
return
|
2016-10-30 23:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Get commit refs.
|
2016-12-18 01:59:20 +00:00
|
|
|
split(branches[idx,"refs"], refs, ",")
|
|
|
|
bspc = idx * style["branch/spacing"]
|
|
|
|
|
|
|
|
# Print branch root element.
|
|
|
|
buf = sprintf(svg["g"], branches[idx,"name"])
|
|
|
|
|
|
|
|
# Add merge paths for branch, if any.
|
|
|
|
split(branches[idx,"merges"], merge, ",")
|
|
|
|
for (i in merge) {
|
|
|
|
split(merge[i], m, "|")
|
|
|
|
|
|
|
|
# Add starting point to previous branch ending point.
|
|
|
|
tmp = "M" m[1] * style["branch/spacing"] "," m[2] * style["commit/spacing"]
|
|
|
|
|
|
|
|
# Add Bezier curve leading to current branch starting point.
|
|
|
|
tmp = tmp " C" m[1] * style["branch/spacing"] "," m[2] * style["commit/spacing"]
|
|
|
|
tmp = tmp " " bspc "," m[2] * style["commit/spacing"]
|
|
|
|
tmp = tmp " " bspc "," refs[1] * style["commit/spacing"]
|
|
|
|
|
|
|
|
# Draw the path.
|
|
|
|
buf = buf "\n" sprintf(svg["path"], tmp)
|
|
|
|
}
|
2016-10-30 23:17:54 +00:00
|
|
|
|
|
|
|
# Add path for branch.
|
2016-11-01 23:27:58 +00:00
|
|
|
tmp = "M" bspc "," refs[1] * style["commit/spacing"]
|
2016-12-18 01:59:20 +00:00
|
|
|
tmp = tmp " L" bspc "," refs[length(refs)] * style["commit/spacing"]
|
2016-10-30 23:17:54 +00:00
|
|
|
|
|
|
|
# Print path.
|
|
|
|
buf = buf "\n" sprintf(svg["path"], tmp)
|
|
|
|
|
|
|
|
# Add commits on path.
|
2016-12-18 01:59:20 +00:00
|
|
|
for (i in refs) {
|
|
|
|
cspc = refs[i] * style["commit/spacing"]
|
2016-10-30 23:17:54 +00:00
|
|
|
|
2016-11-01 23:27:58 +00:00
|
|
|
tmp = sprintf(svg["circle"], bspc, cspc, style["commit/radius"])
|
2016-10-30 23:17:54 +00:00
|
|
|
buf = buf "\n" tmp
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = buf "\n" svg["/g"]
|
|
|
|
return buf
|
|
|
|
}
|
|
|
|
|
|
|
|
# Global Declarations
|
|
|
|
# -------------------
|
|
|
|
#
|
|
|
|
# This block contains logic for initializing global variables used across Grawkit.
|
|
|
|
|
|
|
|
BEGIN {
|
2016-12-18 01:59:20 +00:00
|
|
|
# Errors.
|
|
|
|
error["branch/no-name"] = "Empty name for `git branch`, line %d\n"
|
|
|
|
error["branch/duplicate"] = "Unable to create duplicate branch '%s', line %d\n"
|
|
|
|
error["checkout/no-branch"] = "No branch with name '%s', line %d\n"
|
|
|
|
|
|
|
|
error["checkout/no-name"] = "Empty name for `git checkout`, line %d\n"
|
|
|
|
error["merge/no-name"] = "Empty name for `git merge`, line %d\n"
|
|
|
|
|
2016-10-30 23:17:54 +00:00
|
|
|
# Rule matching.
|
2016-12-18 01:59:20 +00:00
|
|
|
rule["commit"] = "^git commit"
|
|
|
|
rule["commit/message"] = "(--message|-m)[ ]+['|\"]([^'\"]+)['|\"]"
|
|
|
|
|
|
|
|
rule["branch"] = "^git branch"
|
|
|
|
rule["branch/name"] = rule["branch"] "[ ]*([^ ]+)"
|
|
|
|
|
|
|
|
rule["checkout"] = "^git checkout"
|
|
|
|
rule["checkout/name"] = rule["checkout"] "[ ]*([^ ]+)"
|
|
|
|
|
|
|
|
rule["merge"] = "^git merge"
|
|
|
|
rule["merge/name"] = rule["merge"] "[ ]*([^ ]+)"
|
2016-10-30 23:17:54 +00:00
|
|
|
|
|
|
|
# Style definitions.
|
|
|
|
style["branch/spacing"] = "50"
|
|
|
|
style["branch/fill"] = "none"
|
|
|
|
style["branch/stroke"] = "#333"
|
|
|
|
style["branch/stroke-width"] = "10"
|
|
|
|
|
2016-11-01 23:27:58 +00:00
|
|
|
style["commit/spacing"] = "50"
|
2016-10-30 23:17:54 +00:00
|
|
|
style["commit/fill"] = "#fff"
|
|
|
|
style["commit/stroke"] = style["branch/stroke"]
|
|
|
|
style["commit/stroke-width"] = style["branch/stroke-width"] / 2
|
|
|
|
style["commit/radius"] = style["commit/stroke-width"] * 1.5
|
|
|
|
|
|
|
|
# Static SVG templates.
|
2016-12-18 01:59:20 +00:00
|
|
|
svg["svg"] = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%d %d %d %d\">"
|
2016-10-30 23:17:54 +00:00
|
|
|
svg["/svg"] = "</svg>"
|
2016-12-18 01:59:20 +00:00
|
|
|
svg["g"] = "<g id=\"%s\">"
|
2016-10-30 23:17:54 +00:00
|
|
|
svg["/g"] = "</g>"
|
2016-12-18 01:59:20 +00:00
|
|
|
svg["path"] = "<path class=\"branch\" d=\"%s\" />"
|
|
|
|
svg["circle"] = "<circle class=\"commit\" cx=\"%d\" cy=\"%d\" r=\"%s\" />"
|
2016-10-30 23:17:54 +00:00
|
|
|
|
|
|
|
# Branch definitions.
|
2016-12-18 01:59:20 +00:00
|
|
|
branches[0,"name"] = "master"
|
|
|
|
branches[0,"refs"] = 0
|
|
|
|
branches[0,"merges"] = ""
|
|
|
|
len["branches"] = 1
|
2016-10-30 23:17:54 +00:00
|
|
|
|
|
|
|
# Commit definitions.
|
2016-12-18 01:59:20 +00:00
|
|
|
commits[0,"branch"] = "master"
|
|
|
|
commits[0,"message"] = "Initial commit"
|
|
|
|
commits[0,"type"] = "commit"
|
|
|
|
len["commits"] = 1
|
2016-10-30 23:17:54 +00:00
|
|
|
|
|
|
|
# Tracks the state across calls.
|
2016-12-18 01:59:20 +00:00
|
|
|
state["branch"] = 0
|
2016-10-30 23:17:54 +00:00
|
|
|
state["HEAD"] = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
# Rule Definitions
|
|
|
|
# ----------------
|
|
|
|
#
|
2016-12-18 01:59:20 +00:00
|
|
|
# This block contains definitions for line manipulation rules used across Grawkit.
|
2016-10-30 23:17:54 +00:00
|
|
|
# Rules may or may not be exclusive, i.e. the effects of one rule may cascade to
|
|
|
|
# subsequent rules for the same line.
|
|
|
|
|
|
|
|
# > Match `git commit` declarations.
|
|
|
|
$0 ~ rule["commit"] {
|
2016-12-18 01:59:20 +00:00
|
|
|
# Get commit message, if any.
|
|
|
|
match($0, rule["commit/message"], m)
|
2016-10-30 23:17:54 +00:00
|
|
|
|
2016-12-18 01:59:20 +00:00
|
|
|
# Add new commit.
|
|
|
|
addcommit("commit", (2 in m) ? m[2] : "Empty message")
|
|
|
|
next
|
|
|
|
}
|
|
|
|
|
|
|
|
# > Match `git branch` declarations.
|
|
|
|
$0 ~ rule["branch"] {
|
|
|
|
# Get branch name and throw error if one is not set.
|
|
|
|
match($0, rule["branch/name"], n)
|
|
|
|
if (n[1] == "") {
|
|
|
|
printf error["branch/no-name"], FNR
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# Throw error if branch already exists.
|
|
|
|
for (i = 0; i < len["branches"]; i++) {
|
|
|
|
if (branches[i,"name"] == n[1]) {
|
|
|
|
printf error["branch/duplicate"], n[1], FNR | "cat >&2"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Add empty branch as a placeholder.
|
|
|
|
addbranch(n[1])
|
|
|
|
next
|
|
|
|
}
|
|
|
|
|
|
|
|
# > Match `git checkout` declarations.
|
|
|
|
$0 ~ rule["checkout"] {
|
|
|
|
# Get branch name and throw error if one is not set.
|
|
|
|
match($0, rule["checkout/name"], n)
|
|
|
|
if (n[1] == "") {
|
|
|
|
printf error["checkout/no-name"], FNR | "cat >&2"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# Throw error if branch does not exist.
|
|
|
|
found = 0
|
|
|
|
for (i = 0; i < len["branches"]; i++) {
|
|
|
|
if (branches[i,"name"] == n[1]) {
|
|
|
|
found = 1
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (found == 0) {
|
|
|
|
printf error["branch/no-branch"], n[1], FNR | "cat >&2"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# Set internal state.
|
|
|
|
state["branch"] = i
|
|
|
|
next
|
|
|
|
}
|
|
|
|
|
|
|
|
# > Match `git merge` declarations.
|
|
|
|
$0 ~ rule["merge"] {
|
|
|
|
# Get branch name and throw error if one is not set.
|
|
|
|
match($0, rule["merge/name"], n)
|
|
|
|
if (n[1] == "") {
|
|
|
|
printf error["merge/no-name"], FNR | "cat >&2"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# Throw error if branch does not exist.
|
|
|
|
found = 0
|
|
|
|
for (i = 0; i < len["branches"]; i++) {
|
|
|
|
if (branches[i,"name"] == n[1]) {
|
|
|
|
found = 1
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (found == 0) {
|
|
|
|
printf error["branch/no-branch"], n[1], FNR | "cat >&2"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# Add a merge commit to current branch.
|
|
|
|
addcommit("merge", "Merge commit")
|
|
|
|
|
|
|
|
# Add merge reference to target branch.
|
|
|
|
if (branches[i,"merges"] == "") {
|
|
|
|
branches[i,"merges"] = state["branch"] "|" state["HEAD"]
|
|
|
|
} else {
|
|
|
|
branches[i,"merges"] = branches[i,"merges"] "," state["branch"] "|" state["HEAD"]
|
|
|
|
}
|
2016-10-30 23:17:54 +00:00
|
|
|
|
2016-11-01 23:27:58 +00:00
|
|
|
next
|
2016-10-30 23:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# SVG Graph Generation
|
2016-12-18 01:59:20 +00:00
|
|
|
# --------------------
|
2016-10-30 23:17:54 +00:00
|
|
|
#
|
|
|
|
# This block contains logic for building the final SVG output from Grawkit's
|
|
|
|
# internal state, as defined in the command-line provided.
|
2016-12-18 01:59:20 +00:00
|
|
|
|
2016-10-30 23:17:54 +00:00
|
|
|
END {
|
|
|
|
xy = style["branch/stroke-width"] * -1
|
2016-11-01 23:27:58 +00:00
|
|
|
w = (style["branch/spacing"] * (len["branches"] - 1)) + (style["branch/stroke-width"] * 2)
|
|
|
|
h = (style["commit/spacing"] * (len["commits"] - 1)) + (style["commit/stroke-width"] * 4)
|
2016-10-30 23:17:54 +00:00
|
|
|
|
|
|
|
# Print SVG header.
|
|
|
|
printf svg["svg"], xy, xy, w, h
|
|
|
|
printf "\n"
|
|
|
|
|
|
|
|
# Print inline style definitions.
|
2016-12-18 01:59:20 +00:00
|
|
|
print "<style type=\"text/css\"><![CDATA["
|
2016-10-30 23:17:54 +00:00
|
|
|
print ".branch {"
|
|
|
|
print " fill: " style["branch/fill"] ";"
|
|
|
|
print " stroke: " style["branch/stroke"] ";"
|
|
|
|
print " stroke-width: " style["branch/stroke-width"] ";"
|
|
|
|
print "}"
|
|
|
|
print ".commit {"
|
|
|
|
print " fill: " style["commit/fill"] ";"
|
|
|
|
print " stroke: " style["commit/stroke"] ";"
|
|
|
|
print " stroke-width: " style["commit/stroke-width"] ";"
|
|
|
|
print "}"
|
|
|
|
print "]]></style>"
|
|
|
|
|
|
|
|
# Print each branch and corresponding commits in turn.
|
2016-12-18 01:59:20 +00:00
|
|
|
for (i = len["branches"] - 1; i >= 0; i--) {
|
|
|
|
print branch(i)
|
2016-10-30 23:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Print SVG footer.
|
|
|
|
print svg["/svg"]
|
|
|
|
}
|