At work I tend to pop around a lot of projects fixing (and let’s be
honest, breaking) things. And since I spend a lot of time in shell it
would be nice if my shell prompt would tell me the status of the pipeline
of the project and branch that I’m currently in. Especially when I
context switch back to a project, it would be good if cd other/project
would tell me not just what branch I was in, but whether it built
or didn’t.
Today I poked around and got python-gitlab to do that for me. I use
the project’s .git/config
to store a cache of pipeline statuses
(one per branch) and then emit an emoji to say how things are going.
It’s done such that deleting a local branch will also delete the cache
for that branch.
The API calls take time which can be annoying as you’re working in a
shell. Therefore I tried to limit them with caching. When it’s first
run it will cache the project id in pipeline-status.project
After that
it will use the branch.BRANCH-NAME
section to cache the following:
pipeline-cache-hash
to save the current hash.pipeline-cache-update
to save the last update.pipeline-cache-status
to save the last status
The pipeline-cache-status
is only set if it’s been more that 7200
seconds since the last pipeline update. I’ll need to play with that a
bit more.
I have some zsh code for printing info about my current branch and so
in there I just call this script and put the resulting emoji (if there
is one) into there.
Annoyingly I can’t really generalise this script very easily. I could
drive it off some settings in ~/.gitconfig
but haven’t gotten around
to it. For now you’ll have to add the GitLab servers you care about.
And if you use GitHub’s CI system, Travis, CircleCI, Jenkins, AWS’s CI
system or any of a host of others, well, sorry, this won’t work.
Adding support for non-gitlab systems would be interesting, but I think
before then it might be better to have a little daemon that would capture
this - fired off by systemd on Linux systems. The shell could then
poke it for info (faster) and it could be smarter about caching and the
number of requests to the server (better behaved). Obviously that would
involve writing it in a real language!
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
| #!/usr/bin/env bash
# Documentation:
#
# Need to install python-gitlab and jq. To configure python-gitlab
# you need a config file named ~/.python-gitlab.cfg with these contents:
#
# [global]
# default = site1
# ssl_verify = true
# timeout = 5
#
# [site1]
# url = https://gitlab.site1.com/
# private_token = TOKEN-1
# api_version = 4
#
# [site2]
# url = https://gitlab.site2.com/
# private_token = TOKEN-2
# api_version = 4
#
# Replace TOKEN-n with your gitlab private tokens - give it the
# read_api and read_user scopes. The latter might not be needed.
if [[ ! -f ~/.python-gitlab.cfg ]]; then
exit
fi
export TZ=UTC
url="$(git config \
remote."$(git for-each-ref --format='%(upstream:remotename)' \
"$(git symbolic-ref -q HEAD)")".url)"
gitlab=(gitlab -o json)
case "$url" in
https://gitlab.site1.com/*)
url="${url#https://gitlab.site1.com/}"
;;
git@gitlab.site1.com:*)
url="${url#git@gitlab.site1.com:}"
;;
https://gitlab.site2.com/*)
url="${url#https://gitlab.site2.com/}"
gitlab=(gitlab -o json -g site2)
;;
ssh://git@gitlab.site2.com:2200/*)
url="${url#ssh://git@gitlab.site2.com:2200/}"
gitlab=(gitlab -o json -g site2)
;;
*)
echo "π½"
exit
;;
esac
project="$(git config pipeline-status.project)"
if [[ -z "$project" ]]; then
url="${url%.git}"
url="${url//\//%2F}"
project="$("${gitlab[@]}" project get --id "$url" | jq -r '.id')"
if [[ -z "$project" ]]; then
echo "π"
exit
fi
git config pipeline-status.project "$project"
fi
conf="branch.$(git rev-parse --verify --abbrev-ref HEAD 2> /dev/null)"
hash="$(git rev-parse HEAD)"
cache_hash="$(git config "$conf".pipeline-cache-hash)"
if [[ "$hash" == "$cache_hash" ]]; then
git config "$conf".pipeline-cache-status && exit
else
git config "$conf".pipeline-cache-hash "$hash"
git config --unset "$conf".pipeline-cache-status
git config --unset "$conf".pipeline-cache-update
fi
cache-update() {
local update
update="$("${gitlab[@]}" project-pipeline list \
--project-id "$1" --sha "$2" \
| jq -r '.[] | .updated_at' | sort | tail -1)"
if [[ -z "$update" ]]; then
update="$(date +%s)"
else
# Won't work on FreeBSD - date does not take a -d flag.
# Need to parse time in this format: 2020-09-11T10:38:04.394Z
if [[ "$OSTYPE" == linux-gnu ]]; then
update="$(date +%s -d "$update")"
else
update="$(date -j -f '%FT%H:%M:%S' "${update%.*}" +%s)"
fi
fi
git config "$conf".pipeline-cache-update "$update"
}
cache_update=$(git config "$conf".pipeline-cache-update)
if [[ -z "$cache_update" ]]; then
cache-update "$project" "$hash"
elif [[ $(( $(date +%s) - cache_update )) -gt 7200 ]]; then
git config "$conf".pipeline-cache-status && exit
cache-update "$project" "$hash"
fi
cache_update=$(git config "$conf".pipeline-cache-update)
status="$("${gitlab[@]}" project-pipeline list \
--project-id "$project" --sha "$hash" \
| jq -r '.[] | "\(.updated_at) \(.status)"' \
| sort | tail -1 | cut '-d ' -f2)"
# NOTE: The pending and skipped emoji do not seem to provide
# double width hints so don't align properly w/o a training dot.
case "$status" in
created) status="π
" ;;
waiting_for_resource) status="π" ;;
preparing) status="ποΈ" ;;
pending) status="βΈοΈ." ;;
running) status="π" ;;
success) status="π" ;;
failed) status="π₯" ;;
canceled) status="π" ;;
skipped) status="βοΈ." ;;
manual) status="π" ;;
scheduled) status="β°" ;;
"") status="π" ;;
*) status="-unknown-status-${status}-" ;;
esac
if [[ $(( $(date +%s) - cache_update )) -gt 7200 ]]; then
git config "$conf".pipeline-cache-status "$status"
fi
echo "$status"
|