├── .gitignore ├── emacs-remote-save ├── git-trash ├── untilfails ├── c2f ├── f2c ├── untilsuccessful ├── colorify ├── dot2pdf ├── spydate ├── gcallthegit ├── multibuild ├── gpgwho ├── unixtime ├── git-contributors ├── git-clean-except ├── git-timecard ├── make-go-compilers ├── git-commit-chart ├── git-emptybranch ├── git-test-once ├── go-manifest ├── git-baseless-commit ├── go-set-versions ├── git-test-bisect ├── LICENSE ├── git-masstimecard ├── git-linecount ├── git-alternate ├── git-htmlchangelog ├── git-reroot ├── git-test-sequence ├── git-lineowners ├── git-tree-converge └── gitaggregates.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | i386-apple-darwin 4 | powerpc-apple-darwin -------------------------------------------------------------------------------- /emacs-remote-save: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | emacsclient -e '(save-some-buffers t)' 4 | -------------------------------------------------------------------------------- /git-trash: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git commit -n -a -m "Throwing away..." && git reset --hard HEAD^ -------------------------------------------------------------------------------- /untilfails: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while "$@" 4 | do 5 | sleep 1 6 | echo Retrying... 7 | done 8 | -------------------------------------------------------------------------------- /c2f: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for i in "$@" 4 | do 5 | echo "2k $i 9 * 5 / 32 + p" | dc 6 | done 7 | -------------------------------------------------------------------------------- /f2c: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for i in "$@" 4 | do 5 | echo "2k $i 32 - 5 * 9 / p" | dc 6 | done 7 | -------------------------------------------------------------------------------- /untilsuccessful: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while ! "$@" 4 | do 5 | sleep 1 6 | echo Retrying... 7 | done 8 | -------------------------------------------------------------------------------- /colorify: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for i in "$@" 4 | do 5 | vim -e \ 6 | -c "set nu" \ 7 | -c "set bg=dark" \ 8 | -c 'so $VIMRUNTIME/syntax/2html.vim' \ 9 | -c "w" \ 10 | -c "qa!" "$i" 11 | done 12 | -------------------------------------------------------------------------------- /dot2pdf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | IN="$1" 4 | OUT="`basename "$1" .dot`.pdf" 5 | 6 | dot -Tps -o dot2pdf.tmp.$$.ps "$IN" 7 | ps2pdf dot2pdf.tmp.$$.ps 8 | rm dot2pdf.tmp.$$.ps 9 | mv dot2pdf.tmp.$$.pdf "$OUT" 10 | -------------------------------------------------------------------------------- /spydate: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PT=`env TZ=US/Pacific date` 4 | CT=`env TZ=US/Central date` 5 | AT=`env TZ=Australia/Melbourne date` 6 | 7 | echo "Santa Clara $PT" 8 | echo "Central $CT" 9 | echo "Melbourne $AT" 10 | -------------------------------------------------------------------------------- /gcallthegit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gitgc() { 4 | while read adir 5 | do 6 | base=${adir##*/} 7 | dir=${adir%"$base"} 8 | echo "Doing $dir" 9 | git --git-dir="$adir" gc 10 | done 11 | } 12 | 13 | find $HOME -name .git | gitgc 14 | -------------------------------------------------------------------------------- /multibuild: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | p=`pwd` 4 | binname=`basename $p` 5 | 6 | for p in 386 arm amd64 7 | do 8 | for o in linux darwin windows 9 | do 10 | env GOARCH=$p GOOS=$o CGO_ENABLED=0 go build -v -o $binname/$binname.$o.$p & 11 | done 12 | done 13 | 14 | wait 15 | -------------------------------------------------------------------------------- /gpgwho: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # How to list all of the (known) keys that can decrypt a file. 3 | # gpg --list-only almost does this, but doesn't show your own. That's 4 | # usually what I want to know. 5 | 6 | for i in "$@" 7 | do 8 | echo "$i": 9 | gpg --no-default-keyring --secret-keyring /dev/null -a --list-only "$i" 10 | done 11 | -------------------------------------------------------------------------------- /unixtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import time 5 | import string 6 | 7 | def parse(i): 8 | # Remove everything but the digits 9 | i = float(filter(lambda c: c in string.digits, i)) 10 | while i > 2**31: 11 | i /= 10 12 | return i 13 | 14 | if len(sys.argv) == 1: 15 | t = time.time() 16 | print int(t), time.ctime(t) 17 | else: 18 | for i in sys.argv[1:]: 19 | print i, time.ctime(parse(i)) 20 | -------------------------------------------------------------------------------- /git-contributors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Who works on this project? 3 | 4 | For printing a chart (the default), pygooglechart is required: 5 | 6 | http://pygooglechart.slowchop.com/ 7 | 8 | Note that this assumes you've got an OS-X style "open" command. If 9 | you don't, it should be easy enough to adapt main to what you need. 10 | 11 | """ 12 | 13 | import sys 14 | 15 | import gitaggregates 16 | 17 | if __name__ == '__main__': 18 | c = gitaggregates.Contributors(sys.argv[1:]) 19 | gitaggregates.open_chart(c) 20 | -------------------------------------------------------------------------------- /git-clean-except: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Clean all files that are ignored and not tracked, but exclude those listed in .gitexcept file. 3 | # Example: 4 | # git clean-except 5 | 6 | except=`cat .gitexcept | tr -d '\r' | tr '\n' '|' | sed "s/|$//"` 7 | files="git ls-files -o -i --exclude-standard" 8 | count=`$files | egrep -v $except | wc -l` 9 | if [ $count -eq 0 ] 10 | then 11 | echo "Already clean!" 12 | else 13 | echo "Deleting these files:" 14 | $files | egrep -v $except 15 | $files | egrep -v $except | xargs rm 16 | echo "$count files cleaned." 17 | fi 18 | -------------------------------------------------------------------------------- /git-timecard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Create timecard histograms for a ref. 3 | 4 | For printing a chart (the default), pygooglechart is required: 5 | 6 | http://pygooglechart.slowchop.com/ 7 | 8 | Note that this assumes you've got an OS-X style "open" command. If 9 | you don't, it should be easy enough to adapt main to what you need. 10 | 11 | """ 12 | 13 | import sys 14 | 15 | import gitaggregates 16 | 17 | if __name__ == '__main__': 18 | th = gitaggregates.TimeHisto() 19 | th.add_logs(log_args=sys.argv[1:]) 20 | gitaggregates.open_chart(th) 21 | -------------------------------------------------------------------------------- /make-go-compilers: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git clean -dfx 4 | ./make.bash 5 | 6 | for f in runtime/rt0_*.s 7 | do 8 | b=`basename $f .s` 9 | os=`echo $b | awk -F_ '{print $2}'` 10 | arch=`echo $b | awk -F_ '{print $3}'` 11 | 12 | env GOOS=$os GOARCH=$arch ./make.bash --no-clean 13 | done 14 | 15 | go get -u golang.org/x/tools/cmd/benchcmp 16 | go get -u golang.org/x/tools/cmd/cover 17 | go get -u golang.org/x/tools/cmd/godoc 18 | go get -u golang.org/x/tools/cmd/goimports 19 | go get -u golang.org/x/tools/cmd/stringer 20 | go get -u golang.org/x/tools/cmd/vet 21 | -------------------------------------------------------------------------------- /git-commit-chart: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Create bar chart showing divergence over time. 3 | 4 | For printing a chart (the default), pygooglechart is required: 5 | 6 | http://pygooglechart.slowchop.com/ 7 | 8 | Note that this assumes you've got an OS-X style "open" command. If 9 | you don't, it should be easy enough to adapt main to what you need. 10 | 11 | """ 12 | 13 | import sys 14 | 15 | import gitaggregates 16 | 17 | if __name__ == '__main__': 18 | th = gitaggregates.ChangeOverTime() 19 | th.add_logs(log_args=sys.argv[1:]) 20 | gitaggregates.open_chart(th) 21 | -------------------------------------------------------------------------------- /git-emptybranch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Create a new, empty branch in the current repo. 4 | # 5 | 6 | # Verify we're actually in a git repositoriy. 7 | git rev-parse --git-dir > /dev/null || exit 66 8 | 9 | # Ensure we have a branch name. 10 | if [ $# -ne 1 ] 11 | then 12 | echo "What do you want to call this branch?" 13 | exit 64 14 | fi 15 | 16 | refname=refs/heads/$1 17 | 18 | # Let's not clobber an existing branch. 19 | if git rev-parse --verify -q $refname > /dev/null 20 | then 21 | echo "$refname already exists." 22 | exit 65 23 | fi 24 | 25 | # Do the work. 26 | git symbolic-ref -m "Creating an empty branch" HEAD $refname \ 27 | || exit $? 28 | rm .git/index || exit $? 29 | git clean -df || exit $? 30 | -------------------------------------------------------------------------------- /git-test-once: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run a command over a sequence of commits. 3 | # Example: 4 | # git test-once 'make clean && make compile && make test' 5 | 6 | . "$(git --exec-path)/git-sh-setup" 7 | require_work_tree 8 | 9 | get_hash_input() { 10 | echo "$1" 11 | git rev-parse 'HEAD^{tree}' 12 | git diff HEAD 13 | } 14 | 15 | already_passed() { 16 | h=`get_hash_input "$1" | git hash-object --stdin` 17 | git cat-file blob $h > /dev/null 2>/dev/null && echo "Already passed $h" 18 | } 19 | 20 | passed_on() { 21 | h=`get_hash_input "$1" | git hash-object -w --stdin` 22 | echo "Passed: $h." 23 | } 24 | 25 | echo "Testing $1" 26 | already_passed "$1" || ( eval "$1" && passed_on "$1" ) || passed_on "$1" 27 | -------------------------------------------------------------------------------- /go-manifest: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Tell me what versions of various things are in use. 3 | 4 | eval `go env` 5 | 6 | detect() { 7 | pkg="$1" 8 | dir="$2" 9 | 10 | cd "$dir" 11 | gitv=`git describe --always 2>/dev/null` 12 | if [ $? -eq 0 ] 13 | then 14 | echo "$pkg $gitv" 15 | else 16 | hgv=`hg identify -i` 17 | if [ $? -ne 0 ] 18 | then 19 | echo "Warning: Can't identify $pkg" 20 | else 21 | echo "$pkg $hgv" 22 | fi 23 | fi 24 | } 25 | 26 | process() { 27 | pkg="$1" 28 | dir=`go list -f {{.Dir}} "$pkg"` 29 | case "$dir" in 30 | $GOROOT*) 31 | # echo "$pkg is stdlib";; 32 | : 33 | ;; 34 | *) 35 | detect "$pkg" "$dir" 36 | esac 37 | } 38 | 39 | for pkg in `go list -f '{{ range .Deps }} {{.}} {{end}}' "$@"` 40 | do 41 | process $pkg 42 | done 43 | -------------------------------------------------------------------------------- /git-baseless-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # WHY!? 4 | # 5 | # The purpose of this command is to allow me to create a snapshot of a 6 | # tree without history. My primary use for such a thing is to 7 | # distribute large generated documentation sets as gh-pages for 8 | # github. 9 | # 10 | 11 | msg="$@" 12 | 13 | if [ -z "$msg" ] 14 | then 15 | echo "Usage: git baseless-commit my awesome commit message" 16 | exit 1 17 | fi 18 | 19 | thisbranch=`git symbolic-ref HEAD` 20 | oldhead=`git rev-parse HEAD` 21 | 22 | if [ $thisbranch != refs/heads/gh-pages ] 23 | then 24 | echo "You're currently on a branch called $thisbranch" 25 | echo "If you're OK destroying that branch, hit enter, otherwise hit ^C" 26 | read all_about_it 27 | fi 28 | 29 | tree=`git write-tree` 30 | commit=`echo "$msg" | git commit-tree $tree` 31 | git symbolic-ref -m "git baseless-commit" HEAD $thisbranch 32 | git reset --hard $commit 33 | 34 | echo "If you messed up and destroyed something on accident, you can" 35 | echo "still get your old head back: $oldhead" 36 | -------------------------------------------------------------------------------- /go-set-versions: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Reset versions based on a manifest file 3 | 4 | eval `go env` 5 | 6 | setGitVer() { 7 | pkg="$1" 8 | ver="$2" 9 | 10 | echo "Setting git version of $pkg to $ver" 11 | git reset --hard "$ver" 12 | } 13 | 14 | setHgVer() { 15 | pkg="$1" 16 | ver="$2" 17 | 18 | echo "Setting hg version of $pkg to $ver" 19 | hg checkout -C -r "$ver" 20 | } 21 | 22 | setVer() { 23 | pkg="$1" 24 | ver="$2" 25 | 26 | cd "$GOPATH/src/$pkg" || exit 1 27 | gitv=`git describe --always 2>/dev/null` 28 | 29 | if [ $? -eq 0 ] 30 | then 31 | if [ "$gitv" != "$ver" ] 32 | then 33 | setGitVer "$pkg" "$ver" 34 | fi 35 | else 36 | hgv=`hg identify -i` 37 | if [ $? -ne 0 ] 38 | then 39 | echo "Warning: Can't identify $pkg" 40 | else 41 | if [ "$hgv" != "$ver" ] 42 | then 43 | setHgVer "$pkg" "$ver" 44 | fi 45 | fi 46 | fi 47 | } 48 | 49 | cat $1 | while read pkg ver 50 | do 51 | setVer "$pkg" "$ver" 52 | done 53 | -------------------------------------------------------------------------------- /git-test-bisect: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run a command for a test. If it passes make a ref so that if called again on the same commit report success w/o re-testing. 3 | # Example: 4 | # git test-bisect origin/integrate.. 'make clean && make compile && make test' 5 | 6 | ref_name=test 7 | 8 | # The tree must be really really clean. 9 | if ! git update-index --ignore-submodules --refresh > /dev/null; then 10 | echo >&2 "cannot rebase: you have unstaged changes" 11 | git diff-files --name-status -r --ignore-submodules -- >&2 12 | exit 1 13 | fi 14 | diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --) 15 | case "$diff" in 16 | ?*) echo >&2 "cannot rebase: your index contains uncommitted changes" 17 | echo >&2 "$diff" 18 | exit 1 19 | ;; 20 | esac 21 | 22 | start_branch=`git rev-parse --symbolic-full-name HEAD | sed s,refs/heads/,,` 23 | git checkout `git rev-parse HEAD` > /dev/null 2>/dev/null 24 | 25 | good=`git rev-list $1 | tail -1` 26 | bad=`git rev-list $1 | head -1` 27 | echo "Bisecting $1 with $2" 28 | git bisect start 29 | git bisect good ${good} 30 | git bisect bad ${bad} 31 | git bisect run git test-once "$2" 32 | git bisect reset 33 | echo "All's well." 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2014 Dustin Sallings 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /git-masstimecard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Show a timecard across all projects. 3 | 4 | For printing a chart (the default), pygooglechart is required: 5 | 6 | http://pygooglechart.slowchop.com/ 7 | 8 | Note that this assumes you've got an OS-X style "open" command. If 9 | you don't, it should be easy enough to adapt main to what you need. 10 | """ 11 | 12 | import os 13 | import sys 14 | import exceptions 15 | 16 | import gitaggregates 17 | 18 | def resolve(path): 19 | dotgit = os.path.join(path, ".git") 20 | objects = os.path.join(path, "objects") 21 | if os.path.isdir(dotgit): 22 | return resolve(dotgit) 23 | elif os.path.isdir(objects): 24 | return path 25 | sys.stderr.write("Warning: Can't find a git repo for %s\n" % path) 26 | 27 | if __name__ == '__main__': 28 | th = gitaggregates.TimeHisto() 29 | # Separate log args from repo args 30 | repos = [] 31 | log_args = [] 32 | current = repos 33 | for a in sys.argv[1:]: 34 | if log_args is current: 35 | log_args.append(a) 36 | else: 37 | if a == '--': 38 | current = log_args 39 | else: 40 | p = resolve(a) 41 | if p: 42 | repos.append(p) 43 | 44 | th = gitaggregates.TimeHisto() 45 | for r in repos: 46 | th.add_logs(r, log_args) 47 | 48 | gitaggregates.open_chart(th) 49 | -------------------------------------------------------------------------------- /git-linecount: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import subprocess 5 | 6 | class Blob(object): 7 | 8 | def __init__(self, blob_id, size): 9 | self.blob_id = blob_id 10 | self.size = size 11 | self.lines = 0 12 | 13 | def count_lines(self): 14 | self.lines = 0 15 | 16 | if self.size < 2*1024*1024: 17 | args = ['git', 'cat-file', 'blob', self.blob_id] 18 | sub=subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 19 | for l in sub.stdout: 20 | self.lines += 1 21 | 22 | def load_blobs(ref): 23 | args = ['git', 'ls-tree', '-z', '-l', '-r', ref] 24 | sub=subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 25 | 26 | blobs = sub.stdout.read().split("\0") 27 | rv = [] 28 | for b in (b for b in blobs if b): 29 | info, filename = b.split("\t") 30 | mode, t, blob_id, size = info.split() 31 | 32 | assert t == 'blob' 33 | 34 | rv.append(Blob(blob_id, int(size))) 35 | 36 | return rv 37 | 38 | def rev_parse(ref): 39 | args = ['git', 'rev-parse', ref] 40 | sub = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 41 | return sub.stdout.read().strip() 42 | 43 | def name(ref): 44 | args = ['git', 'describe', ref] 45 | sub = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 46 | return sub.stdout.read().strip() 47 | 48 | if __name__ == '__main__': 49 | 50 | refs = sys.argv[1:] 51 | if not refs: 52 | refs = ['HEAD'] 53 | 54 | for ref in refs: 55 | ref = rev_parse(ref) 56 | 57 | blobs = load_blobs(ref) 58 | for b in blobs: 59 | b.count_lines() 60 | 61 | print "%s %d" % (name(ref), sum(b.lines for b in blobs)) 62 | -------------------------------------------------------------------------------- /git-alternate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: et 3 | 4 | from __future__ import with_statement 5 | 6 | import os 7 | import sys 8 | import commands 9 | import exceptions 10 | 11 | def nearest_git_objects(path): 12 | dotgit = os.path.join(path, ".git") 13 | objects = os.path.join(path, "objects") 14 | if os.path.isdir(dotgit): 15 | return nearest_git_objects(dotgit) 16 | elif os.path.isdir(objects): 17 | return objects 18 | raise exceptions.RuntimeError("Could not find git dir from " + path) 19 | 20 | def alt_path(git_path): 21 | return os.path.join(git_path, "info", "alternates") 22 | 23 | def read_objs(git_path): 24 | fn = alt_path(git_path) 25 | alts = set() 26 | if os.path.exists(fn): 27 | with open(fn) as f: 28 | alts = set([l.strip() for l in f.readlines()]) 29 | 30 | return alts 31 | 32 | def setup_alternates(from_objects, to_objects): 33 | alts = read_objs(from_objects) 34 | alts.add(to_objects) 35 | with open(alt_path(from_objects), "w") as f: 36 | f.write("\n".join(alts) + "\n") 37 | 38 | def here(): 39 | (e, o) = commands.getstatusoutput("git rev-parse --git-dir") 40 | if e != 0: 41 | raise exceptions.RuntimeError("This is not a git repo.") 42 | return nearest_git_objects(o.strip()) 43 | 44 | def setup_new_alternate(where): 45 | alt = nearest_git_objects(where) 46 | loc = here() 47 | print loc, "->", alt 48 | setup_alternates(loc, alt) 49 | 50 | def display_alternates(): 51 | p = here() 52 | alts = read_objs(p) 53 | print "Alternates for %s (%d):" % (p, len(alts)) 54 | for s in sorted(alts): 55 | print " %s" % s 56 | 57 | if __name__ == '__main__': 58 | if len(sys.argv) < 2: 59 | display_alternates() 60 | else: 61 | setup_new_alternate(sys.argv[1]) 62 | -------------------------------------------------------------------------------- /git-htmlchangelog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | 5 | import sys 6 | import subprocess 7 | 8 | import gitaggregates 9 | 10 | def read_tag(t): 11 | args = ['git', 'cat-file', 'tag', t] 12 | sub = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 13 | 14 | rv=None 15 | for l in sub.stdout: 16 | if rv is not None: 17 | rv.append(l) 18 | elif l.strip() == '': 19 | rv = [] 20 | 21 | return ''.join(rv) 22 | 23 | def emit(prev, r, f=sys.stdout): 24 | tagdesc = read_tag(r) 25 | 26 | c = gitaggregates.Contributors([prev + '..' + r]) 27 | chart = c.mk_chart() 28 | pie_url = chart.BASE_URL + '&'.join(chart.get_url_bits()) 29 | vdesc = "Changes from release %s to %s" % (prev, r) 30 | if not prev: 31 | vdesc = "Release " + r 32 | f.write("

%s

\n" % (vdesc)) 33 | f.write("""
contributors""" 34 | % pie_url) 35 | f.write("
%s
" % tagdesc) 36 | 37 | if __name__ == '__main__': 38 | 39 | if not sys.argv[1:]: 40 | sys.stderr.write("Need to list some tag objects.\n") 41 | sys.exit(1) 42 | 43 | title = "Changelog" 44 | try: 45 | with open(".git/description") as f: 46 | desc = f.read().strip() 47 | if not desc.startswith("Unnamed repository"): 48 | title= "Changelog for " + desc 49 | except: 50 | pass 51 | 52 | print """ 53 | 55 | 56 | 57 | 58 | %s 59 | 67 | 68 | 69 | 70 | """ % title 71 | 72 | print "

%s

" % title 73 | 74 | prev = '' 75 | 76 | pairs = [] 77 | 78 | for r in sys.argv[1:]: 79 | pairs.append((prev, r)) 80 | prev = r 81 | 82 | for prev, r in reversed(pairs): 83 | emit(prev, r) 84 | 85 | print "" 86 | -------------------------------------------------------------------------------- /git-reroot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Pick up all of the changes of a tree and reparent them elsewhere. 4 | 5 | Given some arbitrary sequence of commits, all of the changes from the 6 | sequence will be applied in the new location. 7 | 8 | This has an effect similar to rebase, but is purely content driven. 9 | Each commit is recorded with the exact state the tree was in, but 10 | repositioned on top of the current tree. This is likely the wrong 11 | tool for whatever job you're hoping to use it on. 12 | 13 | It will never have a conflict, but at the consequence of sometimes 14 | providing you with diffs that don't make a lot of sense. 15 | 16 | Read more about this here: 17 | 18 | """ 19 | 20 | import os 21 | import sys 22 | import commands 23 | import subprocess 24 | 25 | def run_cmd(cmd): 26 | exitstatus, outtext = commands.getstatusoutput(cmd) 27 | if exitstatus != 0: 28 | raise RuntimeException("Command failed.") 29 | return outtext 30 | 31 | def cleanup_log(commit, c): 32 | return c[:c.index("--" + commit + "--")] 33 | 34 | def recommit(commit_log, onto): 35 | commit, tree, an, ae, ad, cn, ce, cd, log = commit_log 36 | log = cleanup_log(commit, log) 37 | env = {'GIT_COMMITTER_NAME': cn, 'GIT_COMMITER_EMAIL': ce, 38 | 'GIT_AUTHOR_NAME': an, 'GIT_AUTHOR_EMAIL': ae, 39 | 'GIT_COMMITER_DATE': cd, 'GIT_AUTHOR_DATE': ad, 40 | 'PATH': os.getenv("PATH")} 41 | args=["git", "commit-tree", tree, '-p', onto] 42 | p = subprocess.Popen(args, stdin=subprocess.PIPE, 43 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) 44 | stdout, stderr = p.communicate(log) 45 | sys.stderr.write(stderr) 46 | p.wait() 47 | if p.returncode != 0: 48 | sys.exit(p.returncode) 49 | 50 | return stdout.strip() 51 | 52 | if __name__ == '__main__': 53 | 54 | log = run_cmd('git log --reverse --pretty=format:"%H%n%T%n%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b--%H--%x00" ' 55 | + sys.argv[1]) 56 | h = run_cmd("git rev-parse HEAD") 57 | commits=[s.split("\n", 8) for s in log.split("\0\n")] 58 | done=0 59 | for commit in commits: 60 | h = recommit(commit, h) 61 | done += 1 62 | sys.stdout.write(" %d/%d\r" % (done, len(commits))) 63 | sys.stdout.flush() 64 | 65 | print "The newly created history is available as " + h 66 | -------------------------------------------------------------------------------- /git-test-sequence: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run a command over a sequence of commits. 3 | # Example: 4 | # git test-sequence origin/master.. 'make clean && make test' 5 | 6 | . "$(git --exec-path)/git-sh-setup" 7 | require_work_tree 8 | 9 | t= 10 | force= 11 | run_once= 12 | ref_name=pass 13 | 14 | # The tree must be really really clean. 15 | if ! git update-index --ignore-submodules --refresh > /dev/null; then 16 | echo >&2 "cannot rebase: you have unstaged changes" 17 | git diff-files --name-status -r --ignore-submodules -- >&2 18 | exit 1 19 | fi 20 | diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --) 21 | case "$diff" in 22 | ?*) echo >&2 "cannot rebase: your index contains uncommitted changes" 23 | echo >&2 "$diff" 24 | exit 1 25 | ;; 26 | esac 27 | 28 | start_branch=`git rev-parse --symbolic-full-name HEAD | sed s,refs/heads/,,` 29 | git checkout `git rev-parse HEAD` > /dev/null 2>/dev/null 30 | 31 | cleanup() { 32 | git checkout $start_branch > /dev/null 2>/dev/null 33 | } 34 | 35 | already_passed() { 36 | obdata=${ref_name}-$t-$1 37 | obhash=`echo $obdata | git hash-object --stdin` 38 | git cat-file blob $obhash > /dev/null 2>/dev/null \ 39 | && echo "Already ${ref_name} $1" 40 | } 41 | 42 | passed_on() { 43 | obdata=${ref_name}-$t-$1 44 | echo $obdata | git hash-object -w --stdin > /dev/null 45 | echo "Passed: $1." 46 | } 47 | 48 | broke_on() { 49 | git log --pretty="format:Broke on %H (%s)%n" -n 1 $1 50 | cleanup 51 | exit 1 52 | } 53 | 54 | new_test() { 55 | echo "Testing $2" 56 | git reset --hard $v && eval "$2" && passed_on $1 || broke_on $v 57 | status=$? 58 | if test -n "$run_once"; then 59 | cleanup 60 | exit $status 61 | fi 62 | } 63 | 64 | 65 | while test $# != 0 66 | do 67 | case "$1" in 68 | --force) 69 | force=yes 70 | ;; 71 | --once) 72 | run_once=yes 73 | ;; 74 | --ref-name) 75 | ref_name=$2 76 | shift 77 | ;; 78 | *) 79 | break; 80 | ;; 81 | esac 82 | shift 83 | done 84 | 85 | t=`echo "$2" | git hash-object --stdin` 86 | 87 | for v in `git rev-list --reverse $1` 88 | do 89 | tree_ver=`git rev-parse "$v^{tree}"` 90 | test -z "$force" && already_passed $tree_ver || new_test $tree_ver "$2" 91 | done 92 | cleanup 93 | 94 | if test -n "$run_once"; then 95 | echo "All commits already passed for --once argument. Quiting." 96 | exit 127 97 | fi 98 | 99 | echo "All's well." 100 | -------------------------------------------------------------------------------- /git-lineowners: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import subprocess 5 | 6 | from collections import defaultdict 7 | 8 | class AuthorCounter(object): 9 | 10 | def __init__(self, rev, filename, size): 11 | self.rev = rev 12 | self.filename = filename 13 | self.size = size 14 | self.umap = defaultdict(lambda: 0) 15 | 16 | def count_authors(self): 17 | 18 | current_sha = '' 19 | current_user = '' 20 | commit_u_map = {} 21 | 22 | if self.size < 2*1024*1024: 23 | args = ['git', 'blame', '-M5', '-C5', '-p', self.rev, self.filename] 24 | sub=subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 25 | for l in sub.stdout: 26 | if l.startswith("\t"): 27 | self.umap[current_user] += 1 28 | else: 29 | try: 30 | if l.rstrip() == 'boundary': 31 | k, v = 'boundary', '' 32 | else: 33 | k, v = l.strip().split(' ', 1) 34 | except ValueError: 35 | sys.stderr.write("Error parsing %s on %s\n" % (repr(l), self.filename)) 36 | k = l 37 | v = '' 38 | if len(k) == 40 or k == 'boundary': # Assumed SHA 39 | current = '' 40 | current_sha = k 41 | if current_sha in commit_u_map: 42 | current_user = commit_u_map[current_sha] 43 | elif k == 'author': 44 | current_user = v 45 | commit_u_map[current_sha] = v 46 | 47 | def load_blobs(ref): 48 | args = ['git', 'ls-tree', '-z', '-l', '-r', ref] 49 | sub=subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 50 | 51 | blobs = sub.stdout.read().split("\0") 52 | rv = [] 53 | for b in (b for b in blobs if b): 54 | info, filename = b.split("\t") 55 | mode, t, blob_id, size = info.split() 56 | 57 | assert t == 'blob' 58 | 59 | rv.append(AuthorCounter(ref, filename, int(size))) 60 | 61 | return rv 62 | 63 | def rev_parse(ref): 64 | args = ['git', 'rev-parse', ref] 65 | sub = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 66 | return sub.stdout.read().strip() 67 | 68 | def name(ref): 69 | args = ['git', 'describe', ref] 70 | sub = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 71 | return sub.stdout.read().strip() 72 | 73 | def mergeDicts(inputs): 74 | rv = defaultdict(lambda: 0) 75 | for d in inputs: 76 | for k,v in d.iteritems(): 77 | rv[k] += v 78 | return rv 79 | 80 | def output(d): 81 | for v,k in sorted(((v,k) for k,v in d.iteritems()), reverse=True): 82 | print v, k 83 | 84 | if __name__ == '__main__': 85 | 86 | refs = sys.argv[1:] 87 | if not refs: 88 | refs = ['HEAD'] 89 | 90 | for ref in refs: 91 | ref = rev_parse(ref) 92 | 93 | blobs = load_blobs(ref) 94 | for b in blobs: 95 | b.count_authors() 96 | 97 | output(mergeDicts(b.umap for b in blobs)) 98 | 99 | -------------------------------------------------------------------------------- /git-tree-converge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Figure out where trees converge in branches when there may be no similar 4 | ancestry. 5 | 6 | Copyright (c) 2008 Dustin Sallings 7 | """ 8 | 9 | from __future__ import with_statement 10 | 11 | import sys 12 | import difflib 13 | import subprocess 14 | import collections 15 | 16 | trees={} 17 | commit_map={} 18 | commits={} 19 | 20 | def load_list(branch): 21 | t=[] 22 | trees[branch] = t 23 | cm = collections.defaultdict(list) 24 | commit_map[branch] = cm 25 | 26 | args = ['git', 'log', '--pretty=format:%T %h', branch] 27 | 28 | sub=subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 29 | 30 | for l in sub.stdout: 31 | a=l.strip().split() 32 | commit = a[-1] 33 | treeHash = a[0] 34 | t.append(treeHash) 35 | cm[treeHash].append(commit) 36 | 37 | def load_commits(): 38 | args = ['git', 'log', '--all', 39 | '--pretty=format:%h%x00%an%x00%ae%x00%cn%x00%ce%x00%s'] 40 | sub=subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 41 | 42 | for l in sub.stdout: 43 | hash, an, ae, cn, ce, desc=l.strip().split("\0") 44 | commits[hash] = (an, ae, cn, ce, desc) 45 | 46 | def commit_info(h): 47 | branch_a, ae, cn, ce, desc = commits[h] 48 | if branch_a == cn: 49 | ai = branch_a 50 | else: 51 | ai = "%s (committed by %s)" % (branch_a, cn) 52 | cl = ('%s' 53 | % (h, h)) 54 | return "
%s: %s
%s
" % (cl, ai, desc) 55 | 56 | def htmlify_col_list(col): 57 | if col: 58 | return "\n".join((commit_info(c) for c in col)) 59 | else: 60 | return " " 61 | 62 | def mk_row(cls, l, r): 63 | return ("%s%s" % (cls, l, r)) 64 | 65 | def emit_differing_lists(op, left_col, right_col): 66 | print mk_row(op, htmlify_col_list(left_col), htmlify_col_list(right_col)) 67 | 68 | def emit_identical_lists(op, left_col, right_col): 69 | for l,r in zip(left_col, right_col): 70 | print mk_row(op, commit_info(l), commit_info(r)) 71 | 72 | if __name__ == '__main__': 73 | branch_a, branch_b = sys.argv[1:] 74 | 75 | load_commits() 76 | load_list(branch_a) 77 | load_list(branch_b) 78 | 79 | print """ 80 | 81 | Tree Comparison from %(branch_a)s to %(branch_b)s 82 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | """ % {'branch_a': branch_a, 'branch_b': branch_b} 115 | a=trees[branch_a] 116 | b=trees[branch_b] 117 | sm = difflib.SequenceMatcher(a=a, b=b) 118 | for op, i1, i2, j1, j2 in sm.get_opcodes(): 119 | left=a[i1:i2] 120 | right=b[j1:j2] 121 | 122 | left_col=[commit_map[branch_a][tree].pop() for tree in left] 123 | right_col=[commit_map[branch_b][tree].pop() for tree in right] 124 | 125 | if op == 'equal': 126 | emit_identical_lists(op, left_col, right_col) 127 | else: 128 | emit_differing_lists(op, left_col, right_col) 129 | 130 | print "
%(branch_a)s%(branch_b)s
" 131 | -------------------------------------------------------------------------------- /gitaggregates.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A collection of git info aggregators for graph commands.""" 3 | 4 | import time 5 | import subprocess 6 | from collections import defaultdict 7 | 8 | class TimeHisto(object): 9 | 10 | def __init__(self): 11 | self.h = defaultdict(lambda: 0) 12 | 13 | def add_logs(self, directory=None, log_args=['HEAD']): 14 | args=['git'] 15 | if directory: 16 | args.append('--git-dir=' + directory) 17 | args.extend(['log', '--format=%ad', '--date=raw']) 18 | args.extend(log_args) 19 | sub = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 20 | 21 | for l in sub.stdout: 22 | t, offset = l.strip().split(' ') 23 | t = float(t) + int(offset) / 100 * 3600 24 | 25 | self.h[time.strftime("%w %H", time.gmtime(t))] += 1 26 | 27 | def dump(self): 28 | for h in range(24): 29 | for d in range(7): 30 | sys.stderr.write("%02d %d - %s\n" 31 | % (h, d, self.h["%d %02d" % (d, h)])) 32 | 33 | def to_gchart(self): 34 | from pygooglechart import ScatterChart 35 | chart = ScatterChart(800, 300, x_range=(-1, 24), y_range=(-1, 7)) 36 | 37 | chart.add_data([(h % 24) for h in range(24 * 8)]) 38 | 39 | d=[] 40 | for i in range(8): 41 | d.extend([i] * 24) 42 | chart.add_data(d) 43 | 44 | day_names = "Sun Mon Tue Wed Thu Fri Sat".split(" ") 45 | days = (0, 6, 5, 4, 3, 2, 1) 46 | 47 | sizes=[] 48 | for d in days: 49 | sizes.extend([self.h["%d %02d" % (d, h)] for h in range(24)]) 50 | sizes.extend([0] * 24) 51 | chart.add_data(sizes) 52 | 53 | chart.set_axis_labels('x', [''] + [str(h) for h in range(24)] + ['']) 54 | chart.set_axis_labels('y', [''] + [day_names[n] for n in days] + ['']) 55 | 56 | chart.add_marker(1, 1.0, 'o', '333333', 25) 57 | return chart.get_url() + '&chds=-1,24,-1,7,0,20' 58 | 59 | for l in sub.stdout: 60 | self.h[time.strftime("%w %H", time.localtime(float(l.strip())))] += 1 61 | 62 | def dump(self): 63 | for h in range(24): 64 | for d in range(7): 65 | sys.stderr.write("%02d %d - %s\n" 66 | % (h, d, self.h["%d %02d" % (d, h)])) 67 | 68 | def to_gchart(self): 69 | from pygooglechart import ScatterChart 70 | chart = ScatterChart(800, 300, x_range=(-1, 24), y_range=(-1, 7)) 71 | 72 | chart.add_data([(h % 24) for h in range(24 * 8)]) 73 | 74 | d=[] 75 | for i in range(8): 76 | d.extend([i] * 24) 77 | chart.add_data(d) 78 | 79 | day_names = "Sun Mon Tue Wed Thu Fri Sat".split(" ") 80 | days = (0, 6, 5, 4, 3, 2, 1) 81 | 82 | sizes=[] 83 | for d in days: 84 | sizes.extend([self.h["%d %02d" % (d, h)] for h in range(24)]) 85 | sizes.extend([0] * 24) 86 | chart.add_data(sizes) 87 | 88 | chart.set_axis_labels('x', [''] + [str(h) for h in range(24)] + ['']) 89 | chart.set_axis_labels('y', [''] + [day_names[n] for n in days] + ['']) 90 | 91 | chart.add_marker(1, 1.0, 'o', '333333', 25) 92 | return chart.get_url() + '&chds=-1,24,-1,7,0,20' 93 | 94 | class Contributors(object): 95 | 96 | width = 420 97 | height = 300 98 | max_entries = 6 99 | 100 | def __init__(self, log_args=['HEAD']): 101 | self.data=[] 102 | args = ['git', 'shortlog', '-sn'] + log_args 103 | sub = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 104 | 105 | for l in sub.stdout: 106 | commits, name = [x.strip() for x in l.split("\t")] 107 | commits = int(commits) 108 | self.data.append((name, commits)) 109 | 110 | def dump(self): 111 | sys.stderr.write(repr(self.data) + "\n") 112 | 113 | def mk_chart(self): 114 | from pygooglechart import PieChart2D 115 | chart = PieChart2D(self.width, self.height) 116 | 117 | data = self.data[:self.max_entries] 118 | if len(self.data) > len(data): 119 | remainder = sum(d[1] for d in self.data[self.max_entries:]) 120 | data.append(('Other', remainder)) 121 | 122 | chart.add_data([d[1] for d in data]) 123 | chart.set_pie_labels([d[0].split()[0] for d in data]) 124 | return chart 125 | 126 | def to_gchart(self): 127 | return self.mk_chart().get_url() 128 | 129 | class ChangeOverTime(object): 130 | 131 | width = 800 132 | height = 300 133 | max_entries = 6 134 | 135 | def __init__(self, log_args=['HEAD']): 136 | self.newdata = [] 137 | self.cumulative = [] 138 | self.cmax = 0 139 | self.max = 0 140 | 141 | def add_logs(self, directory=None, log_args=['HEAD']): 142 | args = ['git', 'log', '--pretty=format:%at'] + log_args 143 | sub = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 144 | 145 | dates = {} 146 | for l in sub.stdout: 147 | k = time.strftime("%F", time.localtime(float(l.strip()))) 148 | dates[k] = dates.get(k, 0) + 1 149 | 150 | total = 0 151 | for k,v in sorted(dates.items()): 152 | total += v 153 | self.max = max(self.max, v) 154 | self.newdata.append( (k, v) ) 155 | self.cumulative.append( (k, total) ) 156 | 157 | self.cmax = total 158 | 159 | def dump(self): 160 | sys.stderr.write(repr(self.data) + "\n") 161 | 162 | def mk_chart(self): 163 | from pygooglechart import SimpleLineChart 164 | chart = SimpleLineChart(self.width, self.height, colours=('91CF60', 'FC8D59')) 165 | 166 | all_labels = [d[0] for d in self.cumulative] 167 | 168 | chart.add_data([d[1] for d in self.cumulative]) 169 | chart.add_data([d[1] for d in self.newdata]) 170 | chart.set_axis_labels('y', range(0, self.cmax, (self.cmax / 4))) 171 | chart.set_axis_labels('x', [all_labels[x] for x in 172 | range(0, len(all_labels), (len(all_labels) / 4))]) 173 | return chart 174 | 175 | def to_gchart(self): 176 | return self.mk_chart().get_url() 177 | 178 | def open_chart(chartish): 179 | subprocess.check_call(['open', chartish.to_gchart()]) 180 | --------------------------------------------------------------------------------