Pretty Git branch graphs

Pretty Git branch graphs

技术背景

在使用Git进行版本控制时,清晰地查看分支图有助于理解项目的开发历史和分支关系。然而,Git默认的日志输出可能不够直观,因此需要一些方法来生成漂亮的分支图。

实现步骤

1. 使用命令行命令

可以通过一些特定的git log命令来生成不同样式的分支图。

  • 简单一行命令
1
git log --all --decorate --oneline --graph

也可以将其设置为别名,方便使用:

1
git config --global alias.adog "log --all --decorate --oneline --graph"

之后使用git adog即可。

  • 更多详细文本输出
1
git log --graph --date-order -C -M --pretty=format:"<%h> %ad [%an] %Cgreen%d%Creset %s" --all --date=short

同样可以设置别名:

1
2
[alias]
graph = log --graph --date-order -C -M --pretty=format:\"<%h> %ad [%an] %Cgreen%d%Creset %s\" --all --date=short

2. 使用脚本和工具

  • 自定义脚本:可以在.gitconfig文件中添加自定义的别名和脚本,实现更复杂的输出格式。例如:
1
2
3
4
[alias]
lg1 = log --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)' --all
lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(auto)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'
lg = lg1

使用git lggit lg1git lg2可以查看不同样式的分支图。

  • 使用第三方工具
    • Gitgraph.js:可以在没有仓库的情况下绘制漂亮的Git分支图,只需编写JavaScript代码配置分支和提交,然后在浏览器中渲染。
1
2
3
4
5
6
7
8
9
10
11
12
var gitGraph = new GitGraph({
template: "blackarrow",
mode: "compact",
orientation: "horizontal",
reverseArrow: true
});

var master = gitGraph.branch("master").commit().commit();
var develop = gitGraph.branch("develop").commit();
master.commit();
develop.commit().commit();
develop.merge(master);
- **Sourcetree**:支持Windows 7+和Mac OS X 10.6+,可以打印出美观且中等大小的历史和分支图。
- **git-forest**:一个优秀的Perl脚本,使用Unicode字符绘制图中的线条,还可以结合`--reverse`选项输出图。可以在`.gitconfig`中设置别名:
1
2
[alias]
tree = "forest --pretty=format:\"%C(red)%h %C(magenta)(%ar) %C(blue)%an %C(reset)%s\" --style=15 --reverse"

3. 生成表格样式输出

可以通过一些脚本实现表格样式的Git日志输出。

  • 基本表格输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
while IFS=+ read -r graph hash time branch message;do
# Count needed amount of white spaces and create them
whitespaces=$((9-$(sed -nl1000 'l' <<< "$graph" | grep -Eo '\\|\||\/| |\*|_' | wc -l)))
whitespaces=$(seq -s' ' $whitespaces|tr -d '[:digit:]')

# Show hashes besides the tree ...
#graph_all="$graph_all$graph$(printf '%7s' "$hash")$whitespaces \n"

# ... or in an own column
graph_all="$graph_all$graph$whitespaces\n"
hash_all="$hash_all$(printf '%7s' "$hash") \n"

# Format all other columns
time_all="$time_all$(printf '%12s' "$time") \n"
branch_all="$branch_all$(printf '%15s' "$branch")\n"
message_all="$message_all$message\n"
done < <(git log --all --graph --decorate=short --color --pretty=format:'+%C(bold 214)%<(7,trunc)%h%C(reset)+%C(dim white)%>(12,trunc)%cr%C(reset)+%C(214)%>(15,trunc)%d%C(reset)+%C(white)%s%C(reset)' && echo);

# Paste the columns together and show the table-like output
paste -d' ' <(echo -e "$time_all") <(echo -e "$branch_all") <(echo -e "$graph_all") <(echo -e "$hash_all") <(echo -e "$message_all")
  • 改进的表格输出:可以将其设置为别名,支持更多自定义选项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
[color "decorate"]
HEAD = bold blink italic 196
branch = 214
tag = bold 222

[alias]

# Delimiter used in every mylog alias as column seperator
delim = ^

# Short overview about the last hashes without graph
mylog = log --all --decorate=short --color --pretty=format:'^%C(dim white)%>(12,trunc)%cr%C(reset)^%C(bold 214)%<(7,trunc)%h%C(reset)' -5

# Log with hashes besides graph tree
mylog2 = log --all --graph --decorate=short --color --pretty=format:'%C(bold 214)%<(7,trunc)%h%C(reset)^%C(dim white)%>(12,trunc)%cr%C(reset)^%C(auto)%>(15,trunc)%D%C(reset)^%C(white)%<(80,trunc)%s%C(reset)'
mylog2-col= 3

# Log with hashes in an own column and more time data
mylog3 = log --all --graph --decorate=short --color --pretty=format:'^%C(dim white)%>(12,trunc)%cr%C(reset)^%C(cyan)%<(10,trunc)%cs%C(reset)^%C(bold 214)%<(7,trunc)%h%C(reset)^%C(auto)%<(15,trunc)%D%C(reset)^%C(white)%s%C(reset)'
mylog3-col= 4

tably = !bash -c '" \
\
\
declare -A col_length; \
apost=$(echo -e "\u0027"); \
delim=$(git config alias.delim); \
git_log_cmd=$(git config alias.$1); \
git_tre_col=${2:-$(git config alias.$1-col)}; \
[[ -z "$git_tre_col" ]] && git_tre_col=1; \
[[ -z "$git_log_cmd" ]] && { git $1;exit; }; \
\
\
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%$apost*}; \
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}")"; \
\
\
git_log_fst_part="${git_log_cmd%%\"$apost\"*}"; \
git_log_lst_part="${git_log_cmd##*\"$apost\"}"; \
git_log_tre_part="${git_log_cmd%%\"$delim\"*}"; \
git_log_tre_part="${git_log_tre_part##*\"$apost\"}"; \
git_log_cmd_count="$git_log_fst_part$apost $git_log_tre_part$apost$git_log_lst_part"; \
col_length["1:1"]=$(eval git "${git_log_cmd_count// --color}" | wc -L); \
\
\
i=0; \
while IFS="$delim" read -r graph rest;do \
((i++)); \
graph_line[$i]="$graph"; \
done < <(eval git "${git_log_cmd/ --color}" && echo); \
\
\
i=0; \
l=0; \
while IFS= read -r line;do \
c=0; \
((i++)); \
((l++)); \
[[ $l -gt $n ]] && l=1; \
while IFS= read -d"$delim" -r col_content;do \
((c++)); \
[[ $c -le $git_tre_col ]] && c_corr=-1 || c_corr=0; \
if [[ $c -eq 1 ]];then \
[[ "${col_content/*}" = "$col_content" ]] && [[ $l -eq 1 ]] && l=$n; \
count=$(wc -L <<< "${graph_line[$i]}"); \
whitespaces=$(seq -s" " $((${col_length["1:1"]}-$count))|tr -d "[:digit:]"); \
col_content[$git_tre_col]="${col_content}$whitespaces"; \
else \
col_content[$c+$c_corr]="$(printf "%-${col_length["$l:$c"]}s" "${col_content:-""}")"; \
fi; \
done <<< "$line$delim"; \
for ((k=$c+1;k<=$i_max;k++));do \
[[ $k -le $git_tre_col ]] && c_corr=-1 || c_corr=0; \
col_content[$k+$c_corr]="$(printf "%-${col_length["$l:$k"]:-${col_length["${last[$k]:-1}:$k"]:-0}}s" "")"; \
done; \
unset col_content[0]; \
echo -e "${col_content[*]}"; \
unset col_content[*]; \
done < <(eval git "$git_log_cmd" && echo); \
"' "git-tably"

最佳实践

  • 选择合适的工具和方法:根据自己的需求和使用习惯选择合适的工具和方法。如果只是偶尔查看分支图,使用简单的命令行命令即可;如果需要更复杂的功能和样式,可以使用第三方工具或自定义脚本。
  • 设置别名:将常用的命令设置为别名,方便快速使用。可以在.gitconfig文件中进行设置。
  • 结合其他工具:可以结合其他工具,如less,对输出进行分页查看,方便查看大量的日志信息。

常见问题

  • 输出格式不符合预期:检查命令或脚本中的参数和格式设置是否正确,特别是git log--pretty选项。
  • 工具无法正常使用:检查工具的安装和配置是否正确,例如git-forest需要确保Perl环境正常,tig需要正确安装。
  • 表格输出不整齐:可能是由于列宽设置不合理或字符计数不准确导致的。可以调整列宽设置和字符计数方法,确保输出整齐。

Pretty Git branch graphs
https://119291.xyz/posts/pretty-git-branch-graphs/
作者
ww
发布于
2025年5月26日
许可协议