forked from lennart/git-achievements
-
Notifications
You must be signed in to change notification settings - Fork 1
/
git-achievements
executable file
·502 lines (459 loc) · 19.8 KB
/
git-achievements
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
#!/usr/bin/env bash
#
# Copyright (c) 2010, Benjamin C. Meyer <[email protected]>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the project nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
function git_achievements_command
{
case $2 in
-p|--publish )
publish_achievements
;;
-l|--list )
cat "${ACHIEVEMENTSLOGFILE}"
;;
* )
echo "Git Achievements"
count=`cat "${ACHIEVEMENTSLOGFILE}" 2> /dev/null | grep Unlocked | wc -l`
echo ""
echo "You currently have: $count achievements"
echo ""
echo "Options:"
echo " -l --list Show all achievements."
echo " -p --publish Generate achievements html files and if achievements.upload is set to \"true\" add the files and push to origin."
;;
esac
}
function log_action
{
local expansion=$(unalias_command "$1")
if [[ $expansion ]] ; then
shift
echo "$expansion $@" >> "${ACTIONLOGFILE}"
else
echo "$@" >> "${ACTIONLOGFILE}"
fi
echo -n "Date: " >> "${ACTIONLOGFILE}"
date >> "${ACTIONLOGFILE}"
}
function output_achievement
{
echo "
********************************************************************************
Git Achievement Unlocked!
$1
$2
********************************************************************************
" | sed -e :a -e 's/^.\{1,79\}$/ & /;ta'
}
function unlock_achievement()
{
grep "$1" "${ACHIEVEMENTSLOGFILE}" > /dev/null 2>/dev/null
if [ $? -eq 0 ] ; then
return
fi
output_achievement "$@" >&2
output_achievement "$@" >> "${ACHIEVEMENTSLOGFILE}"
if [ "`git config --global achievement.upload`" = "true" ] ; then
publish_achievements "$@"
fi
}
function count_command
{
export power=`awk "BEGIN {n=0} /$1/ {n++} END { print log(n)/log(2) }" "$ACTIONLOGFILE"`
export count=`awk "BEGIN {n=0} /$1/ {n++} END { print n }" "$ACTIONLOGFILE"`
#export powerof2=`awk "BEGIN {n=0} /$1/ {n++} END { print and(n, n-1) }" "$ACTIONLOGFILE"`
# mysys gawk it old and doesn't have the bitwise operation and :( so we have to use perl for now
export powerof2=`echo 'use Env; $y=$ENV{'count'}; $x=($y & ($y-1)); print "$x\n" ' | perl -`
# Set a default value of 0. Bug seems to occur when the achievement is unlocked
if [ ${count:-0} -eq 1 ] ; then
powerof2="1"
fi
#echo "cmd: $1 powerof2: $powerof2 power: $power count: $count"
}
function hcount_unlock_achievement
{
title="${1}"
description="${2}"
power="$3"
case $power in
1|2|3)
pre="Apprentice "
;;
4|5|6)
pre=""
;;
*)
pre="Master "
esac
unlock_achievement "${pre}${title} (Level ${power})" "${description}"
}
function count_unlock_achievement
{
title="${1}"
description="${2}"
cmd="${3}"
count_command "${cmd}"
if [ "${powerof2}" = "0" ] ; then
hcount_unlock_achievement "${title}" "${description}" "$power"
fi
}
function unalias_command
{
#XXX maybe this should be recursive?
if [ -n $1 ] && [[ $1 != -* ]]; then
local cmdline=$(git config --get "alias.$1")
echo $cmdline
fi
}
function check_for_achievements
{
# Anyone know of a cleaner way of checking for hooks?
gitdir=`git rev-parse --git-dir 2>/dev/null`
hooks=`ls -F $gitdir/hooks/ 2>/dev/null | grep -e '\*$' | grep -ve 'sample\*$' | wc -l`
if [ ${hooks} -ge 1 ] ; then
hcount_unlock_achievement "Carpenter" "Custom git hooks are installed which help catch issues before they are shared." "$hooks"
fi
# Custom command
if [ -e "`git --exec-path`/$1" ] && [ $1 != "achievements" ] && echo "$1" | grep -q -v ^- ; then
unlock_achievement "Inventor ($1)" "Used a command that isn't part of the built in Git command"
fi
case $2 in
--help )
unlock_achievement "Student" "Accessed the documentation for a command with git [command] --help"
return
;;
esac
local command=$1
local expansion=$(unalias_command "$command")
if [[ $expansion ]] ; then
# not a real inventor
unlock_achievement "Garage Inventor" "Used a custom alias for a Git command"
# get actual command, removing any additional parameters from the command
command=$(echo "$expansion" | awk '{ print $1; }')
fi
case $command in
add )
count_unlock_achievement "Stone Mason" "Added files to the index area for inclusion in the next commit with git add" "$command"
case $2 in
*.gitignore )
unlock_achievement "Caretaker" "Added a .gitignore file to a repository."
;;
-p )
count_unlock_achievement "Miller" "Add only part of a file to the stage $count times with git add -p." "add -p"
;;
esac
;;
am )
count_unlock_achievement "Messenger" "Applied a patch using git am." "$command"
;;
bisect )
count_unlock_achievement "Hunter" "Used git bisect to perform a binary search to find which change introduced a bug." "$command"
;;
blame )
count_unlock_achievement "Investigator" "Used git blame to annotate a file with information about how each line changed." "$command"
;;
bundle )
count_unlock_achievement "Delivery Boy" "Move objects and refs by archive with git bundle." "$command"
;;
cherry-pick )
unlock_achievement "Cherry Picker" "Used git cherry-pick to add a sha from another branch into the current branch." "$command"
;;
checkout )
case $2 in
-b )
count_unlock_achievement "Blacksmith" "Created a branch using git checkout -b." "checkout -b"
;;
esac
;;
clean )
count_unlock_achievement "Cleaning lady" "Remove untracked files from the working tree with git clean" "$command"
;;
commit )
count_unlock_achievement "Author" "Made 2^Level commits using git commit." "$command"
if [ "`git log --pretty=oneline | wc -l`" -eq "1" ] ; then
unlock_achievement "Let there be light" "Commit without a parent." "$command"
fi
case $2 in
--amend )
count_unlock_achievement "Seamstress" "amended a commit with git commit --amend." "commit --amend"
;;
-s )
count_unlock_achievement "Locksmith" "Add Signed-off-by line at the end of the commit log message using git commit -s." "commit -s"
;;
esac
;;
config )
if [[ ( $# -ge 3 && $2 = "user.name" ) || ( $# -ge 4 && $3 = "user.name" ) ]] ; then
unlock_achievement "Baptised" "Set global user name using git config."
fi
if [[ ( $# -ge 3 && $2 = "user.email" ) || ( $# -ge 4 && $3 = "user.email" ) ]] ; then
unlock_achievement "Homeowner" "Set global email address using git config."
fi
;;
diff )
case $2 in
--cached )
count_unlock_achievement "Goldsmith" "Reviewed patches before committing with git diff --cached." "diff --cached"
;;
esac
;;
fetch )
count_unlock_achievement "Collector" "Fetches named heads or tags from another repository with git fetch" "$command"
;;
filter-branch )
count_unlock_achievement "Tree Trimmer" "Rewrite branches with git filter-branch" "$command"
;;
format-patch )
count_unlock_achievement "Archivist" "Prepare each commit with its patch in one file per commit with git format-patch" "$command"
;;
gc )
count_unlock_achievement "Chimney Sweeper" "Used git gc to run a number of housekeeping tasks on the current repository." "$command"
;;
grep )
count_unlock_achievement "Fisherman" "Look for specified patterns in the current repository with git grep." "$command"
;;
imap-send )
count_unlock_achievement "Postman" "Send a collection of patches from stdin to an IMAP folder with git imap-send" "$command"
;;
init )
count_unlock_achievement "Architect" "Created a new repository with git init." "$command"
;;
instaweb )
count_unlock_achievement "Web Designer" "Instantly browse your working repository in gitweb with git instaweb" "$command"
;;
log )
count_unlock_achievement "Historian" "Investigate the commit log using git log." "$command"
case $2 in
-p* )
unlock_achievement "Dentist" "Extracted patches using git log -p."
;;
-S* )
unlock_achievement "Librarian" "Looked for change that introduce or remove a string with git log -S"
;;
esac
;;
merge )
count_unlock_achievement "Banker" "Join two or more development histories together with git merge." "$command"
;;
push )
case $2 in
-n|--dry-run )
;;
* )
# not a dry run
count_unlock_achievement "Socialite" "pushed a branch to a remote repository using git push" "$command"
case $2 in
-f )
count_unlock_achievement "Thug" "Forced pushed a branch with git push -f" "push -f"
;;
esac
;;
esac
;;
rebase )
case $2 in
-i )
count_unlock_achievement "Butcher" "Performed an interactive rebase using git rebase -i." "rebase -i"
;;
--onto )
count_unlock_achievement "Pilgrim" "Performed a rebase using git rebase --onto." "rebase --onto"
;;
esac
;;
reflog )
unlock_achievement "Weaver" "Investigate old branches by using git reflog"
case $2 in
--date=relative )
unlock_achievement "Stamp Collector" "Investigate old branches by using git reflog --date=relative"
;;
esac
;;
remote )
case $2 in
add )
count_unlock_achievement "Merchant" "Added an external repository with git remote add." "remote add"
;;
esac
;;
shell )
unlock_achievement "Beach Lion" "Restricted login shell for GIT-only SSH access with git shell"
;;
show )
count_unlock_achievement "Presenter" "Shows one or more objects (blobs, trees, tags and commits) with git show" "$command"
;;
show-branch )
count_unlock_achievement "Gardner" "Shows the commit ancestry graph with git show-branch" "$command"
;;
stash )
count_unlock_achievement "Product Manager" "Stash the changes in a dirty working directory away with git stash." "$command"
;;
submodule )
case $2 in
add )
count_unlock_achievement "Cathedral Architect" "Added a submodule to a repository." "submodule add"
;;
update )
count_unlock_achievement "Cathedral Worker" "Cloned submodule repository and checked out commits specified by superproject." "submodule update"
;;
esac
;;
svn|p4 )
count_unlock_achievement "Traveler" "Streamed changes between another rcs with git svn or git p4." "$command"
;;
tag )
count_unlock_achievement "Gipsy" "Create, list, delete a tag signed with GPG using git tag" "$command"
;;
whatchanged )
count_unlock_achievement "News Reader" "Show logs with difference each commit introduces with git whatchanged" "$command"
;;
flow )
count_unlock_achievement "Pedantic" "Use the flow extension to encourage an orderly and standardized branching model" "$command"
;;
hash-object|update-index|commit-tree|update-ref )
count_unlock_achievement "Plumber" "Use the internal plumbing commands of git." "$command"
;;
esac
}
function link_to_git_docs
{
sed -e 's_git \([a-z0-9-]*\)_<a href="http://www.kernel.org/pub/software/scm/git/docs/git-\1.html">git-\1</a>_g' | sed -e 's/\/git-hooks/\/githooks/g'
}
function clean_logged_achievements() {
cat "${ACHIEVEMENTSLOGFILE}" | grep -ve '^$' | grep -v 'Git Achievement Unlocked!' | grep -v '*' | sed -e 's/^ *//g' -e 's/ *$//g'
}
function publish_achievements
{
git_achievements="$(which git-achievements)"
git_achievements_dir="$(dirname "${git_achievements}")"
if [ -L "${git_achievements}" ]; then
case "$(dirname $(readlink ${git_achievements}))" in
/*)
git_achievements_dir="$(dirname $(readlink ${git_achievements}))"
;;
*)
git_achievements_dir="${git_achievements_dir}/$(dirname $(readlink ${git_achievements}))"
;;
esac
fi
cd "${git_achievements_dir}"
user=`git config --global user.name`
echo "
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>
<title>${user}'s Git Achievements</title>
<link rel=\"alternate\" type=\"application/rss+xml\" title=\"rss feed\" href=\"index.rss\"/>
<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/>
</head>
<body>
" > index.html
mailhash=`git config --global user.email | xargs echo -n | tr '[A-Z]' '[a-z]' | openssl md5 | sed -e 's/.* //'`
echo "<h2>${user}'s Git Achievements <img src='http://www.gravatar.com/avatar/${mailhash}'/></h2>" >> index.html
# STATS
cat git-achievements | grep 'unlock_achievement ' | grep -v ' sed ' | grep -v '\${' | sed -e 's/.*unlock_achievement //g' -e 's/^"//g' -e 's/"$//g' -e 's/\$power/X/g' -e 's/\$count/X/g' -e 's/\$hooks/X/g' | sort | awk -F"\" \"" '{ print "<li><div class=\"title\">" $1 "</div><div class=\"info\">" $2 "</div></li>" }' | link_to_git_docs > all.html
total=`cat all.html | wc -l`
unlockedCount=`clean_logged_achievements | awk '{ if (NR % 2 != 0) print $0 }' | sed -e 's/Apprentice //g' -e 's/Master //g' -e 's/(.*//g' | sort | uniq | wc -l`
levels=`clean_logged_achievements | awk '{ if (NR % 2 != 0) print $0 }' | grep Level | sed -e 's/.* //g' -e 's/)//g' | awk '{ t=t+$0 } END { print t }'`
onelevels=`clean_logged_achievements | awk '{ if (NR % 2 != 0) print $0 }' | wc -l`
let points="${levels}"+"${onelevels}"
echo "Unlocked ${unlockedCount}/$total <a href=\"http://github.com/icefox/git-achievements\">Git Achievements</a> for $points points<br>" >> index.html
unlocked=`cat "${ACHIEVEMENTSLOGFILE}" | grep Unlocked | wc -l`
# Unlocked Achievements
echo "<ul>" >> index.html
cat "${ACHIEVEMENTSLOGFILE}" | grep -ve '^$' | grep -v 'Git Achievement Unlocked!' | grep -v '*' | sed -e 's/^ *//g' -e 's/ *$//g' | awk '{ if (NR % 2 != 0) printf "<li><div class=\"title\">" $0 "</div>"; else printf " <div class=\"info\">" $0 "</div></li>\n" }' | link_to_git_docs | sort >> index.html
echo "</ul>" >> index.html
# Git Commands
echo 'Git commands sorted by usage:' >> index.html
echo "<pre style=\"text-align: left\">" >> index.html
cat "${ACTIONLOGFILE}" | grep -v Date | grep -v 'git-dir' | awk '{ print $1 }' | sort | uniq -c | sort -nr >> index.html
echo "</pre>" >> index.html
# Locked Achievements
echo "<script type=\"text/javascript\">
function showLocked() {
document.getElementById('locked').style.visibility = 'visible';
document.getElementById('showlocked').style.visibility = 'hidden';
}
</script>
<a id=\"showlocked\" href=\"javascript:showLocked()\" >Show locked Achievements</a>
<div id=\"locked\">
There are $total Achievements. Some achievements can be leveled up depending on the number of times it is used (Used 2 times = level 1, 4 = level 2, 8 = level 3, 16 = level 4, 32 = level 5, etc)
<ul>" >> index.html
cat index.html | sed -e 's/Apprentice //g' -e 's/Master //g'> temp.html
cat all.html | while read line ;
do
stripedline=`echo $line | sed -e 's/<\/div>.*//g' -e 's/(.*//g' -e 's/Apprentice //g'`
grep "$stripedline" temp.html > /dev/null
if [ ! $? -eq 0 ] ; then
echo $line >> index.html
fi
done
echo '</ul></div>' >> index.html
rm temp.html
rm all.html
echo "</body></html>" >> index.html
# Update the RSS feed
echo '<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel>' > index.rss
echo "<title>${user}'s Git Achievements</title>" >> index.rss
echo "<description></description>" >> index.rss
echo "<link></link>" >> index.rss
cat "${ACHIEVEMENTSLOGFILE}" | grep -ve '^$' | grep -v 'Git Achievement Unlocked!' | grep -v '*' | sed -e 's/^ *//g' -e 's/ *$//g' | awk '{ if (NR % 2 != 0) printf "<item><title>" $0 "</title>"; else printf "<description>" $0 "</description></item>\n" }' | tail -n 20 | sed '1!G;h;$!d' >> index.rss
echo '</channel></rss>' >> index.rss
abortcount=`git status | grep 'Changes to be committed' | wc -l`
if [ ! $abortcount -eq 0 ] ; then
echo "There are staged changes in the repository, updates let uncommitted"
return
fi
if [ ! "`git config --global achievement.upload`" = "true" ] ; then
echo "Global achievement.upload not set to true, updates left uncommited"
return
fi
echo "Adding new achievements and publishing to origin."
git add index.html index.rss
if [ -z "${1}" ] ; then
message="Publishing updated achievement"
else
message="New achievement $1"
fi
git commit -qm "${message}"
git push origin gh-pages
}
ACTIONLOGFILE="$HOME/.git-achievements-action.log"
ACHIEVEMENTSLOGFILE="$HOME/.git-achievements.log"
if [ ! "$1" = "achievements" ] ; then
git "$@"
r=$?
if [ ! ${r} -eq 0 ] ; then
exit $r
fi
else
git_achievements_command "$@"
fi
if [ "`echo '$1' | sed -e 's/=.*//g'`" = "--git-dir" ] ; then
exit 0
fi
log_action $@
check_for_achievements $@