├── .cfg-post.d └── git ├── .gitattributes ├── .gitignore ├── .shared_rc.d └── git-pager ├── .stow-local-ignore ├── .zsh └── functions │ ├── _gcd │ ├── _ggf │ ├── _git_shortcuts │ ├── gcd │ └── ggbmv ├── COPYING ├── README.md └── bin ├── auto-commit-daemon ├── auto-sync-daemon ├── gfind ├── gg ├── gg1 ├── gg1a ├── gg1s ├── ggAi ├── gga ├── ggaa ├── ggac ├── ggad ├── ggaf ├── ggaf0 ├── ggaf1 ├── ggafd ├── ggafn ├── ggag ├── ggag1 ├── ggal ├── ggan ├── ggana ├── gganrd ├── ggans ├── gganwa ├── ggap ├── ggas ├── ggasy ├── ggau ├── ggaul ├── ggaw ├── ggaw1 ├── ggawn ├── ggb ├── ggba ├── ggbac ├── ggbc ├── ggbl ├── ggbm ├── ggbnm ├── ggbr ├── ggc ├── ggcg ├── ggci ├── ggcia ├── ggciaa ├── ggcl ├── ggco ├── ggcp ├── ggcpa ├── ggcpc ├── ggcpm ├── ggct ├── ggd ├── ggdc ├── ggdch ├── ggdcm ├── ggde ├── ggdt ├── ggdtn ├── ggdts ├── ggdtsw ├── ggdw ├── gge ├── ggf ├── ggfa ├── ggff ├── ggfp ├── ggfps ├── ggft ├── ggg ├── gggl ├── ggh ├── gghd ├── ggi ├── ggk ├── ggkc ├── ggks ├── ggl ├── ggl1 ├── ggl1l ├── ggla ├── gglcl ├── gglg ├── gglh ├── ggli ├── ggll ├── ggll1 ├── gglla ├── gglm ├── gglp ├── gglpg ├── gglpw ├── ggls ├── gglsi ├── gglsu ├── ggm ├── ggma ├── ggmb ├── ggme ├── ggmo ├── ggmt ├── ggmv ├── ggmx ├── ggmxd ├── ggo ├── ggo1 ├── ggop ├── ggopw ├── ggos ├── ggosw ├── ggpb ├── ggpbb ├── ggph ├── ggpl ├── ggplff ├── ggplr ├── ggr ├── ggrb ├── ggrba ├── ggrbc ├── ggrbi ├── ggrbim ├── ggrbm ├── ggrbs ├── ggre ├── ggrea ├── ggrer ├── ggres ├── ggrh ├── ggrl ├── ggrll ├── ggrln ├── ggrls ├── ggrm ├── ggrp ├── ggrs ├── ggs ├── ggsa ├── ggsb ├── ggsbf ├── ggsbs ├── ggsbu ├── ggsbur ├── ggsh ├── ggsh1 ├── ggsh1l ├── ggshs ├── ggshsw ├── ggst ├── ggsu ├── ggsup ├── ggt ├── ggtD ├── ggtc ├── ggtd ├── ggtps ├── ggup ├── ggur ├── ggw ├── ggwc ├── ggwm ├── git-add-prefix ├── git-annex-clean ├── git-annex-clean-sync ├── git-annex-finddups ├── git-auto-commit ├── git-cdup ├── git-check-email ├── git-cherry-menu ├── git-compare ├── git-compare-upstream ├── git-deps ├── git-down-remote-ignore-opts ├── git-dwim ├── git-export-bz2 ├── git-find-blob ├── git-find-missing-submodule-commits ├── git-head ├── git-icing ├── git-ls-dir ├── git-ls-emacs-lock-files ├── git-merged ├── git-mix ├── git-mixdown ├── git-new-workdir ├── git-prefix ├── git-remote-annex-is-ignored ├── git-remote-is-up ├── git-rewrite-author ├── git-rewrite-author-filter ├── git-rewrite-committer ├── git-rewrite-committer-filter ├── git-rewrite-date ├── git-rm-merged-orphan-branches ├── git-rnotes ├── git-root ├── git-safe-push-to-checkout ├── git-sed-range ├── git-set-upstream ├── git-should-ping-remote-annex ├── git-submodule-foreach-shorten ├── git-svn-fast-show-ignore ├── git-svn-revert-multi ├── git-sync-upstream ├── git-tag-patchset ├── git-upstream ├── git-url-rewrite ├── git-wip ├── qg ├── svnignore2gitexclude └── tigs /.cfg-post.d/git: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! which git >&/dev/null; then 4 | echo "git not found; aborting .cfg-post.d/git" >&2 5 | exit 1 6 | fi 7 | 8 | if which zsh >&/dev/null; then 9 | zsh -c zrec 10 | fi 11 | 12 | ggkg 13 | 14 | # https://github.blog/2022-06-29-improve-git-monorepo-performance-with-a-file-system-monitor/ 15 | git config --global core.fsmonitor true 16 | git config --global core.untrackedcache true 17 | 18 | git config --global color.ui auto 19 | git config --global color.pager true 20 | git config --global color.diff auto 21 | 22 | git config --global sendemail.confirm always 23 | 24 | # Paging 25 | #git config --global core.pager $PAGER 26 | git config --global --bool pager.diff-tree true 27 | 28 | git config --global --bool rebase.autosquash true 29 | git config --global pull.ff only 30 | 31 | git config --global push.default >/dev/null || \ 32 | git config --global push.default upstream 33 | git config --global remote.pushDefault github 34 | 35 | # tarsius recommends "always" as the sensible default, not "true": 36 | # https://github.com/magit/magit/issues/4508#issuecomment-923177662 37 | git config --global branch.autoSetupMerge always 38 | 39 | git config --global --bool merge.defaultToUpstream true 40 | git config --global merge.tool kdiff3 41 | git config --global merge.conflictStyle diff3 42 | git config --global core.attributesfile ~/.gitattributes 43 | git config --global core.excludesfile ~/.gitignore 44 | git config --global receive.denyCurrentBranch updateInstead 45 | git config --global --bool rerere.enabled true 46 | 47 | git config --global diff.org.xfuncname "^\\*+ +(.*)$" 48 | git config --global diff.lisp.xfuncname "^(\\(def.+)" 49 | git config --global diff.sclang.xfuncname "^[[:space:]]*(\*?[[:alnum:]_]+ *\\{.*)" 50 | git config --global diff.schelp.xfuncname \ 51 | "^((TITLE|SUMMARY|DESCRIPTION|(SUB)?SECTION|(CLASS|INSTANCE)METHODS|PRIVATE|METHOD|EXAMPLES)::.*)" 52 | git config --global diff.solidity.xfuncname \ 53 | "^[[:space:]]*((contract[[:space:]]|constructor[[:space:]]*\\(|function[[:space:]]).*)" 54 | 55 | # Work around port blocking at hotels etc. 56 | # See file:git.org::*protocols 57 | # 58 | # This is not a good idea to do globally, because not all sites use the same 59 | # path for both protocols. For example: 60 | # 61 | # https://libvirt.org/downloads.html#git 62 | # 63 | #git config --global url."https://".insteadOf git:// 64 | 65 | git config --global credential.helper 'cache --timeout=3600' 66 | 67 | git config --global init.defaultBranch main 68 | 69 | if which difft >&/dev/null; then 70 | # git config --global diff.tool difftastic 71 | git config --global difftool.prompt false 72 | git config --global difftool.difftastic.cmd difft "$LOCAL" "$REMOTE" 73 | git config --global pager.difftool true 74 | fi 75 | 76 | if which delta >&/dev/null; then 77 | # git config --global core.pager delta 78 | git config --global interactive.diffFilter 'delta --color-only' 79 | git config --global delta.light false 80 | git config --global delta.navigate true 81 | git config --global delta.side-by-side true 82 | git config --global delta.line-numbers true 83 | git config --global diff.colorMoved default 84 | fi 85 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.bibtex diff=bibtex 2 | *.cpp diff=cpp 3 | *.md diff=markdown 4 | *.pl diff=perl 5 | *.perl diff=perl 6 | *.php diff=php 7 | *.py diff=python 8 | *.python diff=python 9 | *.rb diff=ruby 10 | *.ruby diff=ruby 11 | *.tex diff=tex 12 | *.el diff=lisp 13 | *.scm diff=lisp 14 | *.sc diff=sclang 15 | *.scd diff=sclang 16 | *.schelp diff=schelp 17 | *.sol diff=solidity 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *,v 2 | RCS 3 | CVS 4 | Attic 5 | *~ 6 | .#* 7 | \#*# 8 | *.orig 9 | *.old 10 | *.rej 11 | *.bak 12 | *.o 13 | *.elc 14 | *.pyc 15 | *.class 16 | *.flc 17 | *.zwc 18 | _darcs 19 | {arch} 20 | .arch-ids 21 | .arch-inventory 22 | ,,* 23 | .svn 24 | BitKeeper 25 | .pcl-cvs-cache 26 | pm_to_blib 27 | blib 28 | .nfs* 29 | pod2html-*cache 30 | tags 31 | TAGS 32 | .xvpics 33 | .emacs.desktop 34 | .emacs.backup 35 | ++build 36 | _build 37 | Build 38 | .zshhistory 39 | .bash_history 40 | *.rpmsave 41 | *.cfgsave.* 42 | autom4te.cache 43 | .hg 44 | .git 45 | .directory 46 | .autosync.log 47 | .autosync.pid 48 | .osc 49 | _service:* 50 | .kdirstat.cache.gz 51 | .*.mscz, 52 | build-tmp/ 53 | .~lock.*.od?# 54 | .*.swp 55 | .mypy_cache/ 56 | .log/ 57 | -------------------------------------------------------------------------------- /.shared_rc.d/git-pager: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GIT_PAGER="less $LESS -F -S" 4 | 5 | GIT_PAGER="${GIT_PAGER/ -j[0-9] / }" 6 | GIT_PAGER="${GIT_PAGER/ -j[0-9][0-9] / }" 7 | # GIT_PAGER="${GIT_PAGER/-a / }" 8 | 9 | if which difft >&/dev/null; then 10 | export GIT_EXTERNAL_DIFF=difft 11 | elif which delta >&/dev/null; then 12 | export DELTA_PAGER="$GIT_PAGER" 13 | unset GIT_PAGER 14 | return 15 | fi 16 | 17 | git_diff_highlight= 18 | 19 | case "$GIT_PAGER_MODE" in 20 | log) 21 | git_pager_regexp='^commit [0-9a-f]{7}.*' 22 | git_diff_highlight=y 23 | ;; 24 | diff) 25 | # ggsh could be showing a merge commit 26 | if [ "$GIT_EXTERNAL_DIFF" == difft ]; then 27 | git_pager_regexp=' --- [0-9]+/[0-9]+ ---' 28 | else 29 | git_pager_regexp='^(diff|commit) .*' 30 | fi 31 | git_diff_highlight=y 32 | ;; 33 | commit) 34 | git_pager_regexp='^(commit [0-9a-f]{7}.*|diff .*)' 35 | git_diff_highlight=y 36 | ;; 37 | oneline) 38 | # FIXME: what's the idea behind this? 39 | git_pager_regexp='^[0-9a-f]{7}[0-9a-f]*' 40 | ;; 41 | default|"") 42 | #git_pager_regexp='^(commit [0-9a-f]{7}.*|diff .*|[0-9a-f]{7}[0-9a-f]*)' 43 | unset git_pager_regexp 44 | ;; 45 | none) 46 | unset git_pager_regexp 47 | GIT_PAGER="" 48 | ;; 49 | *) 50 | echo "ERROR: unrecognised value for GIT_PAGER_MODE [$GIT_PAGER_MODE]" >&2 51 | ;; 52 | esac 53 | 54 | [ -n "$git_pager_regexp" ] && GIT_PAGER="$GIT_PAGER +/'$git_pager_regexp'" 55 | 56 | if which diff-highlight >/dev/null 2>&1; then 57 | [ -n "$git_diff_highlight" ] && [ -n "$GIT_PAGER" ] && GIT_PAGER="diff-highlight | $GIT_PAGER" 58 | fi 59 | -------------------------------------------------------------------------------- /.stow-local-ignore: -------------------------------------------------------------------------------- 1 | # It's important that .gitignore is absent from this list, so 2 | # that the .gitignore in this repo gets installed by stow as 3 | # ~/.gitignore. 4 | 5 | # However, .git must be in this list: 6 | \.git 7 | /bin/git-deps 8 | 9 | .*,v 10 | RCS 11 | CVS 12 | Attic 13 | .*~ 14 | \.\#.* 15 | \#.*\# 16 | .*\.orig 17 | .*\.old 18 | .*\.rej 19 | .*\.bak 20 | .*\.o 21 | .*\.elc 22 | .*\.pyc 23 | .*\.class 24 | .*\.flc 25 | .*\.zwc 26 | \.hg 27 | _darcs 28 | \{arch\} 29 | \.arch-ids 30 | \.arch-inventory 31 | ,,.* 32 | \.svn 33 | BitKeeper 34 | \.pcl-cvs-cache 35 | pm_to_blib 36 | blib 37 | \.nfs.+ 38 | pod2html-.+cache 39 | tags 40 | TAGS 41 | \.xvpics 42 | \.emacs\.desktop 43 | \.emacs\.backup 44 | \+\+build 45 | _build 46 | Build 47 | \.zshhistory 48 | \.bash_history 49 | .*\.rpmsave 50 | .*\.cfgsave\. 51 | config\.log 52 | autom4te\.cache 53 | .*\.log 54 | README.* 55 | COPYING 56 | -------------------------------------------------------------------------------- /.zsh/functions/_gcd: -------------------------------------------------------------------------------- 1 | #compdef gcd 2 | 3 | _path_files -/ -W $(git root) 4 | -------------------------------------------------------------------------------- /.zsh/functions/_ggf: -------------------------------------------------------------------------------- 1 | #compdef ggf 2 | 3 | #autoload +X _git 4 | __git_remotes "$@" 5 | -------------------------------------------------------------------------------- /.zsh/functions/_git_shortcuts: -------------------------------------------------------------------------------- 1 | #compdef gga ggb ggbmv ggci ggco ggd ggh ggk ggl ggmv ggr ggrb ggrbi ggrl ggs ggt 2 | 3 | # Note that as of 4.3.4 there is a bug in _git that the invocation of 4 | # _call_function to the helper function for the specific command (such 5 | # as _git-branch) happens before the helper function has actually been 6 | # defined. So the first time you hit TAB, _call_function fails but 7 | # the helper function gets defined straight after the failure, so that 8 | # when you hit TAB again it works. 9 | 10 | case "$service" in 11 | gga) git_fn=add ;; 12 | ggb) git_fn=branch ;; 13 | ggbmv) git_fn=branch ;; 14 | ggci) git_fn=commit ;; 15 | ggco) git_fn=checkout ;; 16 | ggd) git_fn=diff ;; 17 | # ggde) git_fn=describe ;; 18 | ggk) git_fn=log ;; 19 | ggl) git_fn=log ;; 20 | ggmv) git_fn=mv ;; 21 | ggr) git_fn=reset ;; 22 | ggre) git_fn=remote ;; 23 | ggrp) git_fn=rev-parse ;; 24 | ggrb|ggrbi) 25 | git_fn=rebase ;; 26 | # ggrl) git_fn=reflog ;; 27 | # ggrm) git_fn=rm ;; 28 | ggs) git_fn=status ;; 29 | # ggsh) git_fn=show ;; 30 | ggt) git_fn=tag ;; 31 | *) echo "BUG in _git_shortcuts: $service not supported"; return 1 ;; 32 | esac 33 | 34 | if true; then 35 | # Simple way 36 | service=git-$git_fn 37 | words[1]=$service 38 | _git "$@" 39 | else 40 | # Grr... can't get this way to work. Eventually _arguments calls 41 | # comparguments which seems to get confused, perhaps by the change 42 | # of the number of words, and we end up with 'no more arguments'. 43 | words[1,2]=(git $git_fn) 44 | CURRENT=3 45 | _normal "$@" 46 | #_dispatch -s git git /usr/bin/git -default- 47 | fi 48 | -------------------------------------------------------------------------------- /.zsh/functions/gcd: -------------------------------------------------------------------------------- 1 | #autoload 2 | 3 | root=$( git root ) 4 | cd "$root/$1" 5 | -------------------------------------------------------------------------------- /.zsh/functions/ggbmv: -------------------------------------------------------------------------------- 1 | #autoload 2 | 3 | me="$(print -P %N)" 4 | 5 | if [[ "$1" == "--help" ]]; then 6 | cat <&2 7 | Usage: $me [OPTION]... BRANCH 8 | Renames git branch BRANCH, allowing interactive editing of the new name. 9 | 10 | EOF 11 | #The following options are passed on to git-branch(1): 12 | # -b, -u 13 | return 0 14 | fi 15 | 16 | # opts=() 17 | # while getopts :bu opt; do 18 | # if [[ $opt = "?" ]]; then 19 | # print "$me: unrecognized option: -$OPTARG" >&2 20 | # print "Try \`$me --help' for more information." >&2 21 | # return 1 22 | # fi 23 | # opts=( "$opts[@]" "-$opt" ) 24 | # done 25 | # (( OPTIND > 1 )) && shift $(( OPTIND - 1 )) 26 | 27 | if (( $# == 0 )); then 28 | print "$me: missing branch argument" 29 | return 1 30 | fi 31 | 32 | if (( $# > 1 )); then 33 | print "$me: too many arguments" 34 | return 1 35 | fi 36 | 37 | src="$1" 38 | 39 | if ! git rev-parse "$src" >&/dev/null; then 40 | print "$me: $src is not a valid branch" 41 | return 1 42 | fi 43 | 44 | dest="$src" 45 | vared -p 'New branch name: ' dest 46 | 47 | if [[ "$src" == "$dest" ]]; then 48 | print "$me: no change in name" 49 | return 1 50 | fi 51 | 52 | if git rev-parse "$dest" >&/dev/null; then 53 | echo -n "$me: $dest already exists; replace? " 54 | if ! read -q replace; then 55 | echo "Aborted." 56 | return 1 57 | fi 58 | git branch -M "$src" "$dest" 59 | else 60 | git branch -m "$src" "$dest" 61 | fi 62 | 63 | 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adam's git configuration files and utilities 2 | 3 | This is my collection of configuration files and utilities for 4 | [git](http://git-scm.com/). 5 | 6 | ## CONTENTS 7 | 8 | Contents include: 9 | 10 | ### Management of multiple concurrent branches 11 | 12 | * [`git-mixdown`](https://github.com/aspiers/git-config/blob/master/bin/git-mixdown) - [mix down](http://en.wikipedia.org/wiki/Audio_mixing_(recorded_music)) multiple branches into a single working branch. Useful for testing a combination of bugfixes / features at once via a throw-away temporary working branch. 13 | * [`git-mix`](https://github.com/aspiers/git-config/blob/master/bin/git-mix) - syntactic sugar for configuring `git mixdown` 14 | * [`ggrbm`](https://github.com/aspiers/git-config/blob/master/bin/ggrbm) - "Git Rebase Multiple", for automated rebasing of multiple branches as a single workflow. 15 | 16 | See also [`git-explode`](https://github.com/aspiers/git-explode) for 17 | automatically separating a single git branch into a number of smaller 18 | branches which are textually independent. 19 | 20 | ### Upstream branch status reporting / management 21 | 22 | * [`git-upstream`](https://github.com/aspiers/git-config/blob/master/bin/git-upstream) - obtain the upstream tracking branch (N.B. depends on `git-head`, `git-root`, and `git-prefix`) 23 | * [`git-set-upstream`](https://github.com/aspiers/git-config/blob/master/bin/git-set-upstream) - set the upstream tracking branch (N.B. depends on `git-head` and `git-root`) 24 | * [`git-merged`](https://github.com/aspiers/git-config/blob/master/bin/git-merged) - show which branches are merged into upstream or the given commit-ish (N.B. depends on `git-root` and `git-upstream`) 25 | * [`git-compare`](https://github.com/aspiers/git-config/blob/master/bin/git-compare) - compare two references to find out how far one is ahead/behind of the other 26 | * [`git-compare-upstream`](https://github.com/aspiers/git-config/blob/master/bin/git-compare-upstream) - set the upstream tracking branch (N.B. depends on `git-compare`, `git-head`, and `git-upstream`, and hence also on `git-root` and and `git-prefix`) 27 | * [`git-wip`](https://github.com/aspiers/git-config/blob/master/bin/git-wip) - returns true if the current repo has any work in progress, i.e. commits out of sync with upstream, or unmodified/untracked files 28 | * [`git-rm-merged-orphan-branches`](https://github.com/aspiers/git-config/blob/master/bin/git-rm-merged-orphan-branches) - remove local branches which have been merged and are not in any other remote. Use with care!! 29 | * [`git-icing`](https://github.com/aspiers/git-config/blob/master/bin/git-icing) - a tasty wrapper around `git cherry` which adds a splash of colour, and blacklisting of commits which should never be upstreamed. 30 | * [`git-cherry-menu`](https://github.com/aspiers/git-config/blob/master/bin/git-cherry-menu) - an interactive wrapper around `git icing` and `git notes` which makes it easy to cherry-pick and/or blacklist non-upstreamed commits. 31 | * [`git-rnotes`](https://github.com/aspiers/git-config/blob/master/bin/git-rnotes) - a wrapper around `git notes` which makes it easier to share notes to and from remote repositories 32 | * [`git-deps`](https://github.com/aspiers/git-deps) - **MOVED TO DEDICATED REPO** automatically detect dependencies between commits 33 | * [`git-tag-patchset`](https://github.com/aspiers/git-config/blob/master/bin/git-tag-patchset) - quick way of tagging patch sets as they get uploaded to Gerrit, to keep track of a review's history, e.g. `git review && git tag-patchset`. 34 | 35 | ### History rewriting 36 | 37 | * [`git-rewrite-author`](https://github.com/aspiers/git-config/blob/master/bin/git-rewrite-author) - rewrite the author for a range of commits 38 | * [`git-rewrite-committer`](https://github.com/aspiers/git-config/blob/master/bin/git-rewrite-committer) - rewrite the committer for a range of commits 39 | * [`git-sed-range`](https://github.com/aspiers/git-config/blob/master/bin/git-sed-range) - run a sed script over every log message in a range of commits 40 | * [`git-add-prefix`](https://github.com/aspiers/git-config/blob/master/bin/git-add-prefix) - add a prefix string to every log message in a range of commits 41 | 42 | ### Management of remotes 43 | 44 | * [`git-url-rewrite`](https://github.com/aspiers/git-config/blob/master/bin/git-url-rewrite) - convenient interface for setting [git URL rewrites via `url.$url.insteadOf`](http://qa-rockstar.livejournal.com/9961.html) 45 | * [`git-annex-clean-sync`](https://github.com/aspiers/git-config/blob/master/bin/git-annex-clean-sync) - a wrapper around [`git annex sync`](https://git-annex.branchable.com/sync/) which avoids trying to touch ignored or unavailable remotes. 46 | 47 | ### Automatic committing and syncing 48 | 49 | * [`git-auto-commit`](https://github.com/aspiers/git-config/blob/master/bin/git-auto-commit) - 50 | automatically commits files according to the `autocommit` [git 51 | attribute](https://git-scm.com/docs/gitattributes). For example, if 52 | `.gitattributes` contains: 53 | 54 | *.org autocommit=min-age=+5m 55 | 56 | then any file ending in `.org` which is newly added (i.e. not in the 57 | index yet) or has unstaged changes, and whose last commit time and 58 | last mtime are both over 5 minutes, will be automatically staged and 59 | included in an automatic commit for this invocation of the commit. 60 | Files which have staged changes are assumed to be part of an 61 | unfinished manual commit process, and are therefore skipped. 62 | 63 | * [`auto-commit-daemon`](https://github.com/aspiers/git-config/blob/master/bin/auto-commit-daemon) - 64 | a wrapper around 65 | [`git-auto-commit`](https://github.com/aspiers/git-config/blob/master/bin/git-auto-commit) 66 | to run it perodically. This can be configured as a per-user systemd 67 | service, e.g. by placing the following in 68 | `~/.config/systemd/user/auto-commit-my-repo.service`: 69 | 70 | [Service] 71 | ExecStart=/bin/sh -c "/path/to/auto-commit-daemon /home/me/my/repo" 72 | Restart=always 73 | NoNewPrivileges=true 74 | SyslogIdentifier=auto-commit-my-repo 75 | Environment=SLEEP=1m 76 | 77 | [Install] 78 | WantedBy=default.target 79 | 80 | * [`auto-sync-daemon`](https://github.com/aspiers/git-config/blob/master/bin/auto-sync-daemon) - 81 | a wrapper around 82 | [`git-annex-clean-sync`](https://github.com/aspiers/git-config/blob/master/bin/git-annex-clean-sync) 83 | which runs it whenever `master` or `synced/master` or 84 | `synced/git-annex` are updated. When run across a network of 85 | remotes, it will keep the `master` branch in sync across all of 86 | them. Works well in combination with setting 87 | `receive.denyCurrentBranch` to `updateInstead`, and using 88 | `git-safe-push-to-checkout` (see below) as the `push-to-checkout` 89 | hook. 90 | 91 | * [`git-safe-push-to-checkout`](https://github.com/aspiers/git-config/blob/master/bin/git-safe-push-to-checkout) - 92 | a smarter `push-to-checkout` hook for when `receive.denyCurrentBranch` 93 | is set to `updateInstead`. It's near-identical to git's default 94 | behaviour when no `push-to-checkout` hook is provided; however it 95 | additionally bails if we have emacs lockfiles indicating edits in 96 | progress for files which would be changed by the push-to-checkout. 97 | This means that push-to-checkout works more safely and doesn't 98 | rewrite files which are currently being edited in emacs with unsaved 99 | changes. It can be installed via: 100 | 101 | ln -s `which git-safe-push-to-checkout` $my_repo/.git/hooks/push-to-checkout 102 | 103 | ### Mapping files/blobs back to commits 104 | 105 | * [`git-ls-dir`](https://github.com/aspiers/git-config/blob/master/bin/git-ls-dir) - 106 | list files in a git repo tree together with the commits which most 107 | recently touched them ([see a 108 | screenshot](http://stackoverflow.com/a/8774800/179332)) 109 | 110 | * [`git-find-blob`](https://github.com/aspiers/git-config/blob/master/bin/git-find-blob) - 111 | find which commits contain a given (non-abbreviated) blob 112 | 113 | ### Sanity-checking submodule references 114 | 115 | * [`git-find-missing-submodule-commits`](https://github.com/aspiers/git-config/blob/master/bin/git-find-missing-submodule-commits) - 116 | find dangling references to submodule commits 117 | 118 | ### Helper scripts 119 | 120 | Little shell wrappers to make the git porcelain and higher-level scripts a little more beautiful. 121 | 122 | * [`git-head`](https://github.com/aspiers/git-config/blob/master/bin/git-head) - obtain the current branch 123 | * [`git-root`](https://github.com/aspiers/git-config/blob/master/bin/git-root) - obtain the absolute path to the root (top-level) directory of the repository we're currently in 124 | * [`git-prefix`](https://github.com/aspiers/git-config/blob/master/bin/git-prefix) - obtain the current directory path relative to the root (top-level) directory of the repository 125 | * [`git-cdup`](https://github.com/aspiers/git-config/blob/master/bin/git-cdup) - obtain the relative path from the current directory to the root (top-level) directory of the repository 126 | * [`gfind`](https://github.com/aspiers/git-config/blob/master/bin/gfind) - like find(1), but only lists files tracked by git 127 | 128 | ### Shorthand wrappers 129 | 130 | * [`gg*`](https://github.com/aspiers/git-config/tree/master/bin/) - a whole bunch of wrappers around standard git commands to reduce the number of keystrokes required and add a little bit of polish to the interface here and there. 131 | 132 | ### ... and more! 133 | 134 | Feel free to [browse the `bin/` directory](https://github.com/aspiers/git-config/tree/master/bin) :-) 135 | 136 | ## INSTALLATION 137 | 138 | This repository is designed to be 139 | [stowed](http://www.gnu.org/software/stow/) directly into your home 140 | directory: 141 | 142 | git clone git://github.com/aspiers/git-config.git 143 | stow -d . -t ~ git-config 144 | 145 | However if you only want to cherry-pick bits and pieces then you can 146 | easily just copy or symlink them in manually. Just be aware that some 147 | of the files depend on other files, some of which are in this 148 | repository, some of which are in other repositories such as 149 | https://github.com/aspiers/shell-env. 150 | 151 | ## LICENSE 152 | 153 | The software in this repository is free software: you can redistribute 154 | it and/or modify it under the terms of the GNU General Public License 155 | as published by the Free Software Foundation, either version 3 of the 156 | License, or (at your option) any later version. 157 | 158 | This software is distributed in the hope that it will be useful, but 159 | WITHOUT ANY WARRANTY; without even the implied warranty of 160 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 161 | General Public License for more details. 162 | 163 | You should have received a copy of the GNU General Public License 164 | along with this program. If not, see . 165 | 166 | 167 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/aspiers/git-config/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 168 | 169 | -------------------------------------------------------------------------------- /bin/auto-commit-daemon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | debug= 4 | if [[ "$1" == '-d' ]]; then 5 | debug=-d 6 | shift 7 | fi 8 | 9 | if [ $# != 1 ]; then 10 | cat <&2 11 | Usage: $(basename $0) [-d] REPO-DIR 12 | EOF 13 | exit 1 14 | fi 15 | 16 | repo_dir="$1" 17 | 18 | for var in SLEEP; do 19 | if [ -z "${(P)var}" ]; then 20 | echo >&2 "Error: \$$var not set; aborting" 21 | exit 1 22 | fi 23 | done 24 | 25 | if [ -n "$MIN_AGE" ]; then 26 | echo >&2 "WARNING: $0 should not have \$MIN_AGE set" 27 | fi 28 | 29 | cd "$repo_dir" 30 | 31 | for var in name email; do 32 | if ! git config user.$var >/dev/null; then 33 | echo >&2 "Error: user.$var not set in git config; aborting" 34 | exit 1 35 | fi 36 | done 37 | 38 | while true; do 39 | git auto-commit $debug 40 | sleep "$SLEEP" 41 | done 42 | -------------------------------------------------------------------------------- /bin/auto-sync-daemon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | BRANCH=$( git config --default master auto-sync.branch ) 4 | 5 | process_inotify_batch () { 6 | while read dir action file; do 7 | # echo >&2 "inotifywait: dir=$dir action=$action file=$file" 8 | try_sync 9 | 10 | # This should not be unnecessary since we are relying on inotifywait 11 | # to only return a single event; the filtering is done by that 12 | # process, not this loop. 13 | # echo >&2 "inotifywait: break" 14 | break 15 | done 16 | } 17 | 18 | try_sync () { 19 | if check_ssh; then 20 | # Skip sync if we found lockfiles which indicate that 21 | # performing a git-annex sync might be problematic due to 22 | # unsaved local changes. 23 | # 24 | # Unfortunately there is no easy way to add a check for lock 25 | # files based on which files the pending git merge run by 26 | # git-annex would change, because there isn't a git hook which 27 | # runs right at the beginning of the merge: 28 | # 29 | # https://stackoverflow.com/questions/76596057/how-to-implement-custom-checks-before-starting-a-git-merge 30 | # 31 | # So instead we have to do a wider blanket check for lock 32 | # files corresponding to files which would be changed by the 33 | # sync are currently being edited. This is better than 34 | # checking when *any* files in the repo are being edited, 35 | # but it's still overkill relative to what we could do if 36 | # there was a pre-merge git hook. 37 | tmp="$(mktemp --tmpdir auto-sync.lockfiles.XXXXXXX)" 38 | git config -z --get-all auto-sync.lockfile > "$tmp" 39 | locked=$(git ls-files --other --ignored -X "$tmp") 40 | rm "$tmp" 41 | if [ -n "$locked" ]; then 42 | echo >&2 "Skipping annex sync; found lock files: $locked" 43 | return 44 | fi 45 | 46 | # Do a sync regardless of whether auto-commit did anything, 47 | # because another remote may have pushed changes to our 48 | # synced/master branch. First give other remotes a chance 49 | # to completely finish their push, just in case of any races. 50 | sleep 5 51 | git-annex-clean-sync 52 | else 53 | echo >&2 "WARNING: can't connect to ssh-agent; skipping annex sync" 54 | fi 55 | } 56 | 57 | check_ssh () { 58 | if ! git remote -v | grep -q ssh; then 59 | echo >&2 "No ssh remotes; skipping ssh agent check". 60 | return 0 61 | fi 62 | 63 | if ! detect_ssh_agent; then 64 | echo >&2 "Failed to detect ssh agent!" 65 | return 1 66 | fi 67 | 68 | if ! ssh-add -l; then 69 | echo >&2 "Failed to detect identities in ssh agent!" 70 | return 1 71 | fi 72 | } 73 | 74 | main () { 75 | if ! inotifywait --help | grep -q -- '--include'; then 76 | echo >&2 "inotifywait doesn't support --include; aborting!" 77 | exit 1 78 | fi 79 | 80 | if [ $# != 1 ]; then 81 | cat <&2 82 | Usage: $(basename $me) REPO-DIR 83 | EOF 84 | exit 1 85 | fi 86 | 87 | repo_dir="$1" 88 | cd "$repo_dir" 89 | 90 | if [[ -e HEAD ]] && [[ -e info ]] && [[ -e objects ]] && [[ -e refs ]] && 91 | [[ -e branches ]] 92 | then 93 | mode=bare 94 | git_dir=. 95 | elif [[ -d .git ]]; then 96 | mode=worktree 97 | git_dir=.git 98 | else 99 | echo >&2 "`pwd` isn't a git repo; aborting!" 100 | exit 1 101 | fi 102 | 103 | sleep_secs=5 104 | 105 | while true; do 106 | # limit of exponential back-off in seconds 107 | max_sleep=$( git config --default 600 auto-sync.max-sleep ) 108 | 109 | if ! check_ssh; then 110 | echo >&2 "Sleeping ${sleep_secs}s ..." 111 | sleep $sleep_secs 112 | sleep_secs=$(( sleep_secs * 2)) 113 | if (( sleep_secs > max_sleep )); then 114 | sleep_secs=$max_sleep 115 | fi 116 | continue 117 | fi 118 | 119 | # Maximum time to wait for an update before syncing. 120 | max_wait=$( git config --default 360 auto-sync.max-wait ) 121 | echo "Waiting up to $max_wait minutes for an update to $BRANCH branch ..." 122 | 123 | # This was a massive PITA to get right. It seems that if you 124 | # specify specific files then it will look up the inodes on 125 | # start-up and only monitor those. There's some similar weirdness 126 | # with moves too. Suffice to say that it's necessary to use 127 | # --include and watch the whole directory. -r is needed to catch 128 | # the synced/ subdirectory too. For more clues see 129 | # https://unix.stackexchange.com/questions/164794/why-doesnt-inotifywatch-detect-changes-on-added-files 130 | # 131 | # FIXME: try to switch to using http://eradman.com/entrproject/ 132 | inotifywait \ 133 | -q -r \ 134 | --include "/($BRANCH|synced/($BRANCH|git-annex))\$" \ 135 | -e create -e modify -e move -e delete \ 136 | -t $(( max_wait * 60 )) \ 137 | "$git_dir/refs/heads" | 138 | process_inotify_batch 139 | if (( pipestatus[1] == 2 )); then 140 | echo "Timed out waiting $max_wait minutes for update; syncing now ..." 141 | try_sync 142 | fi 143 | done 144 | } 145 | 146 | me="$0" 147 | main "$@" 148 | -------------------------------------------------------------------------------- /bin/gfind: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Like find(1), but only lists files tracked by git. 4 | # Excludes files only in the index; if you want those, 5 | # see ggls which is a shortcut for git ls-files. 6 | 7 | if [ $# -eq 0 ] || ! git rev-parse --verify "$1" >/dev/null 2>&1; then 8 | treeish=HEAD 9 | fi 10 | 11 | exec git ls-tree --name-only -r $treeish "$@" 12 | -------------------------------------------------------------------------------- /bin/gg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git gui "$@" 5 | fi 6 | 7 | exec git gui "$@" & 8 | -------------------------------------------------------------------------------- /bin/gg1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ggl1 --graph "$@" 4 | -------------------------------------------------------------------------------- /bin/gg1a: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec gg1 --all "$@" 4 | -------------------------------------------------------------------------------- /bin/gg1s: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Wrapper around any command (or wrapper) which appends "^!" to the 4 | # last argument. This is useful when you have a list of SHA1 digests 5 | # and you want to feed each one to a git log command and only get 6 | # output for that commit. In some cases this is better than simply 7 | # using "-1", since it lets you eliminate output from commits which 8 | # don't affect a certain file, e.g. 9 | # 10 | # git log -1 $sha -- $path 11 | # 12 | # will always output something, even if $sha doesn't touch $path, 13 | # whereas 14 | # 15 | # gg1s git log $sha -- $path 16 | # 17 | # won't. So you can do stuff like 18 | # 19 | # git cherry ... | awk '{print $2}' | xargs -i gg1s git log {} -- $path 20 | # 21 | # to see all commits touching $path which haven't been upstreamed yet. 22 | # 23 | # Having said that, you can simply do 24 | # 25 | # git cherry ... | awk '{print $2}' | xargs -i git log {}^! -- $path 26 | # 27 | # so maybe this wrapper is pointless. Hmm. I'll keep it for now, 28 | # just in case I come up with another use. 29 | 30 | export GIT_PAGER_MODE=none 31 | 32 | sha="${@: -1}" 33 | end=$(($# - 1)) 34 | 35 | exec "${@:1:end}" "${sha}^!" 36 | -------------------------------------------------------------------------------- /bin/ggAi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git add -i "$@" 4 | -------------------------------------------------------------------------------- /bin/gga: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git add "$@" 4 | -------------------------------------------------------------------------------- /bin/ggaa: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex add "$@" 4 | -------------------------------------------------------------------------------- /bin/ggac: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git-annex-clean "$@" 4 | -------------------------------------------------------------------------------- /bin/ggad: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex drop "$@" 4 | -------------------------------------------------------------------------------- /bin/ggaf: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex find "$@" 4 | -------------------------------------------------------------------------------- /bin/ggaf0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex find --not --copies 1 "$@" 4 | -------------------------------------------------------------------------------- /bin/ggaf1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex find --not --copies 2 "$@" 4 | -------------------------------------------------------------------------------- /bin/ggafd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex-finddups "$@" 4 | -------------------------------------------------------------------------------- /bin/ggafn: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # [ -z "$1" ] && set -- . 4 | # 5 | # symlinks -r "$@" \ 6 | # | perl -lne 'm!^dangling: (.+) -> (\.\./)*\.git/annex/objects/! && print $1' 7 | 8 | exec git annex find --not --in=. "$@" 9 | -------------------------------------------------------------------------------- /bin/ggag: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git-annex-clean get "$@" 4 | -------------------------------------------------------------------------------- /bin/ggag1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git-annex-clean get --not --copies 2 "$@" 4 | -------------------------------------------------------------------------------- /bin/ggal: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex lock "$@" 4 | -------------------------------------------------------------------------------- /bin/ggan: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex "$@" 4 | -------------------------------------------------------------------------------- /bin/ggana: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex assistant "$@" 4 | -------------------------------------------------------------------------------- /bin/gganrd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex remotedaemon "$@" 4 | -------------------------------------------------------------------------------- /bin/ggans: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git-annex-clean-sync --no-commit "$@" 4 | -------------------------------------------------------------------------------- /bin/gganwa: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex webapp "$@" 4 | -------------------------------------------------------------------------------- /bin/ggap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | remote="$1" 4 | 5 | git fetch "$remote" 6 | git pull "$remote" master 7 | git annex merge 8 | -------------------------------------------------------------------------------- /bin/ggas: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex status "$@" 4 | -------------------------------------------------------------------------------- /bin/ggasy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # See also ggans => git-annex-clean-sync 4 | 5 | exec git annex sync "$@" 6 | -------------------------------------------------------------------------------- /bin/ggau: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex unused "$@" 4 | -------------------------------------------------------------------------------- /bin/ggaul: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex unlock "$@" 4 | -------------------------------------------------------------------------------- /bin/ggaw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex whereis "$@" 4 | -------------------------------------------------------------------------------- /bin/ggaw1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git annex whereis --not --copies 2 "$@" | \ 4 | perl -wne ' 5 | next if /^ok$/; 6 | /^whereis (.+) \(1 copy\)/ and $f = $1; 7 | /^\s+([a-f0-9-]+) -- (.+)/ and print "$2 $f\n"; 8 | ' 9 | -------------------------------------------------------------------------------- /bin/ggawn: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git annex whereis --not --in=. "$@" 4 | -------------------------------------------------------------------------------- /bin/ggb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == --help ]; then 4 | exec git branch "$@" 5 | fi 6 | 7 | if git branch -h 2>&1 | grep -q -- '--color\[='; then 8 | color=--color=always 9 | else 10 | if [ -t 1 ]; then 11 | color=--color 12 | else 13 | color= 14 | fi 15 | fi 16 | 17 | exec git branch -vv $color "$@" | trim-lines 18 | -------------------------------------------------------------------------------- /bin/ggba: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggb -a "$@" 4 | 5 | -------------------------------------------------------------------------------- /bin/ggbac: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggb -a --contains "$@" 4 | -------------------------------------------------------------------------------- /bin/ggbc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggb --contains "$@" 4 | -------------------------------------------------------------------------------- /bin/ggbl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '-h' ] || [ "$1" = '--help' ]; then 4 | exec git gui "$1" 5 | fi 6 | 7 | git gui blame "$@" & 8 | -------------------------------------------------------------------------------- /bin/ggbm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == --help ]; then 4 | exec git branch "$@" 5 | fi 6 | 7 | exec ggb --merged "$@" 8 | -------------------------------------------------------------------------------- /bin/ggbnm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == --help ]; then 4 | exec git branch "$@" 5 | fi 6 | 7 | exec ggb --no-merged "$@" 8 | -------------------------------------------------------------------------------- /bin/ggbr: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggb -r "$@" 4 | 5 | -------------------------------------------------------------------------------- /bin/ggc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git config "$@" 4 | -------------------------------------------------------------------------------- /bin/ggcg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git config --global "$@" 4 | -------------------------------------------------------------------------------- /bin/ggci: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git commit "$@" 4 | -------------------------------------------------------------------------------- /bin/ggcia: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git commit --amend "$@" 4 | -------------------------------------------------------------------------------- /bin/ggciaa: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggcia -C HEAD "$@" 4 | -------------------------------------------------------------------------------- /bin/ggcl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git config --list "$@" 4 | -------------------------------------------------------------------------------- /bin/ggco: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git checkout "$@" 4 | -------------------------------------------------------------------------------- /bin/ggcp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git cherry-pick -x "$@" 4 | -------------------------------------------------------------------------------- /bin/ggcpa: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git cherry-pick --abort 4 | -------------------------------------------------------------------------------- /bin/ggcpc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git cherry-pick --continue 4 | -------------------------------------------------------------------------------- /bin/ggcpm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git Cherry Pick Multiple 4 | 5 | gglh --reverse "$@" | xargs -n1 git cherry-pick -x 6 | -------------------------------------------------------------------------------- /bin/ggct: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git citool "$@" 4 | -------------------------------------------------------------------------------- /bin/ggd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GIT_PAGER_MODE=diff 4 | . $ZDOTDIR/.shared_rc.d/git-pager 5 | 6 | exec git diff "$@" 7 | -------------------------------------------------------------------------------- /bin/ggdc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GIT_PAGER_MODE=diff 4 | . $ZDOTDIR/.shared_rc.d/git-pager 5 | 6 | exec git diff --cached "$@" 7 | -------------------------------------------------------------------------------- /bin/ggdch: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git diff --check "$@" 4 | -------------------------------------------------------------------------------- /bin/ggdcm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git diff check multiple 4 | 5 | me=`basename $0` 6 | 7 | if [ $# -gt 1 ]; then 8 | echo "Usage: $me [commit range]" >&2 9 | exit 1 10 | fi 11 | 12 | if [ $# -eq 0 ]; then 13 | range="`git upstream`..HEAD" 14 | echo "Checking $range" 15 | echo 16 | else 17 | range="$1" 18 | fi 19 | 20 | error_count=0 21 | 22 | while read sha1; do 23 | if ggdch $sha1^ $sha1 >/dev/null; then 24 | echo "`git describe --all --contains $sha1` has no whitespace errors" 25 | continue 26 | fi 27 | 28 | : $(( error_count++ )) 29 | echo "error count now $error_count" 30 | 31 | ( 32 | git --no-pager log -n1 --color $sha1 | git name-rev --stdin 33 | echo 34 | ggdch --color $sha1^ $sha1 35 | ) | less -F 36 | echo ---------------------------------------------------------------------- 37 | done < <( gglh --reverse "$range" ) 38 | # http://nion.modprobe.de/blog/archives/531-Altering-a-variable-outside-the-scope-of-a-loop-influenced-by-a-subshell.html 39 | 40 | echo "Commits with whitespace errors: $error_count" 41 | exit $error_count 42 | -------------------------------------------------------------------------------- /bin/ggde: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git describe "$@" 5 | fi 6 | 7 | if desc=$( git describe --all "$@" 2>/dev/null ); then 8 | short="${desc#heads/}" 9 | echo "${short#tags/}" 10 | else 11 | git describe --all --contains "$@" 2>/dev/null 12 | fi 13 | -------------------------------------------------------------------------------- /bin/ggdt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '-h' ] || [ "$1" = '--help' ]; then 4 | exec git diff-tree "$@" 5 | fi 6 | 7 | [ -t 1 ] && args=--color 8 | 9 | if [ -z "$1" ]; then 10 | upstream="`git upstream`" || exit 1 # git upstream will display the error 11 | set -- "$upstream"..HEAD 12 | fi 13 | 14 | export GIT_PAGER_MODE=diff 15 | . $ZDOTDIR/.shared_rc.d/git-pager 16 | exec git diff-tree -p $args "$@" 17 | -------------------------------------------------------------------------------- /bin/ggdtn: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Diff Tree Names only 4 | 5 | if [ -z "$1" ]; then 6 | upstream="`git upstream`" || exit 1 # git upstream will display the error 7 | set -- "$upstream"..HEAD 8 | fi 9 | 10 | export GIT_PAGER_MODE=none 11 | . $ZDOTDIR/.shared_rc.d/git-pager 12 | exec git diff-tree --name-status -r "$@" 13 | -------------------------------------------------------------------------------- /bin/ggdts: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Diff Tree --stat 4 | 5 | if [ "$1" = '-h' ] || [ "$1" = '--help' ]; then 6 | exec git diff-tree "$@" 7 | fi 8 | 9 | args=() 10 | [ -t 1 ] && args=(--color "${args[@]}") 11 | if [[ "$1" == --stat* ]]; then 12 | args=("$1" "${args[@]}") 13 | shift 14 | fi 15 | 16 | if [ -z "$1" ]; then 17 | upstream="`git upstream`" || exit 1 # git upstream will display the error 18 | set -- "$upstream"..HEAD 19 | fi 20 | 21 | export GIT_PAGER_MODE=none 22 | . $ZDOTDIR/.shared_rc.d/git-pager 23 | exec git diff-tree "${args[@]}" "$@" 24 | -------------------------------------------------------------------------------- /bin/ggdtsw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Diff Tree --stat wide output 4 | 5 | exec ggdts --stat=120,140 "$@" 6 | 7 | if [ "$1" = '-h' ] || [ "$1" = '--help' ]; then 8 | exec git diff-tree "$@" 9 | fi 10 | 11 | [ -t 1 ] && args=--color 12 | 13 | if [ -z "$1" ]; then 14 | upstream="`git upstream`" || exit 1 # git upstream will display the error 15 | set -- "$upstream"..HEAD 16 | fi 17 | 18 | export GIT_PAGER_MODE=none 19 | . $ZDOTDIR/.shared_rc.d/git-pager 20 | exec git diff-tree --stat=120,140 $args "$@" 21 | -------------------------------------------------------------------------------- /bin/ggdw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git diff "$@" 5 | fi 6 | 7 | exec git diff --color-words "$@" 8 | -------------------------------------------------------------------------------- /bin/gge: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | git config user.email 5 | else 6 | case "$1" in 7 | *@*) 8 | git config user.email "$1" 9 | ;; 10 | *) 11 | git config user.email "$1@adamspiers.org" 12 | ;; 13 | esac 14 | fi 15 | -------------------------------------------------------------------------------- /bin/ggf: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# = 0 ]; then 4 | exec git fetch --prune 5 | else 6 | for remote in "$@"; do 7 | git fetch --prune "$remote" 8 | done 9 | fi 10 | -------------------------------------------------------------------------------- /bin/ggfa: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #exec git fetch --prune --all "$@" 4 | 5 | for remote in $( git remote ); do 6 | if git-remote-annex-is-ignored $remote; then 7 | echo "$remote is ignored" 8 | continue 9 | fi 10 | 11 | if git-should-ping-remote-annex $remote && ! git-remote-is-up $remote; then 12 | echo "$remote appears to be down; excluding from sync" 13 | continue 14 | fi 15 | 16 | git fetch --prune $remote 17 | done 18 | -------------------------------------------------------------------------------- /bin/ggff: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git merge --ff-only "$@" 4 | -------------------------------------------------------------------------------- /bin/ggfp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git format-patch "$@" 5 | fi 6 | 7 | if [ $# = 0 ]; then 8 | set -- "`git upstream`..HEAD" 9 | fi 10 | 11 | exec git format-patch "$@" 12 | -------------------------------------------------------------------------------- /bin/ggfps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git format-patch "$@" 5 | fi 6 | 7 | if [ $# = 0 ]; then 8 | set -- "`git upstream`..HEAD" 9 | fi 10 | 11 | exec git format-patch --stdout "$@" 12 | -------------------------------------------------------------------------------- /bin/ggft: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# = 0 ]; then 4 | exec git fetch --tags 5 | else 6 | for remote in "$@"; do 7 | git fetch --tags "$remote" 8 | done 9 | fi 10 | -------------------------------------------------------------------------------- /bin/ggg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git grep -E "$@" 4 | -------------------------------------------------------------------------------- /bin/gggl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggg -l "$@" 4 | -------------------------------------------------------------------------------- /bin/ggh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # No need for this to pass parameters; instead: 4 | ( 5 | cat <<'EOF' 6 | # Type 'git' for list of most frequently used commands 7 | # Type 'git $cmd -h' for quick help on $cmd 8 | # Type 'git $cmd --help' for long help on $cmd 9 | 10 | EOF 11 | 12 | git help -a ) \ 13 | | $PAGER 14 | -------------------------------------------------------------------------------- /bin/gghd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git head "$@" 4 | -------------------------------------------------------------------------------- /bin/ggi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git incoming 4 | 5 | upstream="`git upstream`" || exit 1 # git upstream will display the error 6 | 7 | echo "HEAD is `ggde`" 8 | echo -e "Upstream is $upstream\n" 9 | 10 | if [ -z "$GIT_PAGER_MODE" ]; then 11 | # not being set via a caller such as ggo1 etc. 12 | # so choose according to the output format which 13 | # we know we can expect from git log 14 | export GIT_PAGER_MODE=log 15 | else 16 | # honour the caller's setting 17 | : 18 | fi 19 | 20 | . $ZDOTDIR/.shared_rc.d/git-pager 21 | 22 | git log "$@" HEAD.."$upstream" 23 | -------------------------------------------------------------------------------- /bin/ggk: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | exec gitk --all "$@" & 5 | else 6 | exec gitk "$@" & 7 | fi 8 | 9 | -------------------------------------------------------------------------------- /bin/ggkc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo >&2 "usage: ggkc [...]" 5 | exit 1 6 | fi 7 | 8 | commit="$1" 9 | shift 10 | 11 | select=--select-commit="$commit" 12 | if [ -z "$1" ]; then 13 | exec gitk --all "$select" & 14 | else 15 | exec gitk "$@" "$select" & 16 | fi 17 | -------------------------------------------------------------------------------- /bin/ggks: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ggk --simplify-by-decoration "$@" 4 | -------------------------------------------------------------------------------- /bin/ggl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git log "$@" 5 | fi 6 | 7 | : ${GIT_PAGER_MODE:=log} 8 | export GIT_PAGER_MODE 9 | . $ZDOTDIR/.shared_rc.d/git-pager 10 | 11 | exec git log --color --decorate "$@" 12 | -------------------------------------------------------------------------------- /bin/ggl1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggl --pretty=oneline --abbrev-commit "$@" 4 | -------------------------------------------------------------------------------- /bin/ggl1l: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # http://www.jukie.net/~bart/blog/pimping-out-git-log 4 | # Thanks Bart ;-) 5 | 6 | me=`basename $0` 7 | 8 | version="`git --version | awk '{print \$3}'`" 9 | case "$version" in 10 | 1.[0-5].*|1.6.0.*) dec="" ;; 11 | 1.6.[2-9].*|1.[7-9].*|2.*) dec="%C(yellow)%d%Creset" ;; 12 | *) 13 | echo "FIXME: add support for git $version to $me" >&2 14 | exit 1 15 | ;; 16 | esac 17 | 18 | LESS="$LESS -S" 19 | 20 | # FIXME: why was this here? We want to default to a pager when 21 | # this command is called directly. 22 | #: ${GIT_PAGER_MODE:=none} 23 | 24 | . $ZDOTDIR/.shared_rc.d/git-pager 25 | 26 | exec git log --color --decorate --abbrev-commit \ 27 | --pretty=tformat:"%Cred%h%Creset -$dec %s %Cgreen(%cd)%Creset" \ 28 | "$@" 29 | -------------------------------------------------------------------------------- /bin/ggla: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggl --all "$@" 4 | -------------------------------------------------------------------------------- /bin/gglcl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Log in ChangeLog format 4 | # 5 | # for use in OBS .changes files 6 | 7 | usage () { 8 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 9 | exit_code=1 10 | if [[ "$1" == [0-9] ]]; then 11 | exit_code="$1" 12 | shift 13 | fi 14 | if [ -n "$1" ]; then 15 | echo >&2 "$*" 16 | echo 17 | fi 18 | 19 | me=`basename $0` 20 | 21 | cat <&2 22 | Usage: $me [FROM [TO]] 23 | options 24 | EOF 25 | exit "$exit_code" 26 | } 27 | 28 | main () { 29 | if [ "$1" == '-h' ] || [ "$1" == '--help' ]; then 30 | usage 0 31 | fi 32 | 33 | [ $# -gt 2 ] && usage 34 | 35 | from="$1" 36 | to="${2:-HEAD}" 37 | to_rev="`git rev-parse --short $to`" 38 | 39 | branch=`ggde --all "$to"` 40 | branch="${branch#remotes/*/} " 41 | 42 | if [ -n "$from" ]; then 43 | args="$from..$to" 44 | from_rev="from `git rev-parse --short $from` " 45 | else 46 | args="$to" 47 | from_rev= 48 | fi 49 | 50 | echo "- update ${branch}${from_rev}to latest git revision ($to_rev)" 51 | exec git log --no-merges --pretty=format:' + %s' "$args" 52 | } 53 | 54 | main "$@" 55 | -------------------------------------------------------------------------------- /bin/gglg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git log "$@" 5 | fi 6 | 7 | export GIT_PAGER_MODE=log 8 | . $ZDOTDIR/.shared_rc.d/git-pager 9 | 10 | exec git log --color --decorate --grep "$@" 11 | -------------------------------------------------------------------------------- /bin/gglh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # show only the SHA1 - useful for multiple cherry-picks (see ggcpm) 4 | exec git log --pretty=tformat:%H --abbrev-commit "$@" 5 | -------------------------------------------------------------------------------- /bin/ggli: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git ls-files -i --exclude-standard "$@" 4 | -------------------------------------------------------------------------------- /bin/ggll: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git log --name-status "$@" 4 | -------------------------------------------------------------------------------- /bin/ggll1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | esc=$'\e' 4 | color="(${esc}\[[0-9;]*m)?" 5 | 6 | exec gg1 --name-status "$@" | grep -vE "^(${color}\|${color} )* *$" 7 | -------------------------------------------------------------------------------- /bin/gglla: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec ggll --all "$@" 4 | -------------------------------------------------------------------------------- /bin/gglm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git log --pretty=format:%s "$@" 4 | -------------------------------------------------------------------------------- /bin/gglp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git log "$@" 5 | fi 6 | 7 | exec ggl -p "$@" 8 | -------------------------------------------------------------------------------- /bin/gglpg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git log "$@" 5 | fi 6 | 7 | exec gglp --grep "$@" 8 | -------------------------------------------------------------------------------- /bin/gglpw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git log "$@" 5 | fi 6 | 7 | exec gglp --color-words "$@" 8 | -------------------------------------------------------------------------------- /bin/ggls: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git ls-files "$@" 4 | -------------------------------------------------------------------------------- /bin/gglsi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # List ignored files 4 | 5 | exec gglsu --ignored "$@" 6 | -------------------------------------------------------------------------------- /bin/gglsu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # List untracked files 4 | # http://stackoverflow.com/questions/3801321/git-list-only-untracked-files-also-custom-commands 5 | 6 | exec git ls-files --other --exclude-standard "$@" 7 | -------------------------------------------------------------------------------- /bin/ggm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git merge "$@" 4 | -------------------------------------------------------------------------------- /bin/ggma: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git merge --abort "$@" 4 | -------------------------------------------------------------------------------- /bin/ggmb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git merge-base "$@" 4 | -------------------------------------------------------------------------------- /bin/ggme: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git merged "$@" 4 | -------------------------------------------------------------------------------- /bin/ggmo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git merged -o "$@" 4 | -------------------------------------------------------------------------------- /bin/ggmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git mergetool "$@" 4 | -------------------------------------------------------------------------------- /bin/ggmv: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git mv "$@" 4 | -------------------------------------------------------------------------------- /bin/ggmx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git mix "$@" 4 | -------------------------------------------------------------------------------- /bin/ggmxd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git mixdown "$@" 4 | -------------------------------------------------------------------------------- /bin/ggo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git Outgoing 4 | 5 | upstream="`git upstream`" || exit 1 # git upstream will display the error 6 | 7 | if [ -t 1 ]; then 8 | echo "HEAD is `ggde`" 9 | echo -e "Upstream is $upstream\n" 10 | fi 11 | 12 | if [ -z "$GIT_PAGER_MODE" ]; then 13 | # not being set via a caller such as ggo1 etc. 14 | # so choose according to the output format which 15 | # we know we can expect from git log 16 | export GIT_PAGER_MODE=log 17 | else 18 | # honour the caller's setting 19 | : 20 | fi 21 | 22 | . $ZDOTDIR/.shared_rc.d/git-pager 23 | 24 | git log "$@" "$upstream"..HEAD 25 | -------------------------------------------------------------------------------- /bin/ggo1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GIT_PAGER_MODE=none 4 | ggo --decorate --pretty=oneline --abbrev-commit "$@" 5 | -------------------------------------------------------------------------------- /bin/ggop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git Outgoing -p 4 | 5 | ggo -p "$@" 6 | -------------------------------------------------------------------------------- /bin/ggopw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git Outgoing -p color Words 4 | 5 | ggo -p --color-words "$@" 6 | -------------------------------------------------------------------------------- /bin/ggos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git Outgoing --stat 4 | 5 | export GIT_PAGER_MODE=log 6 | ggo --stat "$@" 7 | -------------------------------------------------------------------------------- /bin/ggosw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git Outgoing --stat 4 | 5 | export GIT_PAGER_MODE=log 6 | ggo --stat=120,140 "$@" 7 | -------------------------------------------------------------------------------- /bin/ggpb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # pb == parent branch (although it will show a tag if it's closer) 4 | 5 | rev="${1:-HEAD}" 6 | 7 | exec git describe --abbrev=0 --all "$rev"^ 8 | -------------------------------------------------------------------------------- /bin/ggpbb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rev="${1:-HEAD}" 4 | 5 | while rev=$( ggpb "$rev" 2>/dev/null ); do 6 | echo "$rev" 7 | done 8 | -------------------------------------------------------------------------------- /bin/ggph: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! git check-email; then 4 | echo 5 | echo "Aborting push; please fix email first." 6 | exit $? 7 | fi 8 | 9 | exec git push "$@" 10 | -------------------------------------------------------------------------------- /bin/ggpl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git pull "$@" 4 | -------------------------------------------------------------------------------- /bin/ggplff: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git pull --ff-only "$@" 4 | -------------------------------------------------------------------------------- /bin/ggplr: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git pull --rebase "$@" 4 | -------------------------------------------------------------------------------- /bin/ggr: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git reset "$@" 4 | -------------------------------------------------------------------------------- /bin/ggrb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git rebase "$@" 5 | fi 6 | 7 | # Redundant with git >= 1.7.6 8 | if [ $# -eq 0 ]; then 9 | # default to rebasing against upstream 10 | if upstream="$( git upstream )"; then 11 | set "$upstream" 12 | else 13 | exit 1 14 | fi 15 | fi 16 | 17 | exec git rebase "$@" 18 | -------------------------------------------------------------------------------- /bin/ggrba: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git rebase "$@" 5 | fi 6 | 7 | exec git rebase --abort "$@" 8 | -------------------------------------------------------------------------------- /bin/ggrbc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git rebase "$@" 5 | fi 6 | 7 | exec git rebase --continue "$@" 8 | -------------------------------------------------------------------------------- /bin/ggrbi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git rebase "$@" 5 | fi 6 | 7 | # Redundant with git >= 1.7.6 8 | if [ $# -eq 0 ]; then 9 | # default to rebasing against upstream 10 | if upstream="$( git upstream )"; then 11 | set "$upstream" 12 | else 13 | exit 1 14 | fi 15 | fi 16 | 17 | exec git rebase -i "$@" 18 | -------------------------------------------------------------------------------- /bin/ggrbim: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ggrbm -i "$@" 4 | -------------------------------------------------------------------------------- /bin/ggrbm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | me=`basename $0` 4 | 5 | usage () { 6 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 7 | exit_code=1 8 | if [[ "$1" == [0-9] ]]; then 9 | exit_code="$1" 10 | shift 11 | fi 12 | 13 | cat <&2 14 | Usage: $me [options] [BRANCH-CONFIG] 15 | 16 | BRANCH-CONFIG defaults to $wd/.git/topic-branches and takes the 17 | format: 18 | 19 | # Comments are allowed 20 | # Each line contains a set of arguments to pass to git rebase 21 | # e.g. 22 | trunk myroot 23 | myroot mytopic1 24 | myroot mytopic2 25 | 26 | # You can use arbitrary git commands by prefixing with '!': 27 | !branch -f working mytopic1 28 | !merge --no-edit mytopic2 29 | 30 | Options: 31 | 32 | -i Do interactive rebase 33 | EOF 34 | 35 | if [ -n "$1" ]; then 36 | echo "$*" >&2 37 | echo 38 | fi 39 | 40 | exit $exit_code 41 | } 42 | 43 | parse_opts () { 44 | while [ $# != 0 ]; do 45 | case "$1" in 46 | -h|--help) 47 | usage 0 48 | ;; 49 | -i) 50 | interactive=-i 51 | shift 52 | ;; 53 | -*) 54 | usage "Unrecognised option: $1" 55 | ;; 56 | *) 57 | break 58 | ;; 59 | esac 60 | done 61 | 62 | if [ "$#" -gt 1 ]; then 63 | usage 64 | elif [ "$#" -eq 1 ]; then 65 | conf="$1" 66 | shift 67 | elif [ "$#" -eq 0 ]; then 68 | conf="$root/.git/topic-branches" 69 | fi 70 | 71 | if ! [ -e "$conf" ]; then 72 | die "$conf not found; aborting." 73 | fi 74 | 75 | ARGV=( "$@" ) 76 | } 77 | 78 | die () { 79 | echo >&2 "$*" 80 | exit 1 81 | } 82 | 83 | process_line () { 84 | case "${args[0]}" in 85 | !*) 86 | args[0]="${args[0]#!}" 87 | process_arbitrary_git_command 88 | ;; 89 | *) 90 | process_rebase_command 91 | ;; 92 | esac 93 | } 94 | 95 | process_arbitrary_git_command () { 96 | echo "git ${args[*]}" 97 | 98 | if ! git "${args[@]}"; then 99 | had_issues=y 100 | echo "git ${args[*]} failed" 101 | echo "Please fix and then exit shell." 102 | bash <&3 103 | fi 104 | } 105 | 106 | process_rebase_command () { 107 | range="${args[0]}..${args[1]}" 108 | 109 | check_whitespace 110 | echo 111 | rebase_until_success 112 | echo 113 | if [ -z "$had_issues" ]; then 114 | echo -n "git rebase ${args[*]} done" 115 | else 116 | echo -n "git rebase ${args[*]} done; hit enter to continue ... " 117 | read <&3 118 | fi 119 | echo 120 | div 121 | } 122 | 123 | check_whitespace () { 124 | echo "Checking for whitespace errors in $range" 125 | if ! ggdcm "$range"; then 126 | echo -n "Hit enter to continue regardless ... " 127 | read <&3 128 | echo 129 | fi 130 | } 131 | 132 | rebase_until_success () { 133 | echo "git rebase $interactive ${args[*]}" 134 | had_issues= 135 | if ! git rebase $interactive "${args[@]}"; then 136 | had_issues=y 137 | while [ -e "$root/.git/rebase-apply" ] || [ -d "$root/.git/rebase-merge" ]; do 138 | echo 139 | echo "git rebase ${args[@]} not completed." 140 | echo "Please fix via git rebase --continue / --abort then exit shell." 141 | bash <&3 142 | done 143 | fi 144 | } 145 | 146 | restore_head () { 147 | if [ -n "$orig_head" ]; then 148 | echo "Checking out original HEAD ($orig_head) ... " 149 | git checkout "$orig_head" 150 | else 151 | echo "WARNING: Couldn't determine original HEAD; current HEAD is probably different". 152 | fi 153 | } 154 | 155 | main () { 156 | root="`git root`" 157 | if [ -z "$root" ]; then 158 | die "git root failed; aborting." 159 | fi 160 | 161 | if ! which div >&/dev/null; then 162 | die "div not found on \$PATH; please get from: https://github.com/aspiers/shell-env/blob/master/bin/div" 163 | fi 164 | 165 | parse_opts "$@" 166 | 167 | if [ -e "$root/.git/rebase-apply" ]; then 168 | die "$me: rebase already in progress! aborting." 169 | fi 170 | 171 | orig_head=`git head` 172 | 173 | exec 3>&0 # save STDIN tty 174 | egrep -v '^ *(#|$)' "$conf" | while read -a args; do 175 | process_line 176 | done 177 | 178 | echo 179 | restore_head 180 | } 181 | 182 | main "$@" 183 | -------------------------------------------------------------------------------- /bin/ggrbs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git rebase "$@" 5 | fi 6 | 7 | exec git rebase --skip "$@" 8 | -------------------------------------------------------------------------------- /bin/ggre: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git REmote 4 | 5 | if [ $# = 0 ]; then 6 | exec git remote -v 7 | else 8 | exec git remote "$@" 9 | fi 10 | -------------------------------------------------------------------------------- /bin/ggrea: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git REmote Add 4 | 5 | if [ $# = 0 ]; then 6 | exec git remote -v 7 | else 8 | exec git remote add "$@" 9 | fi 10 | -------------------------------------------------------------------------------- /bin/ggrer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git REmote Remove 4 | 5 | if [ $# = 0 ]; then 6 | exec git remote -v 7 | else 8 | exec git remote rm "$@" 9 | fi 10 | -------------------------------------------------------------------------------- /bin/ggres: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git REmote Seturl 4 | 5 | if [ $# = 0 ]; then 6 | exec git remote -v 7 | else 8 | exec git remote set-url "$@" 9 | fi 10 | -------------------------------------------------------------------------------- /bin/ggrh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git reset --hard "$@" 4 | -------------------------------------------------------------------------------- /bin/ggrl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ref="${1:-HEAD}" 4 | 5 | exec ggrln "$ref@{now}" 6 | -------------------------------------------------------------------------------- /bin/ggrll: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git reflog "$@" 5 | fi 6 | 7 | ref="${1:-HEAD}" 8 | shift 9 | 10 | exec ggl -g --abbrev-commit "$ref@{now}" "$@" 11 | -------------------------------------------------------------------------------- /bin/ggrln: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git reflog "$@" 5 | fi 6 | 7 | export GIT_PAGER_MODE=default 8 | . $ZDOTDIR/.shared_rc.d/git-pager 9 | 10 | ref="${1:-HEAD}" 11 | shift 12 | 13 | exec git log --color --decorate -g --abbrev-commit --pretty=oneline "$ref" "$@" 14 | -------------------------------------------------------------------------------- /bin/ggrls: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '--help' ]; then 4 | exec git reflog "$@" 5 | fi 6 | 7 | ref="${1:-HEAD}" 8 | shift 9 | 10 | exec ggl -g --abbrev-commit --stat "$ref@{now}" "$@" 11 | -------------------------------------------------------------------------------- /bin/ggrm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git rm "$@" 4 | -------------------------------------------------------------------------------- /bin/ggrp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | set HEAD 5 | fi 6 | 7 | exec git rev-parse "$@" 8 | -------------------------------------------------------------------------------- /bin/ggrs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git reset --soft "$@" 4 | -------------------------------------------------------------------------------- /bin/ggs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | status=`git -c color.ui=always status -s "$@"` 4 | ret=$? 5 | 6 | if [ -n "$status" ]; then 7 | git -c color.ui=always branch -v -v | grep --color=no '^\*' 8 | echo "$status" 9 | fi 10 | 11 | git root >/dev/null 2>&1 && git-check-email 12 | 13 | exit $ret 14 | -------------------------------------------------------------------------------- /bin/ggsa: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ggs -uall "$@" 4 | -------------------------------------------------------------------------------- /bin/ggsb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git SuBmodule 4 | 5 | exec git submodule "$@" 6 | -------------------------------------------------------------------------------- /bin/ggsbf: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git SuBmodule Foreach 4 | 5 | exec git submodule foreach "$@" | perl -pe "s/^(Entering '.+)/\e[0;1m\$1\e[0m/" 6 | -------------------------------------------------------------------------------- /bin/ggsbs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git SuBmodule Summary 4 | 5 | exec git submodule summary "$@" 6 | -------------------------------------------------------------------------------- /bin/ggsbu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git SuBmodule Update 4 | 5 | exec git submodule update "$@" 6 | -------------------------------------------------------------------------------- /bin/ggsbur: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git SuBmodule Update Rebase 4 | 5 | exec git submodule update --rebase "$@" 6 | -------------------------------------------------------------------------------- /bin/ggsh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | type=commit 4 | for (( i = 1; i <= $#; i++ )); do 5 | val="${!i}" 6 | case "$val" in 7 | -*) : 8 | #echo "Ignoring option $val" 9 | ;; 10 | *) 11 | type=`git cat-file -t "$val"` 12 | #echo "Type of $val is $type" 13 | break 14 | ;; 15 | esac 16 | done 17 | 18 | case "$type" in 19 | blob) 20 | export GIT_PAGER_MODE=none 21 | ;; 22 | commit) 23 | if [ $# -gt 1 ]; then 24 | export GIT_PAGER_MODE=commit 25 | else 26 | export GIT_PAGER_MODE=diff 27 | fi 28 | ;; 29 | esac 30 | 31 | . $ZDOTDIR/.shared_rc.d/git-pager 32 | 33 | #echo git show "$@" 34 | exec git show --pretty=fuller "$@" 35 | -------------------------------------------------------------------------------- /bin/ggsh1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # http://www.jukie.net/~bart/blog/pimping-out-git-log 4 | # Thanks Bart ;-) 5 | 6 | me=`basename $0` 7 | 8 | version="`git --version | awk '{print \$3}'`" 9 | case "$version" in 10 | 1.[0-5].*|1.6.0.*) dec="" ;; 11 | 1.6.[2-9].*|1.[7-9].*|2.*) dec="%C(yellow)%d%Creset" ;; 12 | *) 13 | echo "FIXME: add support for git $version to $me" >&2 14 | exit 1 15 | ;; 16 | esac 17 | 18 | LESS="$LESS -S" 19 | 20 | : ${GIT_PAGER_MODE:=none} 21 | export GIT_PAGER_MODE 22 | . $ZDOTDIR/.shared_rc.d/git-pager 23 | 24 | mode=-s 25 | [[ "$*" == *'--name'* ]] && mode= 26 | 27 | exec git show $mode --color --decorate --abbrev-commit --date=relative \ 28 | --pretty=tformat:"%Cred%h%Creset -$dec %s %Cgreen(%cr)%Creset" \ 29 | "$@" 30 | -------------------------------------------------------------------------------- /bin/ggsh1l: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | esc=$'\e' 4 | color="(${esc}\[[0-9;]*m)?" 5 | 6 | exec ggsh1 --name-status "$@" #| grep -vE "^(${color}\|${color} )* *$" 7 | -------------------------------------------------------------------------------- /bin/ggshs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '-h' ] || [ "$1" = '--help' ]; then 4 | exec git show "$@" 5 | fi 6 | 7 | exec ggsh --stat "$@" -------------------------------------------------------------------------------- /bin/ggshsw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '-h' ] || [ "$1" = '--help' ]; then 4 | exec git show "$@" 5 | fi 6 | 7 | exec ggsh --stat=120,140 --color "$@" -------------------------------------------------------------------------------- /bin/ggst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git stash "$@" 4 | -------------------------------------------------------------------------------- /bin/ggsu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git sync-upstream "$@" 4 | -------------------------------------------------------------------------------- /bin/ggsup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git-set-upstream "$@" 4 | -------------------------------------------------------------------------------- /bin/ggt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git tag "$@" 4 | -------------------------------------------------------------------------------- /bin/ggtD: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git tag -D "$@" 4 | -------------------------------------------------------------------------------- /bin/ggtc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git tag --contains "$@" 4 | -------------------------------------------------------------------------------- /bin/ggtd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git tag -d "$@" 4 | -------------------------------------------------------------------------------- /bin/ggtps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git tag-patchset "$@" 4 | -------------------------------------------------------------------------------- /bin/ggup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git-upstream "$@" 4 | -------------------------------------------------------------------------------- /bin/ggur: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git url-rewrite "$@" 4 | -------------------------------------------------------------------------------- /bin/ggw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git wip && ggs 4 | -------------------------------------------------------------------------------- /bin/ggwc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git whatchanged "$@" 4 | -------------------------------------------------------------------------------- /bin/ggwm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git when-merged "$@" 4 | -------------------------------------------------------------------------------- /bin/git-add-prefix: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | me=`basename $0` 4 | 5 | usage () { 6 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 7 | exit_code=1 8 | if [[ "$1" == [0-9] ]]; then 9 | exit_code="$1" 10 | shift 11 | fi 12 | if [ -n "$1" ]; then 13 | echo "$*" >&2 14 | echo 15 | fi 16 | 17 | cat <&2 18 | Usage: 19 | # add 'Bug 123456 -' prefix to every commit log in range 20 | $me 'Bug 123456' REVISION-RANGE 21 | EOF 22 | exit "$exit_code" 23 | } 24 | 25 | if [ "$1" == '-h' ] || [ "$1" == '--help' ]; then 26 | usage 0 27 | fi 28 | 29 | [ $# == 2 ] || usage 30 | 31 | prefix="$1" 32 | revrange="$2" 33 | 34 | git-sed-range "1 { s/^/$prefix - / }" "$revrange" 35 | -------------------------------------------------------------------------------- /bin/git-annex-clean: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Run git annex while avoiding trying to touch ignored or 4 | # unavailable remotes. 5 | 6 | opts=() 7 | to_ignore=() 8 | to_ping=() 9 | for remote in $( git remote ); do 10 | if git-remote-annex-is-ignored $remote; then 11 | # For some reason git-annex still tries to pull from ignored remotes 12 | to_ignore+=( $remote) 13 | opts+=( -c remote.$remote.url= ) 14 | elif git-should-ping-remote-annex $remote; then 15 | to_ping+=( $remote ) 16 | fi 17 | done 18 | if [[ $to_ignore ]]; then 19 | echo >&2 "Ignoring remotes: $to_ignore\n" 20 | fi 21 | 22 | if which parallel >&/dev/null; then 23 | echo >&2 "Found GNU parallel!" 24 | echo >&2 "Checking liveness of remotes in parallel: $to_ping\n" 25 | opts+=( 26 | $( 27 | echo $to_ping | 28 | parallel --trim=lr -d\ -n1 \ 29 | git-down-remote-ignore-opts {} 30 | ) 31 | ) 32 | else 33 | echo >&2 "Couldn't find GNU parallel :-(" 34 | echo >&2 "Checking liveness of remotes in series: $to_ping\n" 35 | for remote in $to_ping; do 36 | opts+=( $(git-down-remote-ignore-opts "$remote") ) 37 | done 38 | fi 39 | 40 | echo 41 | echo git $opts[@] annex "$@" 42 | git $opts[@] annex "$@" 43 | -------------------------------------------------------------------------------- /bin/git-annex-clean-sync: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Run git annex sync while avoiding trying to touch ignored or 4 | # unavailable remotes. 5 | 6 | git-annex-clean sync "$@" 7 | -------------------------------------------------------------------------------- /bin/git-annex-finddups: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use File::Find; 7 | use File::stat; 8 | use Getopt::Long; 9 | 10 | use constant DOTS_EVERY => 10_000; 11 | 12 | my %keys; 13 | my %opts; 14 | my $processed = 0; 15 | my $duplicate_sets = 0; 16 | my $duplicate_files = 0; 17 | my $saved = 0; 18 | my $bytes_formatter = get_bytes_formatter(); 19 | 20 | sub usage { 21 | warn @_, "\n" if @_; 22 | 23 | (my $ME = $0) =~ s,.*/,,; 24 | 25 | die < $dst\n"; 47 | return; 48 | } 49 | 50 | $key =~ s!/(SHA[\w-]+)/\1!/$1!; 51 | push @{ $keys{$key} }, $File::Find::name; 52 | } 53 | 54 | sub get_size { 55 | foreach my $file (@_) { 56 | if (my $s = stat($file)) { 57 | return $s->size; 58 | } 59 | } 60 | # warn "Couldn't stat any of duplicates:\n"; 61 | # warn " $_\n" foreach @_; 62 | # warn "so they won't be counted in the saved space total.\n"; 63 | return 0; 64 | } 65 | 66 | sub show_dups { 67 | while (my ($key, $files) = each %keys) { 68 | next unless @$files > 1; 69 | 70 | $duplicate_sets++; 71 | $duplicate_files += @$files; 72 | my $size = get_size(@$files); 73 | my $surplus = @$files - 1; 74 | #print "$surplus surplus files at $size each\n"; 75 | $saved += $size * $surplus; 76 | print "$key\n" if $opts{keys}; 77 | foreach my $file (sort @$files) { 78 | $file =~ s/"/\\"/g; 79 | print " ", ($file =~ /\s/ ? qq{"$file"} : $file), "\n"; 80 | } 81 | print "\n"; 82 | } 83 | 84 | my $hsaved = $bytes_formatter->($saved); 85 | print "$duplicate_files duplicate files in $duplicate_sets sets (saved $hsaved)\n"; 86 | } 87 | 88 | sub get_bytes_formatter { 89 | eval { require Format::Human::Bytes; }; 90 | if (! $@) { 91 | return sub { Format::Human::Bytes::base10(shift) }; 92 | } 93 | 94 | eval { require Number::Bytes::Human; }; 95 | if (! $@) { 96 | return sub { Number::Bytes::Human::format_bytes(shift) }; 97 | } 98 | 99 | return sub { $_[0] . " bytes" }; 100 | } 101 | 102 | GetOptions(\%opts, 'help|h', 'keys|k') or usage(); 103 | usage() if $opts{help}; 104 | 105 | push @ARGV, '.' unless @ARGV; 106 | 107 | $| = 1; 108 | find(\&wanted, @ARGV); 109 | print STDERR "\n" if $processed > DOTS_EVERY; 110 | show_dups(); 111 | -------------------------------------------------------------------------------- /bin/git-auto-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # See also: 4 | # - https://github.com/gitwatch/gitwatch 5 | 6 | import argparse 7 | import datetime 8 | import logging 9 | import os.path 10 | import pygit2 # type: ignore 11 | import re 12 | import subprocess 13 | import sys 14 | from textwrap import dedent, wrap 15 | 16 | 17 | STATUS_FLAGS = { 18 | pygit2.GIT_STATUS_CURRENT: "CURRENT", 19 | pygit2.GIT_STATUS_INDEX_NEW: "INDEX_NEW", 20 | pygit2.GIT_STATUS_INDEX_MODIFIED: "INDEX_MODIFIED", 21 | pygit2.GIT_STATUS_INDEX_DELETED: "INDEX_DELETED", 22 | pygit2.GIT_STATUS_WT_NEW: "WT_NEW", 23 | pygit2.GIT_STATUS_WT_MODIFIED: "WT_MODIFIED", 24 | pygit2.GIT_STATUS_WT_DELETED: "WT_DELETED", 25 | pygit2.GIT_STATUS_IGNORED: "IGNORED", 26 | pygit2.GIT_STATUS_CONFLICTED: "CONFLICTED", 27 | } 28 | 29 | FORMAT = '%(levelname)-7s | %(message)s' 30 | logging.basicConfig(format=FORMAT) 31 | 32 | 33 | def get_status_output(flags): 34 | if flags & pygit2.GIT_STATUS_IGNORED: 35 | return "!!" 36 | elif flags & pygit2.GIT_STATUS_WT_NEW: 37 | return "??" 38 | elif flags & pygit2.GIT_STATUS_WT_MODIFIED: 39 | return " M" 40 | elif flags & pygit2.GIT_STATUS_WT_DELETED: 41 | return " D" 42 | elif flags & pygit2.GIT_STATUS_INDEX_NEW: 43 | return "A " 44 | elif flags & pygit2.GIT_STATUS_INDEX_MODIFIED: 45 | return "M " 46 | elif flags & pygit2.GIT_STATUS_INDEX_DELETED: 47 | return "D " 48 | elif flags & pygit2.GIT_STATUS_CONFLICTED: 49 | return "UU" 50 | elif flags & pygit2.GIT_STATUS_CURRENT: 51 | return ".." 52 | else: 53 | return " " 54 | 55 | 56 | def get_status_flags(flags): 57 | return [ 58 | descr 59 | for flag, descr in STATUS_FLAGS.items() 60 | if flags & flag 61 | ] 62 | 63 | 64 | def file_is_staged(flags): 65 | return flags & (pygit2.GIT_STATUS_INDEX_NEW 66 | | pygit2.GIT_STATUS_INDEX_MODIFIED 67 | | pygit2.GIT_STATUS_INDEX_DELETED) 68 | 69 | 70 | def file_is_conflicted(flags): 71 | return flags & pygit2.GIT_STATUS_CONFLICTED 72 | 73 | 74 | def get_hostname(): 75 | nick_file = os.path.expanduser("~/.localhost-nickname") 76 | if os.path.isfile(nick_file): 77 | with open(nick_file) as f: 78 | return f.readline().rstrip("\n") 79 | else: 80 | return os.getenv("HOST") or os.getenv("HOSTNAME") 81 | 82 | 83 | def quit(msg): 84 | logging.debug(msg) 85 | sys.exit(1) 86 | 87 | 88 | def abort(msg): 89 | logging.error(msg) 90 | sys.exit(1) 91 | 92 | 93 | class GitAutoCommitter: 94 | def __init__(self, repo_path): 95 | self.repo_path = repo_path 96 | logging.debug("GitAutoCommitter on repo %s" % self.repo_path) 97 | self.repo = pygit2.Repository(repo_path) 98 | self.check_config() 99 | 100 | def check_config(self): 101 | if self.config_get("user.name") is None: 102 | abort("user.name is not set in git config; aborting!") 103 | if self.config_get("user.email") is None: 104 | abort("user.email is not set in git config; aborting!") 105 | 106 | def manual_attention_required(self): 107 | found_issues = [] 108 | for filepath, flags in self.process_files(): 109 | if self.ignored(filepath): 110 | # logging.debug(f"# {filepath} ignored by git") 111 | continue 112 | 113 | if self.auto_commit_policy(filepath) is None: 114 | logging.debug(f"# {filepath} has no autocommit policy") 115 | continue 116 | 117 | st = get_status_output(flags) 118 | if file_is_staged(flags): 119 | logging.debug(f"{st} {filepath}\t\t<-- staged") 120 | found_issues.append(f"{filepath} was staged") 121 | elif file_is_conflicted(flags): 122 | logging.debug(f"{st} {filepath}\t\t<-- conflicted") 123 | found_issues.append(f"{filepath} had conflicts") 124 | else: 125 | logging.debug(f"{st} {filepath}") 126 | 127 | return found_issues 128 | 129 | def auto_commit_changes(self): 130 | staged = self.stage_changes() 131 | if staged > 0: 132 | self.commit_changes() 133 | else: 134 | quit("Nothing to commit") 135 | 136 | def stage_changes(self): 137 | staged = 0 138 | 139 | for filepath, flags in self.process_files(): 140 | if self.ignored(filepath): 141 | logging.debug(f"# {filepath} ignored by git") 142 | continue 143 | 144 | # Flags can be found here: 145 | # https://github.com/libgit2/pygit2/blob/320ee5e733039d4a3cc952b287498dbc5737c353/src/pygit2.c#L312-L320 146 | if self.should_commit(filepath): 147 | if flags & pygit2.GIT_STATUS_WT_NEW: 148 | self.stage_file(filepath, "new") 149 | staged += 1 150 | elif flags & pygit2.GIT_STATUS_WT_MODIFIED: 151 | self.stage_file(filepath, "changed") 152 | staged += 1 153 | 154 | # else: 155 | # fl = " ".join(get_status_flags(flags)) 156 | # logging.debug(f"Not staging {filepath} with flags {fl}") 157 | 158 | if staged > 0: 159 | self.repo.index.write() 160 | return staged 161 | 162 | def auto_commit_policy(self, filepath): 163 | policy = self.repo.get_attr(filepath, "autocommit") 164 | if not policy or policy in ('false', 'none'): 165 | return None 166 | return policy 167 | 168 | def policy_min_age(self, policy, filepath): 169 | m = re.match(r'min-age=\+(\d+)([mshd])', policy) 170 | if not m: 171 | abort(f"Couldn't parse autocommit attribute {policy} for {filepath}") 172 | amount = int(m.group(1)) 173 | unit = m.group(2) 174 | if unit == "s": 175 | secs = amount 176 | elif unit == "m": 177 | secs = amount * 60 178 | elif unit == "h": 179 | secs = amount * 60 * 60 180 | elif unit == "d": 181 | secs = amount * 60 * 60 * 24 182 | else: 183 | abort("BUG in policy_min_age") 184 | logging.debug(f"{filepath} has min-age policy of {secs} secs") 185 | return datetime.timedelta(seconds=secs) 186 | 187 | def should_commit(self, filepath): 188 | policy = self.auto_commit_policy(filepath) 189 | if policy is None: 190 | return False 191 | 192 | min_age = self.policy_min_age(policy, filepath) 193 | commit_age = self.time_since_last_commit(filepath) 194 | file_age = self.time_since_mtime(filepath) 195 | if commit_age < min_age: 196 | logging.debug(f"Not staging {filepath}, " 197 | f"last committed {commit_age} ago") 198 | return False 199 | 200 | if file_age < min_age: 201 | logging.debug(f"Not staging {filepath}, " 202 | f"last modified {file_age} ago") 203 | return False 204 | 205 | return True 206 | 207 | def config_get(self, name): 208 | try: 209 | return self.repo.config[name] 210 | except KeyError: 211 | return None 212 | 213 | def commit_changes(self): 214 | author = pygit2.Signature( 215 | self.repo.config["user.name"], 216 | self.repo.config["user.email"] 217 | ) 218 | committer = pygit2.Signature( 219 | self.config_get("auto-commit.name") 220 | or self.config_get("user.name"), 221 | self.config_get("auto-commit.email") 222 | or self.config_get("user.email") 223 | ) 224 | tree = self.repo.index.write_tree() 225 | head_oid = self.repo.head.resolve().target 226 | host = get_hostname() 227 | message = f"auto-commit on {host} by {__file__}" 228 | oid = self.repo.create_commit( # noqa 229 | "refs/heads/master", author, committer, message, tree, 230 | [head_oid] # parent commit(s) 231 | ) 232 | # commit = self.repo.get(oid) 233 | # logging.debug(f"\n{commit.short_id} {message}") 234 | logging.debug("") 235 | subprocess.call(["git", "show", "--format=fuller", "--name-status"]) 236 | 237 | def process_files(self): 238 | for filepath, flags in self.repo.status().items(): 239 | yield filepath, flags 240 | 241 | def ignored(self, filepath): 242 | dirname, filename = os.path.split(filepath) 243 | if filename.startswith(".#"): 244 | # emacs lock file 245 | return True 246 | 247 | return False 248 | 249 | def time_since_mtime(self, filepath): 250 | now = datetime.datetime.now() 251 | last_change = datetime.datetime.fromtimestamp( 252 | os.stat(filepath).st_mtime) 253 | return now - last_change 254 | 255 | def in_index(self, filepath): 256 | try: 257 | self.repo.index[filepath] 258 | return True 259 | except KeyError: 260 | return False 261 | 262 | def time_since_last_commit(self, filepath): 263 | if not self.in_index(filepath): 264 | logging.debug("%s not in index" % filepath) 265 | return datetime.timedelta(weeks=99999) 266 | 267 | descr = subprocess.check_output( 268 | ["git", "describe", "--always", f"HEAD:{filepath}"], 269 | encoding='utf-8') 270 | ref, _path = descr.split(":", 1) 271 | rev = self.repo.revparse_single(ref) 272 | now = datetime.datetime.now() 273 | last_change = datetime.datetime.fromtimestamp(rev.commit_time) 274 | return now - last_change 275 | 276 | def stage_file(self, filepath, reason): 277 | logging.debug(f"%-20s {filepath}" % f"Staged {reason} file:") 278 | self.repo.index.add(filepath) 279 | 280 | 281 | def parse_args(): 282 | descr = "\n".join(wrap(dedent("""\ 283 | Automatically commit files according to the policy defined 284 | in their autocommit attribute. Doesn't do anything if any 285 | change is already staged in git's index. 286 | """), width=int(os.getenv("COLUMNS", "70")))) 287 | 288 | parser = argparse.ArgumentParser( 289 | description=descr, 290 | formatter_class=argparse.RawDescriptionHelpFormatter, 291 | ) 292 | parser.add_argument( 293 | "-d", "--debug", action="store_true", 294 | help="Enable debug output" 295 | ) 296 | parser.add_argument( 297 | "repo_path", metavar="REPO-PATH", nargs="?", 298 | default=".", help="Path to repository to auto-commit" 299 | ) 300 | 301 | return parser.parse_known_args() 302 | 303 | 304 | def main(): 305 | options, args = parse_args() 306 | if options.debug: 307 | logging.getLogger().setLevel(logging.DEBUG) 308 | 309 | gac = GitAutoCommitter(options.repo_path) 310 | 311 | issues = gac.manual_attention_required() 312 | if issues: 313 | logging.error("Manual attention required:") 314 | for issue in issues: 315 | logging.error(f" {issue}") 316 | sys.exit(1) 317 | 318 | logging.debug("") 319 | gac.auto_commit_changes() 320 | 321 | 322 | main() 323 | -------------------------------------------------------------------------------- /bin/git-cdup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git rev-parse --show-cdup "$@" 4 | -------------------------------------------------------------------------------- /bin/git-check-email: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! git config user.email >/dev/null; then 4 | cat <&2 5 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 6 | WARNING: git user.email config variable is not set! 7 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 8 | 9 | You should type something like: 10 | 11 | git config user.email x@adamspiers.org 12 | 13 | EOF 14 | exit 1 15 | fi 16 | 17 | exit 0 18 | -------------------------------------------------------------------------------- /bin/git-cherry-menu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Interactive wrapper around git-icing and git notes which makes 4 | # it easy to cherry-pick and/or blacklist non-upstreamed commits. 5 | # 6 | # Use git-rnotes to push/pull the upstreaming blacklist to/from 7 | # other repositories: 8 | # 9 | # http://article.gmane.org/gmane.comp.cloud.crowbar/386 10 | 11 | usage () { 12 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 13 | exit_code=1 14 | if [[ "$1" == [0-9] ]]; then 15 | exit_code="$1" 16 | shift 17 | fi 18 | if [ -n "$1" ]; then 19 | echo "$*" >&2 20 | echo 21 | fi 22 | 23 | me=`basename $0` 24 | 25 | cat <&2 26 | usage: ${me/git-/git [] } [...] 27 | suggested options: 28 | 29 | -c cherry-menu.redo-upstreamed=true 30 | Include commits which are annotated has having been 31 | previously upstreamed with a different patch-id. This can 32 | be useful if you decide to hard-reset the upstream branch in 33 | order to redo some cherry-picking you made a mess of, but 34 | want to be able to reuse the notes which were created the 35 | first time round. 36 | 37 | -c cherry-menu.skip-todos=true 38 | Skip commits which have notes including 'TODO'. This allows 39 | unresolved upstreaming tasks to be tracked via an external 40 | issue tracker without getting in the way during repeated 41 | runs of cherry-menu. 42 | 43 | COMMAND is typically "git icing -v2" or "git cherry" but can be 44 | anything which gives output in the same format, e.g. 45 | 46 | git icing -v2 \$upstream \$downstream | grep ... > tmpfile 47 | # Could edit tmpfile here if we want 48 | $me cat tmpfile 49 | 50 | Provides an interactive wrapper around git-icing (or git cherry). For 51 | each commit provided on STDIN by COMMAND which has not yet been 52 | upstreamed, asks the user whether they want to cherry-pick the commit, 53 | blacklist it, or skip it. After a successful cherry-pick, the source 54 | commit will be automatically blacklisted if the patch-id changed. 55 | 56 | You can quit the process at any time and safely re-run it later - it 57 | will resume from where you left off. 58 | 59 | Invoking icing with "-v2" ensures that previously blacklisted / 60 | upstreamed commits are also processed. 61 | EOF 62 | exit "$exit_code" 63 | } 64 | 65 | main () { 66 | parse_opts "$@" 67 | 68 | : ${GIT_NOTES_REF:=refs/notes/upstreaming} 69 | export GIT_NOTES_REF 70 | 71 | exec 3>&0 # save STDIN tty 72 | 73 | "$@" | while read mode sha1 rest; do 74 | head=$( git rev-parse HEAD ) || fatal "git rev-parse HEAD failed; aborting." 75 | 76 | abbrev_sha1 77 | 78 | mode_should_skip && continue 79 | 80 | safe_run git show --notes "$sha1" 81 | echo 82 | 83 | cherry_menu 84 | 85 | if [ -z "$skip" ]; then 86 | echo 87 | do_cherry_pick 88 | fi 89 | 90 | divider 91 | done 92 | } 93 | 94 | parse_opts () { 95 | verbosity=2 96 | 97 | while [ $# != 0 ]; do 98 | case "$1" in 99 | -h|--help) 100 | usage 0 101 | ;; 102 | -*) 103 | usage "Unrecognised option: $1" 104 | ;; 105 | *) 106 | break 107 | ;; 108 | esac 109 | done 110 | 111 | if [ $# = 0 ]; then 112 | usage 113 | fi 114 | } 115 | 116 | colour () { 117 | colour="$1" 118 | shift 119 | echo -e "\e[${colour}m$*\e[0m" 120 | } 121 | 122 | blacklist_if_new_patch_id () { 123 | old_patch_id=$( safe_run git show "$sha1" | git patch-id | awk '{print $1}' ) \ 124 | || fatal "Failed to retrieve patch-id for $abbrev_sha1" 125 | new_patch_id=$( safe_run git show HEAD | git patch-id | awk '{print $1}' ) \ 126 | || fatal "Failed to retrieve patch-id for HEAD" 127 | if [ "$old_patch_id" != "$new_patch_id" ]; then 128 | colour "1;33" "The git patch-id changed during cherry-picking. This is normal when" 129 | colour "1;33" "the diff context changes or merge conflicts are resolved." 130 | safe_run git notes add -m"skip: all 131 | 132 | patch-id changed by cherry-picking." "$sha1" 133 | colour "1;35" "Blacklisted $abbrev_sha1 so future runs won't attempt to duplicate the upstreaming." 134 | prompt_to_continue 135 | fi 136 | } 137 | 138 | abbrev_sha1 () { 139 | if [ "${#sha1}" = 40 ]; then 140 | abbrev_sha1="${sha1:0:10}" 141 | else 142 | abbrev_sha1="${sha1}" 143 | fi 144 | } 145 | 146 | mode_should_skip () { 147 | case "$mode" in 148 | -|\#) 149 | colour "1;32" "Already upstream: $abbrev_sha1 - $rest" 150 | return 0 151 | ;; 152 | .) 153 | colour "1;35" "Blacklisted: $abbrev_sha1 - $rest" 154 | 155 | [ "$(git config cherry-menu.redo-upstreamed)" != 'true' ] && continue 156 | if safe_run git notes show "$sha1" | grep -iq 'patch[ -]id'; then 157 | colour "1;34" "cherry-menu.redo-upstreamed is set, so redoing $abbrev_sha1 which is probably blacklisted only due to patch-id change:" 158 | echo 159 | else 160 | return 0 161 | fi 162 | ;; 163 | \?) 164 | echo 165 | colour "1;31" "Unparseable note for:" 166 | colour "1;31" " $abbrev_sha1 - $rest" 167 | colour "1;31" "Please edit via the 'b' option." 168 | echo 169 | prompt_to_continue 170 | return 1 171 | ;; 172 | .\?) 173 | echo 174 | colour "1;31" "Unexpected blacklisting note for upstreamed commit:" 175 | colour "1;31" " $abbrev_sha1 - $rest" 176 | colour "1;31" "Please edit via the 'b' option." 177 | echo 178 | prompt_to_continue 179 | return 1 180 | ;; 181 | !\?) 182 | echo 183 | colour "1;31" "Unexpected TODO note for upstreamed commit:" 184 | colour "1;31" " $abbrev_sha1 - $rest" 185 | colour "1;31" "Please edit via the 'b' option." 186 | echo 187 | prompt_to_continue 188 | return 1 189 | ;; 190 | !) 191 | if [ "$(git config cherry-menu.skip-todos)" = 'true' ]; then 192 | colour "35" "TODO note for: $abbrev_sha1 - $rest" 193 | return 0 194 | else 195 | echo 196 | colour "35" "TODO note for: $abbrev_sha1 - $rest" 197 | echo 198 | return 1 199 | fi 200 | ;; 201 | +) 202 | return 1 203 | ;; 204 | *) 205 | fatal "Unrecognised mode '$mode' from '$ARGV'; aborting." 206 | ;; 207 | esac 208 | } 209 | 210 | do_cherry_pick () { 211 | if ! git cherry-pick -x -s "$sha1"; then 212 | while true; do 213 | echo 214 | echo "Spawning a shell so you can fix; exit the shell when done." 215 | "${SHELL:-/bin/sh}" <&3 216 | 217 | if [[ $(git status --porcelain) ]]; then 218 | git status 219 | echo 220 | warn "Working tree is still not clean; please try again." 221 | else 222 | break 223 | fi 224 | done 225 | fi 226 | 227 | new_head=$( git rev-parse HEAD ) || fatal "git rev-parse HEAD failed; aborting." 228 | if [ "$new_head" = "$head" ]; then 229 | echo "Warning: HEAD did not change; no action taken." 230 | prompt_to_continue 231 | else 232 | blacklist_if_new_patch_id 233 | fi 234 | } 235 | 236 | has_note () { 237 | git notes show "$1" >/dev/null 2>&1 238 | } 239 | 240 | prompt_to_continue () { 241 | echo -n "Press enter to continue ... " 242 | read <&3 243 | } 244 | 245 | cherry_menu () { 246 | skip= 247 | while true; do 248 | echo -n "Cherry-pick / blacklist / skip $abbrev_sha1, or quit [c/b/s/q]? " 249 | read answer <&3 250 | case "$answer" in 251 | c) 252 | break 253 | ;; 254 | b) 255 | if ! has_note "$sha1"; then 256 | safe_run git notes append -m'skip: all 257 | XXX (you can optionally change the "all" above to the name of the 258 | XXX upstream branch if you want to limit blacklisting to that upstream) 259 | 260 | XXX Enter your justification for blacklisting here or 261 | XXX remove the whole note to cancel blacklisting.' "$sha1" 262 | fi 263 | safe_run git notes edit "$sha1" <&3 264 | echo 265 | if has_note "$sha1"; then 266 | colour "1;35" "Blacklisted $abbrev_sha1" 267 | else 268 | colour "1;35" "$abbrev_sha1 not blacklisted" 269 | fi 270 | prompt_to_continue 271 | skip=y 272 | break 273 | ;; 274 | s) 275 | colour "1;34" "Skipping $abbrev_sha1" 276 | sleep 1 277 | skip=y 278 | break 279 | ;; 280 | q) 281 | exit 0 282 | ;; 283 | *) 284 | ;; 285 | esac 286 | done 287 | } 288 | 289 | warn () { 290 | colour "1;33" "$*" >&2 291 | } 292 | 293 | fatal () { 294 | colour "1;31" "$*" >&2 295 | exit 1 296 | } 297 | 298 | safe_run () { 299 | if ! "$@"; then 300 | fatal "$* failed! Aborting." 301 | fi 302 | } 303 | 304 | divider () { 305 | echo 306 | echo "-----------------------------------------------------------------------" 307 | echo 308 | } 309 | 310 | ARGV="$*" 311 | 312 | main "$@" 313 | 314 | -------------------------------------------------------------------------------- /bin/git-compare: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # http://stackoverflow.com/questions/2969214/git-programmatically-know-by-how-much-the-branch-is-ahead-behind-a-remote-branc 4 | 5 | usage () { 6 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 7 | exit_code=1 8 | if [[ "$1" == [0-9] ]]; then 9 | exit_code="$1" 10 | shift 11 | fi 12 | if [ -n "$1" ]; then 13 | echo >&2 "$*" 14 | echo 15 | fi 16 | 17 | me=`basename $0` 18 | 19 | cat <&2 20 | Usage: $me [options] LEFT RIGHT 21 | 22 | LEFT and RIGHT are "commit-ish" references. 23 | 24 | Options: 25 | -h, --help Show this help and exit 26 | -q Quiet mode 27 | --sh Output result as shell code to be source'd 28 | options 29 | EOF 30 | exit "$exit_code" 31 | } 32 | 33 | parse_opts () { 34 | shmode= 35 | 36 | while [ $# != 0 ]; do 37 | case "$1" in 38 | -h|--help) 39 | usage 0 40 | ;; 41 | --sh) 42 | shmode=yes 43 | shift 44 | ;; 45 | -*) 46 | usage "Unrecognised option: $1" 47 | ;; 48 | *) 49 | break 50 | ;; 51 | esac 52 | done 53 | 54 | if [ $# != 2 ]; then 55 | usage 56 | fi 57 | 58 | left="$1" 59 | right="$2" 60 | } 61 | 62 | main () { 63 | parse_opts "$@" 64 | 65 | set -- `git rev-list --count --left-right ${left}...${right}` 66 | behind="$1" 67 | ahead="$2" 68 | 69 | if [ "$1" = '-q' ]; then 70 | # quiet mode; just exit successfully iff we're in sync with upstream 71 | exit $(( behind + ahead )) 72 | fi 73 | 74 | if [ -n "$shmode" ]; then 75 | cat <&2 92 | exit 1 93 | fi 94 | } 95 | 96 | main "$@" 97 | -------------------------------------------------------------------------------- /bin/git-compare-upstream: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # http://stackoverflow.com/questions/2969214/git-programmatically-know-by-how-much-the-branch-is-ahead-behind-a-remote-branc 4 | 5 | shmode= 6 | if [ "$1" = '--sh' ]; then 7 | shmode="$1" 8 | shift 9 | fi 10 | 11 | head=`git head` 12 | upstream=`git upstream` 13 | 14 | git compare $shmode "$upstream" "$head" 15 | -------------------------------------------------------------------------------- /bin/git-deps: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat <&2 "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | remote="$1" 9 | 10 | if git remote-is-up "$remote"; then 11 | echo >&2 "$remote is up" 12 | else 13 | echo >&2 "$remote appears to be down; excluding" 14 | echo \ 15 | -c remote."$1".url= \ 16 | -c remote."$1".annex-ignore=true 17 | fi 18 | -------------------------------------------------------------------------------- /bin/git-dwim: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git sync-upstream || exit 1 4 | 5 | if [ "`git upstream`" != 'github' -a -n "`git config remote.github.url`" ]; then 6 | if ! head=`git head`; then 7 | echo "git head failed; aborting!" >&2 8 | exit 1 9 | fi 10 | 11 | git sync-upstream "github/$head" 12 | fi 13 | -------------------------------------------------------------------------------- /bin/git-export-bz2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git archive "$@" | bzip2 -c 4 | -------------------------------------------------------------------------------- /bin/git-find-blob: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # From http://stackoverflow.com/questions/223678/git-which-commit-has-this-blob 4 | 5 | use strict; 6 | use warnings; 7 | 8 | use Memoize; 9 | 10 | sub usage { 11 | warn @_, "\n" if @_; 12 | 13 | (my $ME = $0) =~ s,.*/,,; 14 | 15 | die < [] 17 | Options: 18 | -h, --help Show this help 19 | 20 | Finds which commits have the given blob. 21 | EOUSAGE 22 | } 23 | 24 | sub check_tree { 25 | my ($tree, $obj_name) = @_; 26 | my @subtree; 27 | 28 | { 29 | open my $ls_tree, '-|', git => 'ls-tree' => $tree 30 | or die "Couldn't open pipe to git-ls-tree: $!\n"; 31 | 32 | while (<$ls_tree>) { 33 | /\A[0-7]{6} (\S+) (\S+)/ 34 | or die "unexpected git-ls-tree output"; 35 | return 1 if $2 eq $obj_name; 36 | push @subtree, $2 if $1 eq 'tree'; 37 | } 38 | } 39 | 40 | check_tree($_, $obj_name) && return 1 for @subtree; 41 | 42 | return; 43 | } 44 | 45 | memoize 'check_tree'; 46 | 47 | usage if not @ARGV or $ARGV[0] eq '-h' or $ARGV[0] eq '--help'; 48 | 49 | my $blob_arg = shift; 50 | open my $rev_parse, '-|', git => 'rev-parse' => '--verify', $blob_arg 51 | or die "Couldn't open pipe to git-rev-parse: $!\n"; 52 | 53 | chomp(my $obj_name = <$rev_parse> || ''); 54 | die "git rev-parse --verify $blob_arg failed\n" 55 | unless close($rev_parse); 56 | die "git rev-parse returned unexpected output: $obj_name\n" 57 | unless $obj_name =~ /^[0-9a-f]{40}$/; 58 | 59 | print "(full blob is $obj_name)\n" if $obj_name ne $blob_arg; 60 | 61 | open my $log, '-|', 'git', 'log', '--all', @ARGV, '--pretty=format:%T %h %s' 62 | or die "Couldn't open pipe to git-log: $!\n"; 63 | 64 | while (<$log>) { 65 | chomp; 66 | my ( $tree, $commit, $subject ) = split " ", $_, 3; 67 | print "$commit $subject\n" if check_tree($tree, $obj_name); 68 | } 69 | -------------------------------------------------------------------------------- /bin/git-find-missing-submodule-commits: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | me=`basename $0` 4 | 5 | usage () { 6 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 7 | exit_code=1 8 | if [[ "$1" == [0-9] ]]; then 9 | exit_code="$1" 10 | shift 11 | fi 12 | if [ -n "$1" ]; then 13 | echo "$*" >&2 14 | echo 15 | fi 16 | 17 | cat <&2 18 | Usage: $me [OPTIONS] COMMIT-REF [COMMIT-REF ...] [-- SUBMODULE ...] 19 | Options: 20 | -v, --verbose Verbose output 21 | -b, --blame Find commits introducing dangling references to submodule commits 22 | -f, --format Format for commits shown with --blame option [medium] 23 | -h, --help Show this help 24 | 25 | For each commit ref given, checks that each given submodule in the 26 | commit tree references a valid commit in the submodule. 27 | 28 | Valid refs are of the form: 29 | 30 | refs/heads 31 | refs/heads/master 32 | refs/remotes 33 | refs/remotes/myremote 34 | 35 | etc. 36 | 37 | If no submodules are given, obtains a list from git config. 38 | Submodules should be relative paths from the root of the git repo. 39 | EOF 40 | exit "$exit_code" 41 | } 42 | 43 | parse_args () { 44 | verbose= 45 | blame= 46 | format=medium 47 | 48 | while [ -n "$1" ]; do 49 | case "$1" in 50 | -h|--help) 51 | shift 52 | usage 0 53 | ;; 54 | -v|--verbose) 55 | verbose=true 56 | shift 57 | ;; 58 | -b|--blame) 59 | blame=true 60 | shift 61 | ;; 62 | -f|--format) 63 | format="$2" 64 | shift 2 65 | ;; 66 | *) 67 | break 68 | ;; 69 | esac 70 | done 71 | 72 | while [ -n "$1" ]; do 73 | if [ "$1" == '--' ]; then 74 | shift 75 | break 76 | fi 77 | refs+=( "$1" ) 78 | shift 79 | done 80 | 81 | verbose "Checking refs: ${refs[*]}" 82 | 83 | if [ -n "$1" ]; then 84 | submodules=( "$@" ) 85 | verbose "Using submodules from CLI: ${submodules[*]}" 86 | else 87 | while read key val; do 88 | submodule="${key#submodule.}" 89 | submodule="${submodule%.url}" 90 | submodules+=( "$submodule" ) 91 | done < <( git config --get-regexp 'submodule\.' ) 92 | verbose "Using submodules from git config: ${submodules[*]}" 93 | fi 94 | } 95 | 96 | find_culprit () { 97 | local ref="$1" 98 | local subcommit="$2" 99 | git --no-pager log -n1 --pretty="$format" -S"$subcommit" "$ref" 100 | } 101 | 102 | verbose () { 103 | if [ -n "$verbose" ]; then 104 | echo "$*" 105 | fi 106 | } 107 | 108 | declare -a refs submodules 109 | parse_args "$@" 110 | 111 | repo_root=`pwd` 112 | 113 | git for-each-ref "${refs[@]}" | 114 | while read hash objtype ref; do 115 | if [ "$objtype" != 'commit' ] && [ "$objtype" != 'tag' ]; then 116 | echo "Warning: ignoring $objtype $ref" >&2 117 | continue 118 | fi 119 | 120 | if [ -z "$verbose" ]; then 121 | short_ref="${ref#refs/*/}" 122 | else 123 | short_ref="$ref" 124 | fi 125 | for submodule in "${submodules[@]}"; do 126 | if subcommit=$( git rev-parse -q --verify $ref:$submodule ); then 127 | ( 128 | cd $submodule 129 | if ! git cat-file -e $subcommit >/dev/null; then 130 | echo "$short_ref: $submodule/ missing $subcommit" 131 | if [ -n "$blame" ]; then 132 | cd "$repo_root" 133 | find_culprit "$short_ref" "$subcommit" 134 | fi 135 | else 136 | verbose "$short_ref: $submodule/ ok $subcommit" 137 | fi 138 | ) 139 | else 140 | verbose "$short_ref: no reference to $submodule" 141 | fi 142 | done 143 | done 144 | -------------------------------------------------------------------------------- /bin/git-head: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # http://stackoverflow.com/questions/1417957/show-just-the-current-branch-in-git 4 | 5 | # Hmm. This is a good example of why people sometimes complain that 6 | # the git porcelain is ugly. 7 | 8 | set -o pipefail 9 | 10 | # Parsing porcelain is an ugly approach because it couples the 11 | # solution to the formatting which could change: 12 | #git branch | awk '$1 == "*" { print $2 }' 13 | 14 | # This one only works with git >= 1.6.3, but that's been around 15 | # since Sept 2010 so it should be fine by now. 16 | #ref=$( git rev-parse --abbrev-ref HEAD ) 17 | #if [ "$ref" = HEAD ]; then 18 | # # detached branch 19 | # git rev-parse HEAD 20 | #else 21 | # echo "$ref" 22 | #fi 23 | 24 | # The maintainer-approved solution: 25 | # https://git-blame.blogspot.co.uk/2013/06/checking-current-branch-programatically.html 26 | # http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch 27 | if branch=$(git symbolic-ref --short -q HEAD); then 28 | echo "$branch" 29 | else 30 | git rev-parse HEAD 31 | fi 32 | -------------------------------------------------------------------------------- /bin/git-icing: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # 3 | # Some extra icing on top of git cherry's tasty cake ... 4 | # 5 | # - Allow blacklisting of commits which should never be upstreamed, 6 | # via the git-notes(1) mechanism. To blacklist a commit from being 7 | # upstreamed to any branch: 8 | # 9 | # git notes --ref=upstreaming add -m'skip: all' $sha 10 | # 11 | # or to blacklist from being upstreamed to specific branches (currently 12 | # only works when the upstream branch is explicitly provided): 13 | # 14 | # git notes --ref=upstreaming add -m'skip: upstream-branch' $sha 15 | # git notes --ref=upstreaming append -m'skip: another-branch' $sha 16 | # git notes --ref=upstreaming append -m'skip: /branch-regexp/' $sha 17 | # 18 | # It is strongly recommended that you also include justification for 19 | # why this commit should not be upstreamed. You can place free-form 20 | # text in the note, as long as the 'skip: ' line is preserved, with 21 | # no indentation. To edit the note: 22 | # 23 | # git notes --ref=upstreaming edit $sha 24 | # 25 | # To remove from the blacklist: 26 | # 27 | # git notes --ref=upstreaming remove $sha 28 | # 29 | # To push / pull the blacklist notes between git repositories, see: 30 | # 31 | # http://stackoverflow.com/questions/12055303/merging-git-notes-when-there-are-merge-conflicts-in-them/ 32 | # 33 | # - Categorise and colour-code commits. The first field of each 34 | # line output by `git cherry' is extended to show more than just 35 | # `+' and `-'. Run `git icing --help' to show all possibilities. 36 | 37 | require 'optparse' 38 | require 'open3' 39 | require 'shellwords' 40 | 41 | # This would make the code nicer, but let's minimise dependencies instead ... 42 | # require 'term/ansicolor' 43 | # include Term::ANSIColor 44 | 45 | CATEGORIES = [ 46 | [ '+', 1, '31', 'not yet upstream' ], 47 | [ '!', 1, '35', 'still needs to be upstreamed, tracked on a TODO list' ], 48 | [ '.?', 1, '1;30', 'upstream *and* blacklisted?!' ], 49 | [ '!?', 1, '1;30', 'upstream *and* marked as a TODO?' ], 50 | [ '.', 2, '1;30', 'blacklisted - should not be pushed to this upstream' ], 51 | [ '?', 2, '33', 'not yet upstream, with unparseable note' ], 52 | [ '-', 3, '32', 'already upstream' ], 53 | [ '#', 3, '36', 'already upstream with an annotation note' ], 54 | ] 55 | 56 | CATEGORIES_BY_PREFIX = CATEGORIES.inject({}) { |h, new| h[new[0]] = new[1, 3]; h } 57 | 58 | $verbosity = 1 59 | $summary = false 60 | 61 | def output(level, colour, msg) 62 | return unless level <= $verbosity 63 | 64 | if STDOUT.tty? and colour 65 | msg = "\033[#{colour}m#{msg}\033[0m" 66 | end 67 | puts msg 68 | end 69 | 70 | def upstreaming_note(sha) 71 | ENV['LANG'] = 'C' 72 | cmd = [ 'git', 'notes', '--ref=upstreaming', 'show', sha ] 73 | out = nil 74 | Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thread| 75 | out = stdout.readlines().join('') 76 | err = stderr.readlines().join('') 77 | status = wait_thread.value 78 | return nil if err =~ /No note found for object/ 79 | unless status.success? 80 | raise cmd.shelljoin + " exited with status #{status.exitstatus}: #{err}" 81 | end 82 | unless err.empty? 83 | raise cmd.shelljoin + " outputted to STDERR: #{err}" 84 | end 85 | end 86 | out 87 | end 88 | 89 | def blacklisted(note, upstream_branch) 90 | skippers = note.split("\n").grep(/^skip: (.+?)\s*$/m) { |x| $1 } 91 | for skipper in skippers 92 | return true if skipper == 'all' 93 | if upstream_branch 94 | skipper = Regexp.new($1) if skipper =~ %r{^/(.+)/$} 95 | return true if skipper === upstream_branch 96 | end 97 | end 98 | false 99 | end 100 | 101 | def parse_options 102 | parser = OptionParser.new do |opts| 103 | opts.banner = "usage: git icing [options] [git cherry args]\n\n" 104 | opts.on("-v", "--verbosity [N]", Integer, "Set verbosity level") do |verbosity| 105 | $verbosity = verbosity || 2 106 | end 107 | opts.on("-s", "--summary", Integer, "Show summary") do 108 | $summary = true 109 | end 110 | end 111 | 112 | def parser.help 113 | rows = CATEGORIES.map do |field, level, colour, description| 114 | field = "%-2s" % field 115 | if STDOUT.tty? 116 | " \e[#{colour}m#{field}\e[0m | #{level} | \e[#{colour}m#{description}\e[0m" 117 | else 118 | " #{field} | #{level} | #{description}" 119 | end 120 | end 121 | super + < 11 | # 12 | # Note that this is significantly better performing than the following 13 | # quick and dirty shell hack: 14 | # 15 | # git ls-files "$@" | while read file; do 16 | # git --no-pager log \ 17 | # -n 1 \ 18 | # --date=iso \ 19 | # --format="%Cblue%h%Creset %Cgreen%ad%Creset C(yellow)%an%Creset $file %C(cyan)[%s]%Creset" \ 20 | # -- $file 21 | # done 22 | # 23 | # See also: 24 | # http://stackoverflow.com/questions/223678/git-which-commit-has-this-blob 25 | # http://stackoverflow.com/questions/6957441/how-to-find-out-what-commit-a-checked-out-file-came-from 26 | 27 | use strict; 28 | use warnings; 29 | 30 | use Getopt::Long; 31 | use Term::ANSIColor qw(:constants colorstrip); 32 | 33 | sub usage { 34 | warn @_, "\n" if @_; 35 | 36 | (my $ME = $0) =~ s,.*/,,; 37 | 38 | die <) { 51 | if (/^\S+\s+(blob|commit) \S+\s+(\S+)$/) { 52 | my $filename = $2; 53 | $unknown{$filename}++; 54 | } 55 | } 56 | close IN or exit 1; # die "Error running $cmd" . (length $! ? ": $!" : '') . "\n"; 57 | 58 | if (! %unknown) { 59 | die <) { 82 | chomp; 83 | if (/^([0-9a-f]+)~(.*)~([^~]+)~(.+)$/) { 84 | ($commit, $author, $date, $message) = ($1, $2, $3, $4); 85 | #print "Got commit $commit\n"; 86 | if (++$commits % 10_000 == 0) { 87 | print STDERR "."; 88 | $dots++; 89 | } 90 | } 91 | elsif (/^:[0-9]+\s+[0-9]+\s+[0-9a-f]+\s+[0-9a-f]+\s+[A-Z]\s+(.*)$/) { 92 | my $path = $1; 93 | unless ($date) { 94 | die "INTERNAL ERROR: didn't get commit meta-data prior to file blob $1; aborting\n"; 95 | } 96 | if ($unknown->{$path}) { 97 | delete $unknown->{$path}; 98 | $found++; 99 | if (++$found % 10_000 == 0) { 100 | print STDERR "+"; 101 | $dots++; 102 | } 103 | my $short_commit = substr($commit, 0, 8); 104 | #print "$short_commit $path\n"; 105 | push @attributed, { 106 | path => $path, 107 | author => $author, 108 | date => $date, 109 | commit => $short_commit, 110 | message => $message, 111 | }; 112 | %$unknown or last; 113 | } 114 | } 115 | else { 116 | #print "Didn't parse: [$_]\n"; 117 | } 118 | } 119 | close IN; 120 | 121 | print STDERR "\n" if $dots; 122 | 123 | return \@attributed; 124 | } 125 | 126 | sub show_results { 127 | my ($attributed) = @_; 128 | 129 | chomp(my $prefix = `git rev-parse --show-prefix`); 130 | 131 | for my $a (@$attributed) { 132 | (my $path = $a->{path}) =~ s!^\Q$prefix!!; 133 | my @fields = ( 134 | CYAN . $a->{commit}, 135 | GREEN . $a->{date}, 136 | YELLOW . $a->{author}, 137 | RESET . $path, 138 | BLUE . " [$a->{message}]" 139 | ); 140 | my $line = sprintf RESET . join(' ', @fields) . RESET . "\n"; 141 | $line = colorstrip($line) unless -t 1; 142 | print $line; 143 | } 144 | } 145 | 146 | sub main { 147 | my %opts = ( commitish => 'HEAD' ); 148 | 149 | Getopt::Long::Configure('pass_through'); 150 | 151 | GetOptions(\%opts, 'help|h', 'commitish|c=s') or usage(); 152 | usage() if $opts{help}; 153 | 154 | my $unknown = get_file_list($opts{commitish}); 155 | my $attributed = find_commits($opts{commitish}, $unknown); 156 | 157 | if (%$unknown) { 158 | die "ERROR! Failed to find commits for the following files:\n", 159 | map " $_\n", sort keys %$unknown; 160 | } 161 | 162 | show_results($attributed); 163 | } 164 | 165 | main(); 166 | -------------------------------------------------------------------------------- /bin/git-ls-emacs-lock-files: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git ls-files -i -o --exclude-standard | grep '^\.#.' 4 | -------------------------------------------------------------------------------- /bin/git-merged: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Show which branches are merged into upstream or the given commit-ish 4 | # (N.B. depends on `git-root` and `git-upstream`) 5 | 6 | me=`basename $0` 7 | 8 | usage () { 9 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 10 | exit_code=1 11 | if [[ "$1" == [0-9] ]]; then 12 | exit_code="$1" 13 | shift 14 | fi 15 | if [ -n "$1" ]; then 16 | echo >&2 "$*" 17 | echo 18 | fi 19 | 20 | cat <&2 21 | Usage: $me [options] [UPSTREAM-BRANCH] 22 | Options: 23 | -o, --orphans Only show branches which are not in any remotes 24 | -h, --help Show this help and exit 25 | EOF 26 | exit "$exit_code" 27 | } 28 | 29 | parse_opts () { 30 | while [ $# != 0 ]; do 31 | case "$1" in 32 | -h|--help) 33 | usage 0 34 | ;; 35 | -o|--orphans) 36 | orphans=y 37 | ;; 38 | -*) 39 | usage "Unrecognised option: $1" 40 | ;; 41 | *) 42 | break 43 | ;; 44 | esac 45 | shift 46 | done 47 | 48 | if [ $# -gt 1 ]; then 49 | usage 50 | fi 51 | 52 | args=("$@") 53 | } 54 | 55 | find_remotes_with_branch () { 56 | remotes=() 57 | cd $root/.git/refs/remotes/ 58 | for remote in *; do 59 | [[ -e "$remote/$1" ]] && remotes+=( "$remote" ) 60 | done 61 | } 62 | 63 | get_behind_ahead () { 64 | # http://stackoverflow.com/questions/2969214/git-programmatically-know-by-how-much-the-branch-is-ahead-behind-a-remote-branc 65 | set -- `git rev-list --count --left-right $upstream...$branch` 66 | behind="$1" 67 | ahead="$2" 68 | } 69 | 70 | analyse_branch () { 71 | get_behind_ahead "$upstream" "$branch" 72 | if [[ "$ahead" != 0 ]]; then 73 | echo >&2 "BUG: $branch from 'git branch --merged' was ahead by $ahead commits?!" 74 | exit 1 75 | fi 76 | 77 | find_remotes_with_branch "$branch" 78 | 79 | if [[ $orphans ]]; then 80 | if ! [[ $remotes ]]; then 81 | echo -e "\e[0;1m$branch\e[0m is behind \e[1;34m$behind\e[0m" 82 | fi 83 | return 0 84 | fi 85 | 86 | echo -ne "\e[0;1m$branch\e[0m is behind \e[1;34m$behind\e[0m" 87 | if [[ $remotes ]]; then 88 | echo -e ", also in:" 89 | if [[ "${#remotes[@]}" = 1 ]]; then 90 | echo -ne "\e[33m" 91 | else 92 | echo -ne "\e[32m" 93 | fi 94 | for remote in "${remotes[@]}"; do 95 | if [[ -e "$remote/$1" ]]; then 96 | echo "${remote##*/}" 97 | fi 98 | done | 99 | column -c $(( COLUMNS - 4 )) | 100 | sed 's/^/ /' 101 | echo -ne "\e[0m" 102 | else 103 | echo -e " and \e[1;31mnot in any remotes\e[0m" 104 | fi 105 | } 106 | 107 | main () { 108 | root=`git root` 109 | upstream="$1" 110 | if ! [[ $upstream ]]; then 111 | upstream=`git upstream` || exit 1 112 | fi 113 | 114 | [[ $orphans ]] || echo -e "Branches merged into \e[1;35m$upstream\e[0m:\n" 115 | git branch --no-color --merged "$upstream" | cut -c3- | 116 | while read branch; do 117 | analyse_branch "$branch" 118 | done 119 | } 120 | 121 | color () { 122 | if [[ -t 1 ]]; then 123 | cat 124 | else 125 | esc=`echo -e '\e'` 126 | sed "s/${esc}\[[0-9;]\+m//g" 127 | fi 128 | } 129 | 130 | parse_opts "$@" 131 | main "${args[@]}" | color 132 | -------------------------------------------------------------------------------- /bin/git-mix: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # git-mix - syntactic sugar for configuring "git mixdown" 4 | # 5 | # Wrapper around git-config(1) which makes it convenient to configure 6 | # mix branches for use with git-mixdown. 7 | # 8 | # Copyright (C) 2014 Adam Spiers 9 | # 10 | # The software in this repository is free software: you can redistribute 11 | # it and/or modify it under the terms of the GNU General Public License 12 | # as published by the Free Software Foundation, either version 3 of the 13 | # License, or (at your option) any later version. 14 | # 15 | # This software is distributed in the hope that it will be useful, but 16 | # WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | # General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | default_mix_branch=working 24 | 25 | usage () { 26 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 27 | exit_code=1 28 | if [[ "$1" == [0-9] ]]; then 29 | exit_code="$1" 30 | shift 31 | fi 32 | if [ -n "$1" ]; then 33 | echo >&2 "$*" 34 | echo 35 | fi 36 | 37 | me='git mix' 38 | 39 | cat <&2 40 | usage: $me [options] [--] [arguments] 41 | 42 | Arguments: 43 | Show all available mixes 44 | Show mix for target branch 45 | A B [C ...] Set mix for target branch to branches A, B, ... 46 | [] +D [-B ...] Add/remove branches to/from mix for target 47 | branch (defaults to 'working') 48 | 49 | Options: 50 | -h, --help Show this help and exit 51 | -d Remove mix for target branch 52 | 53 | You can also change the mix via git config, e.g. edit the mix order 54 | via git config --edit. 55 | EOF 56 | exit "$exit_code" 57 | } 58 | 59 | parse_opts () { 60 | checkout= 61 | 62 | while [ $# != 0 ]; do 63 | case "$1" in 64 | -h|--help) 65 | usage 0 66 | ;; 67 | -d) 68 | if [ $# != 2 ]; then 69 | usage 70 | fi 71 | delete_mix "$2" 72 | exit 0 73 | ;; 74 | --) 75 | shift 76 | break 77 | ;; 78 | *) 79 | break 80 | ;; 81 | esac 82 | done 83 | 84 | ARGV=( "$@" ) 85 | } 86 | 87 | fatal () { 88 | echo "$*" >&2 89 | exit 1 90 | } 91 | 92 | show_mixes () { 93 | # git config --list | grep "^mixdown\." | sed 's/^mixdown\.//' | \ 94 | # sort -t= -k1,1 --stable 95 | mixes=( $( 96 | git config --list | grep "^mixdown\." | \ 97 | sed -e 's/^mixdown\.//' -e 's/=.*//' | sort -u 98 | ) ) 99 | for mix in "${mixes[@]}"; do 100 | unescape_mix 101 | echo -n "$mix: " 102 | echo `list_mix "$mix"` # evil hack to convert new lines to spaces 103 | done 104 | } 105 | 106 | list_mix () { 107 | mix="$1" 108 | escape_mix 109 | if git config --get "mixdown.$mix" >/dev/null 2>&1; then 110 | git config --list | grep "^mixdown\.$mix=" | sed "s/^mixdown\.$mix=//" 111 | else 112 | fatal "ERROR: No such mix configured for '$mix'" 113 | fi 114 | } 115 | 116 | delete_mix () { 117 | mix="$1" 118 | escape_mix 119 | git config --unset-all "mixdown.$mix" 120 | } 121 | 122 | set_mix () { 123 | mix="$1" 124 | escape_mix 125 | shift 126 | 127 | delete_mix "$mix" 128 | 129 | for branch in "$@"; do 130 | git config --add "mixdown.$mix" "$branch" 131 | done 132 | } 133 | 134 | SLASH="-x2f-" 135 | 136 | escape_mix () { 137 | mix="${mix//\//$SLASH}" 138 | } 139 | 140 | unescape_mix () { 141 | mix="${mix//$SLASH/\/}" 142 | } 143 | 144 | change_mix () { 145 | mix="$1" 146 | escape_mix 147 | shift 148 | 149 | key="mixdown.${mix//\//_}" 150 | 151 | for change in "$@"; do 152 | case "$change" in 153 | -*) 154 | branch="${change#-}" 155 | regexp="^$branch\$" 156 | if git config --get "$key" "$regexp" >/dev/null; then 157 | git config --unset "$key" "$regexp" 158 | else 159 | echo "WARNING: $branch was not in the mix for $mix" >&2 160 | fi 161 | ;; 162 | +?*) 163 | git config --add "$key" "${change#+}" 164 | ;; 165 | *) 166 | usage "Invalid change '$change'." 167 | ;; 168 | esac 169 | done 170 | } 171 | 172 | main () { 173 | parse_opts "$@" 174 | set -- "${ARGV[@]}" 175 | 176 | if [ $# = 0 ]; then 177 | show_mixes 178 | elif [[ $1 == [-+]* ]]; then 179 | change_mix "$default_mix_branch" "$@" 180 | elif [ $# = 1 ]; then 181 | list_mix "${1,,}" 182 | else 183 | mix="${1,,}" 184 | shift 185 | if [[ $1 == [-+]* ]]; then 186 | change_mix "$mix" "$@" 187 | else 188 | set_mix "$mix" "$@" 189 | fi 190 | fi 191 | } 192 | 193 | main "$@" 194 | -------------------------------------------------------------------------------- /bin/git-mixdown: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # git-mixdown - "mixdown" multiple branches into a combined working branch 4 | # 5 | # Useful for testing a combination of bugfixes / features at once via 6 | # a throw-away temporary working branch. See accompanying utility git-mix. 7 | # 8 | # Copyright (C) 2013 Adam Spiers 9 | # 10 | # The software in this repository is free software: you can redistribute 11 | # it and/or modify it under the terms of the GNU General Public License 12 | # as published by the Free Software Foundation, either version 3 of the 13 | # License, or (at your option) any later version. 14 | # 15 | # This software is distributed in the hope that it will be useful, but 16 | # WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | # General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | combined_branch=working 24 | 25 | usage () { 26 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 27 | exit_code=1 28 | if [[ "$1" == [0-9] ]]; then 29 | exit_code="$1" 30 | shift 31 | fi 32 | if [ -n "$1" ]; then 33 | echo >&2 "$*" 34 | echo 35 | fi 36 | 37 | me=`basename $0` 38 | 39 | cat <&2 40 | Usage: $me [options] [BASE-BRANCH BRANCH2 [BRANCH3 ...]] 41 | Options: 42 | -h, --help Show this help and exit 43 | -b, --branch BRANCH Target branch for mixdown [default: $combined_branch] 44 | -c, --checkout Leave target branch checked out 45 | [default: restore previous checkout] 46 | -s, --strategy STRATEGY Set merge strategy 47 | 48 | Points the target branch at BASE-BRANCH and then mixes all the other 49 | branches in. Leaves the working copy with the target branch checked out. 50 | 51 | If no arguments are given, branches are read from the mixdown 52 | section of git config instead, using only the keys corresponding 53 | to the target branch. 54 | EOF 55 | exit "$exit_code" 56 | } 57 | 58 | parse_opts () { 59 | checkout= 60 | strategy=octopus 61 | merge_opts=( ) 62 | 63 | while [ $# != 0 ]; do 64 | case "$1" in 65 | -h|--help) 66 | usage 0 67 | ;; 68 | -b|--branch) 69 | combined_branch="$2" 70 | shift 2 71 | ;; 72 | -c|--checkout) 73 | checkout=y 74 | shift 75 | ;; 76 | -s|--strategy) 77 | if [ -z "$2" ]; then 78 | usage "--strategy needs a value" 79 | fi 80 | strategy="$2" 81 | merge_opts+=( -s "$2" ) 82 | shift 2 83 | ;; 84 | -*) 85 | usage "Unrecognised option: $1" 86 | ;; 87 | *) 88 | break 89 | ;; 90 | esac 91 | done 92 | 93 | if [ $# = 1 ]; then 94 | usage 95 | fi 96 | 97 | if [ $# = 0 ]; then 98 | branches=( $( git mix "$combined_branch" ) ) 99 | if [ "${#branches[@]}" = 0 ]; then 100 | err="No branches found for '$combined_branch' mix!" 101 | err="$err\nMixes currently defined:\n`git mix`" 102 | fatal "$err" 103 | fi 104 | base="${branches[0]}" 105 | unset branches[0] 106 | else 107 | base="$1" 108 | shift 109 | branches=( "$@" ) 110 | fi 111 | } 112 | 113 | fatal () { 114 | echo -e "$*" >&2 115 | exit 1 116 | } 117 | 118 | safe_run () { 119 | if ! "$@"; then 120 | fatal "$* failed! Aborting." 121 | fi 122 | } 123 | 124 | restore_head () { 125 | if [ -n "$orig_head" ]; then 126 | echo "Checking out original HEAD ($orig_head) ... " 127 | git checkout "$orig_head" 128 | else 129 | echo "WARNING: Couldn't determine original HEAD; current HEAD is probably different". 130 | fi 131 | } 132 | 133 | main () { 134 | parse_opts "$@" 135 | 136 | orig_head=`git rev-parse --abbrev-ref HEAD` 137 | 138 | if ! git rev-parse --quiet --verify "${base}^{commit}" >/dev/null; then 139 | fatal "$base is not a valid commit ref! Aborting." 140 | fi 141 | 142 | echo "Will start at $base and merge: ${branches[@]}" 143 | safe_run git checkout -B "$combined_branch" "$base" 144 | 145 | if [ "$strategy" = octopus ]; then 146 | safe_run git merge --no-edit "${merge_opts[@]}" "${branches[@]}" 147 | else 148 | for branch in "${branches[@]}"; do 149 | if ! git merge --no-edit "${merge_opts[@]}" "$branch"; then 150 | echo "Fix conflicts, then exit shell to continue ..." 151 | $SHELL 152 | fi 153 | done 154 | fi 155 | 156 | if [ -z "$checkout" ]; then 157 | restore_head 158 | fi 159 | } 160 | 161 | main "$@" 162 | -------------------------------------------------------------------------------- /bin/git-new-workdir: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | usage () { 4 | echo "usage:" $@ 5 | exit 127 6 | } 7 | 8 | die () { 9 | echo $@ 10 | exit 128 11 | } 12 | 13 | if test $# -lt 2 || test $# -gt 3 14 | then 15 | usage "$0 []" 16 | fi 17 | 18 | orig_git=$1 19 | new_workdir=$2 20 | branch=$3 21 | 22 | # want to make sure that what is pointed to has a .git directory ... 23 | git_dir=$(cd "$orig_git" 2>/dev/null && 24 | git rev-parse --git-dir 2>/dev/null) || 25 | die "Not a git repository: \"$orig_git\"" 26 | 27 | case "$git_dir" in 28 | .git) 29 | git_dir="$orig_git/.git" 30 | ;; 31 | .) 32 | git_dir=$orig_git 33 | ;; 34 | esac 35 | 36 | # don't link to a configured bare repository 37 | isbare=$(git --git-dir="$git_dir" config --bool --get core.bare) 38 | if test ztrue = z$isbare 39 | then 40 | die "\"$git_dir\" has core.bare set to true," \ 41 | " remove from \"$git_dir/config\" to use $0" 42 | fi 43 | 44 | # don't link to a workdir 45 | if test -L "$git_dir/config" 46 | then 47 | die "\"$orig_git\" is a working directory only, please specify" \ 48 | "a complete repository." 49 | fi 50 | 51 | # don't recreate a workdir over an existing repository 52 | if test -e "$new_workdir" 53 | then 54 | die "destination directory '$new_workdir' already exists." 55 | fi 56 | 57 | # make sure the the links use full paths 58 | git_dir=$(cd "$git_dir"; pwd) 59 | 60 | # create the workdir 61 | mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!" 62 | 63 | # create the links to the original repo. explictly exclude index, HEAD and 64 | # logs/HEAD from the list since they are purely related to the current working 65 | # directory, and should not be shared. 66 | for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn 67 | do 68 | case $x in 69 | */*) 70 | mkdir -p "$(dirname "$new_workdir/.git/$x")" 71 | ;; 72 | esac 73 | ln -s "$git_dir/$x" "$new_workdir/.git/$x" 74 | done 75 | 76 | # now setup the workdir 77 | cd "$new_workdir" 78 | # copy the HEAD from the original repository as a default branch 79 | cp "$git_dir/HEAD" .git/HEAD 80 | # checkout the branch (either the same as HEAD from the original repository, or 81 | # the one that was asked for) 82 | git checkout -f $branch 83 | -------------------------------------------------------------------------------- /bin/git-prefix: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec git rev-parse --show-prefix "$@" 4 | -------------------------------------------------------------------------------- /bin/git-remote-annex-is-ignored: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo >&2 "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | git config --type=bool remote."$1".annex-ignore | grep -q true 9 | -------------------------------------------------------------------------------- /bin/git-remote-is-up: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo >&2 "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | git ls-remote "$1" >&/dev/null 9 | -------------------------------------------------------------------------------- /bin/git-rewrite-author: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | here=`dirname $0` 4 | 5 | if [ -z "$1" ]; then 6 | set -- "`git upstream`"..HEAD 7 | fi 8 | 9 | export FILTER_BRANCH_SQUELCH_WARNING=1 10 | 11 | git filter-branch -f --env-filter ". $here/git-rewrite-author-filter" "$@" 12 | -------------------------------------------------------------------------------- /bin/git-rewrite-author-filter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # N.B. this file is source'd by git-rewrite-author, not executed! 4 | 5 | if [ -z "$GIT_NEW_AUTHOR_EMAIL" ] && 6 | ! GIT_NEW_AUTHOR_EMAIL="`git config user.email`"; then 7 | cat <&2 8 | 9 | You must set and export GIT_NEW_AUTHOR_EMAIL! 10 | For example, 11 | 12 | export GIT_NEW_AUTHOR_EMAIL="x@adamspiers.org" 13 | 14 | Or, just set user.email: 15 | 16 | git config user.email x@adamspiers.org 17 | 18 | You can also optionally export GIT_NEW_AUTHOR_NAME. 19 | 20 | Aborting. 21 | EOF 22 | exit 1 23 | fi 24 | 25 | if [ "$GIT_AUTHOR_NAME" = "${GIT_OLD_AUTHOR_NAME:-Adam Spiers}" ]; then 26 | GIT_AUTHOR_NAME="${GIT_NEW_AUTHOR_NAME:-Adam Spiers}" 27 | GIT_AUTHOR_EMAIL="$GIT_NEW_AUTHOR_EMAIL" 28 | #GIT_AUTHOR_DATE="" 29 | else 30 | : 31 | #echo "\$GIT_AUTHOR_NAME was $GIT_AUTHOR_NAME" >&2 32 | fi 33 | 34 | -------------------------------------------------------------------------------- /bin/git-rewrite-committer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | here=`dirname $0` 4 | 5 | if [ -z "$1" ]; then 6 | set -- "`git upstream`"..HEAD 7 | fi 8 | 9 | export FILTER_BRANCH_SQUELCH_WARNING=1 10 | 11 | git filter-branch -f --env-filter ". $here/git-rewrite-committer-filter" "$@" 12 | -------------------------------------------------------------------------------- /bin/git-rewrite-committer-filter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # N.B. this file is source'd by git-rewrite-committer, not executed! 4 | 5 | if [ -z "$GIT_NEW_COMMITTER_EMAIL" ] && 6 | ! GIT_NEW_COMMITTER_EMAIL="`git config user.email`"; then 7 | cat <&2 8 | 9 | You must set and export GIT_NEW_COMMITTER_EMAIL! 10 | For example, 11 | 12 | export GIT_NEW_COMMITTER_EMAIL="x@adamspiers.org" 13 | 14 | Or, just set user.email: 15 | 16 | git config user.email x@adamspiers.org 17 | 18 | You can also optionally export GIT_NEW_COMMITTER_NAME. 19 | 20 | Aborting. 21 | EOF 22 | exit 1 23 | fi 24 | if [ "$GIT_COMMITTER_NAME" = "${GIT_OLD_COMMITTER_NAME:-Adam Spiers}" ]; then 25 | GIT_COMMITTER_NAME="${GIT_NEW_COMMITTER_NAME:-Adam Spiers}" 26 | GIT_COMMITTER_EMAIL="$GIT_NEW_COMMITTER_EMAIL" 27 | echo $GIT_NEW_COMMITTER_EMAIL 28 | #export GIT_COMMITTER_DATE="" 29 | # else 30 | # echo "\$GIT_COMMITTER_NAME was $GIT_COMMITTER_NAME" >&2 31 | fi 32 | 33 | -------------------------------------------------------------------------------- /bin/git-rewrite-date: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GIT_AUTHOR_DATE="$1" 4 | export GIT_COMMITTER_DATE="$1" 5 | 6 | git commit --amend --no-edit --reset-author 7 | -------------------------------------------------------------------------------- /bin/git-rm-merged-orphan-branches: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git merged -o "$@" | awk '{ print $1 }' | xargs -r git branch -d 4 | -------------------------------------------------------------------------------- /bin/git-rnotes: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Wrapper around 'git notes' to make it easier to share notes to and 4 | # from remote repositories. 5 | 6 | safe_git () { 7 | echo "git $*" 8 | if ! git "$@"; then 9 | exit 1 10 | fi 11 | } 12 | 13 | fetch () { 14 | if ! git rev-parse "$local_notes" >/dev/null 2>&1; then 15 | # First-time fetch of remote notes 16 | safe_git fetch $remote $local_notes:$local_notes 17 | fi 18 | 19 | # This one serves like the remote tracking branch, except that we 20 | # have to handle the tracking ourselves. 21 | safe_git fetch $remote $local_notes:$remote_notes 22 | } 23 | 24 | push () { 25 | safe_git push $remote $local_notes:$local_notes 26 | fetch 27 | } 28 | 29 | # This should be only done manually by people who really know 30 | # what they're doing. 31 | # 32 | # push_f () { 33 | # safe_git push -f $remote $local_notes:$local_notes 34 | # } 35 | 36 | merge () { 37 | safe_git notes merge -v $remote_notes 38 | } 39 | 40 | pull () { 41 | fetch && merge 42 | } 43 | 44 | me=`basename $0` 45 | 46 | usage () { 47 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 48 | exit_code=1 49 | if [[ "$1" == [0-9] ]]; then 50 | exit_code="$1" 51 | shift 52 | fi 53 | if [ -n "$1" ]; then 54 | echo >&2 "$*" 55 | echo 56 | fi 57 | 58 | cat <&2 59 | Usage: $me [options] SUBCOMMAND REMOTE 60 | Options: 61 | -h, --help Show this help and exit 62 | 63 | Subcommands: 64 | 65 | fetch 66 | push 67 | merge 68 | pull 69 | 70 | EOF 71 | exit "$exit_code" 72 | } 73 | 74 | parse_args () { 75 | if [ "$1" == '-h' ] || [ "$1" == '--help' ]; then 76 | usage 0 77 | fi 78 | 79 | [ $# = 2 ] || usage 80 | 81 | subcommand="$1" remote="$2" 82 | 83 | notes_namespace=$( git notes get-ref ) 84 | if [[ $notes_namespace != refs/notes/* ]]; then 85 | echo >&2 "ERROR: Notes namespace '$notes_namespace' wasn't prefixed with 'refs/notes/'; aborting" 86 | exit 1 87 | fi 88 | notes_namespace="${notes_namespace#refs/}" 89 | notes_namespace="${notes_namespace#notes/}" 90 | 91 | local_notes="refs/notes/$notes_namespace" 92 | remote_notes="refs/notes/$remote/$notes_namespace" 93 | } 94 | 95 | main () { 96 | parse_args "$@" 97 | 98 | case "$subcommand" in 99 | fetch|push|pull|merge) 100 | "$subcommand" 101 | ;; 102 | *) 103 | usage 104 | ;; 105 | esac 106 | } 107 | 108 | main "$@" 109 | -------------------------------------------------------------------------------- /bin/git-root: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shows absolute path to parent of .git directory 4 | # (i.e. the working directory) 5 | 6 | git_dir=`git rev-parse --git-dir` || exit 1 7 | 8 | if [ "$git_dir" = '.git' ]; then 9 | pwd 10 | else 11 | dirname "$git_dir" 12 | fi 13 | -------------------------------------------------------------------------------- /bin/git-safe-push-to-checkout: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Smarter push-to-checkout hook for when receive.denyCurrentBranch is 4 | # set to updateInstead. It's near-identical to git's default 5 | # behaviour when no push-to-checkout hook is provided; however it 6 | # additionally bails if we have emacs lockfiles indicating edits in 7 | # progress for files which would be changed by the push-to-checkout. 8 | # 9 | # This means that push-to-checkout works more safely and doesn't rewrite 10 | # files which are currently being edited in emacs with unsaved changes. 11 | 12 | commit="$1" 13 | #echo "push-to-checkout $commit" 14 | 15 | die () { 16 | echo >&2 "$*" 17 | exit 1 18 | } 19 | 20 | if ! git cat-file -t HEAD >&/dev/null; then 21 | die "No history yet" 22 | fi 23 | 24 | # The below is a more-or-less exact translation to shell of the C code for 25 | # the default behaviour for git's push-to-checkout hook defined in the 26 | # push_to_deploy() function in builtin/receive-pack.c 27 | # 28 | # However it additionally bails if we have emacs lockfiles indicating 29 | # edits in progress for files which would be changed by the push-to-checkout. 30 | 31 | 32 | if ! git update-index -q --ignore-submodules --refresh; then 33 | die "Up-to-date check failed" 34 | fi 35 | 36 | if ! git diff-files --quiet --ignore-submodules --; then 37 | die "Working directory has unstaged changes" 38 | fi 39 | 40 | # This is a rough translation of: 41 | # 42 | # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX 43 | if git cat-file -t HEAD >&/dev/null; then 44 | head=HEAD 45 | else 46 | head=4b825dc642cb6eb9a060e54bf8d69288fbee4904 47 | fi 48 | 49 | if ! git diff-index --quiet --cached --ignore-submodules $head --; then 50 | die "Working directory has staged changes" 51 | fi 52 | 53 | if [[ "`pwd`" == */.git ]]; then 54 | # Not a bare repo; check for emacs lockfiles 55 | #git diff-tree HEAD $commit -- 56 | cd .. 57 | 58 | while read srcmode dstmode src dst status src dst; do 59 | if [ -L ".#$src" ]; then 60 | die "emacs lockfile present for $src; can't update" 61 | fi 62 | if [ -n "$dst" -a -L ".#$dst" ]; then 63 | die "emacs lockfile present for $dst; can't update" 64 | fi 65 | done < <(git diff-tree HEAD $commit --) 66 | fi 67 | 68 | if ! git read-tree -u -m $commit; then 69 | die "Could not update working tree to new HEAD" 70 | fi 71 | 72 | -------------------------------------------------------------------------------- /bin/git-sed-range: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | me=`basename $0` 4 | 5 | usage () { 6 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 7 | exit_code=1 8 | if [[ "$1" == [0-9] ]]; then 9 | exit_code="$1" 10 | shift 11 | fi 12 | if [ -n "$1" ]; then 13 | echo "$*" >&2 14 | echo 15 | fi 16 | 17 | cat <&2 18 | Usage: 19 | # remove 'Bug 123456 -' prefix from every commit log in range 20 | $me '1 { s/^Bug 123456 -// }' REVISION-RANGE 21 | 22 | REVISION-RANGE defaults to \$upstream..HEAD 23 | EOF 24 | exit "$exit_code" 25 | } 26 | 27 | if [ "$1" == '-h' ] || [ "$1" == '--help' ]; then 28 | usage 0 29 | fi 30 | 31 | [ $# == 0 ] && usage 32 | [ $# -gt 2 ] && usage 33 | 34 | sed="$1" 35 | revrange="${2:-`ggup`..HEAD}" 36 | 37 | git filter-branch -f --msg-filter "sed '$sed'" "$revrange" 38 | -------------------------------------------------------------------------------- /bin/git-set-upstream: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set ref of upstream branch for a local branch 4 | 5 | me=`basename $0` 6 | 7 | usage () { 8 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 9 | exit_code=1 10 | if [[ "$1" == [0-9] ]]; then 11 | exit_code="$1" 12 | shift 13 | fi 14 | if [ -n "$1" ]; then 15 | echo "$*" >&2 16 | echo 17 | fi 18 | 19 | cat <&2 20 | Usage: $me [] 21 | Sets a branch (HEAD by default) to track a remote branch. 22 | EOF 23 | exit "$exit_code" 24 | } 25 | 26 | set_upstream () { 27 | if [ $# -eq 1 ]; then 28 | set -- `git head` "$1" 29 | fi 30 | 31 | local_branch="$1" 32 | remote_ref="$2" 33 | 34 | if git branch -h 2>&1 | grep -q -- --set-upstream-to; then 35 | git branch --set-upstream-to "$remote_ref" "$local_branch" 36 | elif git branch -h 2>&1 | grep -q -- --set-upstream; then 37 | git branch --set-upstream "$local_branch" "$remote_ref" 38 | else 39 | git rev-parse "$remote_ref" >/dev/null || exit 1 40 | remote="${remote_ref%/*}" 41 | remote_branch="${remote_ref#*/}" 42 | [ "$remote" = "$remote_ref" ] && remote=. 43 | git config branch.$local_branch.remote "$remote" 44 | git config branch.$local_branch.merge "$remote_branch" 45 | if [ "$remote" = '.' ]; then 46 | echo "Branch $local_branch set up to track local branch $remote_branch." 47 | else 48 | echo "Branch $local_branch set up to track remote branch $remote_branch from $remote." 49 | fi 50 | fi 51 | } 52 | 53 | root="`git root`" 54 | if ! [ -d "$root" ]; then 55 | exit 1 56 | fi 57 | 58 | if [ "$1" == '-h' ] || [ "$1" == '--help' ] || [ $# -eq 0 ] || [ $# -gt 2 ]; then 59 | usage 0 60 | fi 61 | 62 | set_upstream "$@" 63 | -------------------------------------------------------------------------------- /bin/git-should-ping-remote-annex: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo >&2 "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | git config --type=bool remote."$1".annex-ping | grep -q true 9 | -------------------------------------------------------------------------------- /bin/git-submodule-foreach-shorten: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby -p 2 | 3 | $_.gsub! /Entering '(.+)'(\e\[\d+(;\d+)*m)?\n/, "\\1:\\2\t" 4 | -------------------------------------------------------------------------------- /bin/git-svn-fast-show-ignore: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | me=`basename $0` 4 | 5 | usage () { 6 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 7 | exit_code=1 8 | if [[ "$1" == [0-9] ]]; then 9 | exit_code="$1" 10 | shift 11 | fi 12 | if [ -n "$1" ]; then 13 | echo "$*" >&2 14 | echo 15 | fi 16 | 17 | cat <&2 18 | Usage: $me SVN-WORKING-DIR 19 | EOF 20 | exit "$exit_code" 21 | } 22 | 23 | if [ "$1" == '-h' ] || [ "$1" == '--help' ]; then 24 | usage 0 25 | fi 26 | 27 | if [ $# -eq 0 ]; then 28 | usage 29 | fi 30 | 31 | svn_wd="$1" 32 | 33 | # if ! [ -d .git/info ]; then 34 | # echo "Must be run from top of git working tree." >&2 35 | # exit 1 36 | # fi 37 | 38 | ( cd "$svn_wd" && svn propget svn:ignore --xml -R . ) | svnignore2gitexclude 39 | -------------------------------------------------------------------------------- /bin/git-svn-revert-multi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # git-svn-revert-multi - revert a sequence of svn commits 4 | # Copyright (C) 2011 Adam Spiers 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | use strict; 20 | use warnings; 21 | 22 | use Getopt::Long; 23 | 24 | Getopt::Long::Configure('bundling'); 25 | 26 | sub usage { 27 | warn @_, "\n" if @_; 28 | 29 | (my $ME = $0) =~ s,.*/,,; 30 | 31 | die < 'Insert your description here.' 52 | ); 53 | GetOptions( 54 | \%opts, 55 | 'help|h', 56 | 'message|m=s', 57 | 'no-commit|n', 58 | ) or usage(); 59 | usage() if $opts{help}; 60 | usage("Do not redirect STDIN otherwise an interactive shell cannot be spawned.\n") 61 | unless -t 0; 62 | 63 | my $format = "%-6s %-40s %s"; 64 | 65 | my $log_header = <) { 76 | my @F = split /\s+/, $_; 77 | my $commit = $F[0]; 78 | chomp (my $sha = `git rev-parse $commit`); 79 | if ($? > 0) { 80 | die "Failed to parse commit id \`$commit' on line $.; aborting!\n"; 81 | } 82 | my $show = `git show $sha`; 83 | $show =~ m!git-svn-id: .+@(\d+)! or die "no svn rev for:\n$show"; 84 | my $svn_rev = $1; 85 | my @lines = split /\n/, $show; 86 | (my $first_log_line = $lines[4]) =~ s/^\s+//; 87 | my $revert_line = sprintf $format, 'r' . $svn_rev, $sha, $first_log_line; 88 | $longest = length $revert_line if length $revert_line > $longest; 89 | print "$revert_line\n"; 90 | $log .= "$revert_line\n"; 91 | my @cmd = qw(git revert --no-edit); 92 | push @cmd, '--no-commit' if $opts{'no-commit'}; 93 | push @cmd, $sha; 94 | system @cmd; 95 | if ($? > 0) { 96 | warn <> 8; 106 | die "Shell exited with exit code $exit_code; abandoning revert.\n" 107 | if $exit_code > 0; 108 | } 109 | } 110 | 111 | $log_header .= sprintf($format, qw(svn git summary)) . "\n"; 112 | $log_header .= "-" x $longest . "\n"; 113 | 114 | $log = $log_header . $log; 115 | 116 | if ($opts{'no-commit'}) { 117 | system qw(git commit --edit -m), $log; 118 | } 119 | else { 120 | print "\nSuggested log message for squashed commit:\n\n" . $log; 121 | } 122 | -------------------------------------------------------------------------------- /bin/git-sync-upstream: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | error () { 4 | echo "$*" >&2 5 | exit 1 6 | } 7 | 8 | is_interactive () { 9 | [ -t 0 ] 10 | } 11 | 12 | repo_is_clean () { 13 | # "clean" == nothing to do except maybe commit untracked files 14 | # which would not be harmed by any git operations except git clean -f, 15 | # which we won't be doing here! 16 | [ -z "`git status --porcelain | egrep -v '^\?\?'`" ] 17 | } 18 | 19 | commit_local_changes () { 20 | status=`git -c color.ui=always status -s "$@"` 21 | if [ -z "$status" ]; then 22 | return 0 # nothing to do 23 | fi 24 | 25 | echo "$status" 26 | echo 27 | 28 | if ! is_interactive; then 29 | if repo_is_clean; then 30 | : # we can still safely proceed with push/pull 31 | return 0 32 | else 33 | echo "Repo has uncommitted changes." 34 | return 1 35 | fi 36 | fi 37 | 38 | if [ -n "$DISPLAY" ]; then 39 | echo "Launching git-gui; commit local changes before proceeding with sync ..." 40 | echo "Press Control-C to cancel" 41 | git gui 42 | else 43 | echo "\$DISPLAY not set so can't launch git-gui. Launching $SHELL;" 44 | echo "please commit changes via CLI then exit the shell." 45 | $SHELL 46 | fi 47 | } 48 | 49 | # Returns true if detected 50 | detect_upstream () { 51 | head=`git head` 52 | if ! upstream=`git upstream`; then 53 | echo "Upstream not set for $head" 54 | 55 | is_interactive || return 1 56 | 57 | cat </dev/null`; then 71 | echo 72 | echo "Upstream still not set for $head." >&2 73 | return 1 74 | fi 75 | 76 | return 0 77 | } 78 | 79 | parse_upstream () { 80 | local upstream="$1" 81 | 82 | remote="${upstream%/*}" 83 | remote_branch="${upstream#*/}" 84 | } 85 | 86 | compare_with_upstream () { 87 | local upstream="$1" 88 | 89 | # http://stackoverflow.com/questions/2969214/git-programmatically-know-by-how-much-the-branch-is-ahead-behind-a-remote-branc 90 | 91 | set -- `git rev-list --count --left-right $upstream...HEAD` 92 | behind="$1" 93 | ahead="$2" 94 | } 95 | 96 | show_upstream_info () { 97 | git remote -v show -n "$1" 98 | } 99 | 100 | confirm_action () { 101 | action="$1" 102 | 103 | while true; do 104 | echo "Enter 'y' to $action," 105 | echo -n " 's' to skip, or Control-C to abort > " 106 | read answer 107 | case "$answer" in 108 | y) return 0 ;; 109 | s) return 1 ;; 110 | esac 111 | done 112 | } 113 | 114 | pull_from_upstream () { 115 | local upstream="$1" 116 | 117 | echo 118 | echo -e "\e[1;31m$head is behind $upstream by $behind commits:\e[0m" 119 | echo 120 | git log HEAD.."$upstream" 121 | 122 | if is_interactive; then 123 | pull_from_upstream_interactive "$upstream" 124 | else 125 | safe_merge_with_upstream_batch "$upstream" 126 | fi 127 | } 128 | 129 | pull_from_upstream_interactive () { 130 | local upstream="$1" 131 | 132 | args="$remote $remote_branch" 133 | echo 134 | confirm_action "pull missing commits from $upstream" || return 135 | 136 | echo "Running git pull $args ..." 137 | echo 138 | if ! git pull $args; then 139 | error "git pull $args failed; aborting. Please manually fix and re-run." 140 | fi 141 | } 142 | 143 | safe_merge_with_upstream_batch () { 144 | local upstream="$1" 145 | 146 | if ! repo_is_clean; then 147 | echo "Repo has uncommitted changes; cannot safely attempt automatic merge." 148 | return 1 149 | fi 150 | 151 | git merge --no-commit --no-ff "$upstream" 152 | status=$? 153 | if git status --porcelain | egrep '^(U|.U)'; then 154 | [ "$status" = 0 ] && error "git merge --no-commit succeeded with unmerged files?!" 155 | echo 156 | echo "In non-interactive mode; aborted merge." 157 | git reset --hard 158 | return 1 159 | else 160 | [ "$status" != 0 ] && error "git merge --no-commit failed with no unmerged files?!" 161 | git reset --hard 162 | git merge "$upstream" || error "batch git merge failed" 163 | fi 164 | } 165 | 166 | rebase_on_upstream () { 167 | local upstream="$1" 168 | 169 | echo 170 | echo -e "\e[1;31m$head is behind $upstream by $behind commits:\e[0m" 171 | echo 172 | git log HEAD.."$upstream" 173 | 174 | if is_interactive; then 175 | rebase_on_upstream_interactive "$upstream" 176 | else 177 | safe_merge_with_upstream_batch "$upstream" 178 | fi 179 | } 180 | 181 | rebase_on_upstream_interactive () { 182 | local upstream="$1" 183 | 184 | echo 185 | confirm_action "rebase on missing commits from $upstream" || return 186 | 187 | echo "Running git rebase ..." 188 | echo 189 | if ! git rebase; then 190 | error "git rebase failed; aborting. Please manually fix and re-run." 191 | fi 192 | } 193 | 194 | safe_rebase_on_upstream_batch () { 195 | local upstream="$1" 196 | 197 | if ! repo_is_clean; then 198 | echo "Repo has uncommitted changes; cannot safely attempt automatic rebase." 199 | return 1 200 | fi 201 | 202 | if ! git rebase "$upstream"; then 203 | echo "Automatic rebase failed; aborting rebase." 204 | git rebase --abort 205 | return 1 206 | fi 207 | } 208 | 209 | push_to_upstreams_remote () { 210 | HOST_NAME_FILE=$HOME/.localhost-nickname 211 | if [ -f "$HOST_NAME_FILE" ] && src="$(<$HOST_NAME_FILE)" && [ -n "$src" ]; then 212 | args="$remote $head:refs/remotes/$src/$head" 213 | echo 214 | # Note that we need -f because the local branch could have been 215 | # rewritten since the last time this was run. 216 | if ! git push -f $args; then 217 | error "git push $args failed; aborting. Please manually fix and re-run." 218 | fi 219 | else 220 | echo >&2 221 | echo "Could not determine remote name for local repo in upstream repo; skipping update of remote tracking branch." >&2 222 | fi 223 | 224 | if is_interactive; then 225 | echo 226 | confirm_action "push new commits to $upstream" || return 227 | fi 228 | } 229 | 230 | push_to_upstreams_local () { 231 | args="$remote $head" 232 | echo 233 | out=$( git push $args 2>&1 ) 234 | success=$? 235 | echo "$out" 236 | if [ $success != 0 ]; then 237 | case "$out" in 238 | *'remote rejected'*) 239 | echo "Ignoring error caused by pushing to non-bare repository." 240 | ;; 241 | *) 242 | error "git push $args failed; aborting. Please manually fix and re-run." 243 | ;; 244 | esac 245 | fi 246 | } 247 | 248 | push_to_upstream () { 249 | local upstream="$1" 250 | 251 | echo 252 | echo -e "\e[1;33m$head is ahead of $upstream by $ahead commits:\e[0m" 253 | echo 254 | git log "$upstream"..HEAD 255 | 256 | # Since the upstream could be non-bare, a normal push could fail. 257 | # So we first push to the upstream's remote tracking branch 258 | # representing our local repository, so that the upstream is 259 | # guaranteed to contain a copy of our local version, even if not 260 | # in its local branch. 261 | # 262 | # See the question I posed on stackoverflow which noone has 263 | # answered yet: 264 | # http://stackoverflow.com/questions/13015101/how-to-programmatically-predict-when-git-push-will-result-in-branch-is-currentl 265 | push_to_upstreams_remote 266 | 267 | push_to_upstreams_local 268 | } 269 | 270 | final_check () { 271 | echo 272 | if git-wip; then 273 | echo -e "\e[1;31mUnresolved issues in $root:\e[0m" 274 | echo 275 | ggs 276 | else 277 | echo -e "\e[1;32mNo unresolved issues in $root :-)\e[0m" 278 | fi 279 | } 280 | 281 | sync_with_upstream () { 282 | local upstream="$1" 283 | 284 | parse_upstream "$upstream" 285 | 286 | echo 287 | echo "git fetch $remote" 288 | echo 289 | if ! git fetch "$remote"; then 290 | error "Failed to fetch $remote; aborting." 291 | fi 292 | compare_with_upstream "$upstream" 293 | is_interactive && show_upstream_info "$remote" 294 | 295 | if [ "$behind" = 0 ] && [ "$ahead" = 0 ]; then 296 | echo "Already in sync with upstream." 297 | return 0 298 | fi 299 | 300 | if [ "$behind" != 0 ]; then 301 | #rebase_on_upstream "$upstream" 302 | pull_from_upstream "$upstream" 303 | fi 304 | 305 | if [ "$ahead" != 0 ]; then 306 | push_to_upstream "$upstream" 307 | fi 308 | } 309 | 310 | me=`basename $0` 311 | 312 | usage () { 313 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 314 | exit_code=1 315 | if [[ "$1" == [0-9] ]]; then 316 | exit_code="$1" 317 | shift 318 | fi 319 | if [ -n "$1" ]; then 320 | echo "$*" >&2 321 | echo 322 | fi 323 | 324 | cat <&2 325 | Usage: $me UPSTREAM 326 | EOF 327 | exit "$exit_code" 328 | } 329 | 330 | main () { 331 | root="`git root`" 332 | root="${root/$HOME/~}" 333 | 334 | if [ "$1" == '-h' ] || [ "$1" == '--help' ]; then 335 | usage 0 336 | fi 337 | 338 | [ $# -gt 1 ] && usage 339 | 340 | if [ -n "$1" ]; then 341 | upstream="$1" 342 | else 343 | detect_upstream || exit 1 344 | fi 345 | 346 | commit_local_changes 347 | sync_with_upstream "$upstream" 348 | final_check 349 | } 350 | 351 | main "$@" 352 | -------------------------------------------------------------------------------- /bin/git-tag-patchset: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Quick way of tagging patch sets as they get uploaded to Gerrit, to 4 | # keep track of a review's history. Ideally git-review would do this; 5 | # submitted as https://storyboard.openstack.org/#!/story/2005068 6 | 7 | me=`basename $0` 8 | 9 | usage () { 10 | if [ -n "$1" ]; then 11 | echo "$*" >&2 12 | echo 13 | fi 14 | 15 | cat <&2 16 | Usage: $me [-f|--force] [PATCH-SET-NUM] [COMMIT-ISH] 17 | 18 | Make a tag for a new or given patch set for the current branch which 19 | points to COMMIT-ISH. 20 | 21 | The tag will be named in this format: 22 | 23 | \${current_branch}/PS\${num} 24 | 25 | Options: 26 | 27 | -f, --force Force-update an existing tag to point to COMMIT-ISH 28 | EOF 29 | exit 1 30 | } 31 | 32 | find_latest_patch_set () { 33 | set -o pipefail 34 | git show-ref | 35 | awk '$2 ~ /^refs\/tags\/'"${tag_prefix////\\/}"'[0-9]+$/ {print $2}' | 36 | sed "s,^refs/tags/${tag_prefix},," | 37 | sort -n | 38 | tail -n 1 39 | } 40 | 41 | main () { 42 | case "$1" in 43 | -f|--force) 44 | force=-f 45 | shift 46 | ;; 47 | esac 48 | 49 | if [ $# -gt 2 ]; then 50 | usage 51 | fi 52 | 53 | if ! branch="`git head`"; then 54 | echo >&2 "Couldn't figure out current branch! Aborting." 55 | exit 1 56 | fi 57 | target="${2:-$branch}" 58 | 59 | tag_prefix="$branch/PS" 60 | 61 | if [ -n "$1" ]; then 62 | tag="${tag_prefix}$1" 63 | else 64 | latest_patch_set=$( find_latest_patch_set ) 65 | if [[ $? != 0 ]]; then 66 | echo >&2 "Couldn't figure out latest existing patch set for $branch; aborting." 67 | exit 1 68 | fi 69 | if [[ -z "$latest_patch_set" ]]; then 70 | echo >&2 "No previous patchset; assuming this is the first." 71 | new_patch_set=1 72 | else 73 | new_patch_set="$(( latest_patch_set + 1 ))" 74 | echo "Latest patch set is $latest_patch_set; new patch set will be $new_patch_set" 75 | fi 76 | tag="${tag_prefix}$new_patch_set" 77 | fi 78 | 79 | if git rev-parse --quiet --verify "$tag" >/dev/null; then 80 | current=$( git rev-parse "$tag") 81 | if [[ "$current" == $(git rev-parse "$target") ]]; then 82 | echo "$tag already points to $target" 83 | else 84 | git tag $force "$tag" "$target" 85 | fi 86 | else 87 | if git tag "$tag" "$target"; then 88 | echo "Tagged $target as $tag" 89 | fi 90 | fi 91 | } 92 | 93 | main "$@" 94 | -------------------------------------------------------------------------------- /bin/git-upstream: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Figure out ref of local branch's upstream remote/branch. 4 | 5 | me=`basename $0` 6 | 7 | usage () { 8 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 9 | exit_code=1 10 | if [[ "$1" == [0-9] ]]; then 11 | exit_code="$1" 12 | shift 13 | fi 14 | if [ -n "$1" ]; then 15 | echo "$*" >&2 16 | echo 17 | fi 18 | 19 | cat <&2 20 | Usage: $me [] 21 | Outputs the upstream remote/branch for a given local branch, 22 | if it can be calculated. 23 | 24 | The local branch defaults to HEAD. 25 | EOF 26 | exit "$exit_code" 27 | } 28 | 29 | get_svn_upstream () { 30 | if ! [ -d "$root/.git/svn" ]; then 31 | return 1 32 | fi 33 | 34 | if [ `ls "$root/.git/svn" | wc -l` == 1 ]; then 35 | if upstream=`cd $root/.git/svn && ls`; then 36 | return 0 37 | else 38 | echo "WARNING: cd $root/.git/svn && ls failed" >&2 39 | fi 40 | fi 41 | 42 | if ! svn_info=$( git svn info 2>&1 ); then 43 | return 1 44 | fi 45 | 46 | if echo "$svn_info" | grep -q 'Unable to determine upstream SVN info'; then 47 | return 1 48 | fi 49 | 50 | svn_branch=$( 51 | echo "$svn_info" | 52 | perl -lne ' 53 | /^URL: (.+)/ and $u = $1; 54 | if (/^Repository Root: (.+)/) { 55 | $r = $1; 56 | $u =~ s!\Q$r/!!; 57 | 58 | # This bit required since git svn info changed to including 59 | # the subpath of the cwd in the URL (somewhere in between 1.6.0 60 | # and 1.7.1): 61 | ($subdir = `git prefix`) =~ s!/\n$!!; 62 | $u =~ s!/$subdir$!!; 63 | 64 | print $u 65 | }' 66 | ) 67 | # Need to apply refspec transformation; 68 | #svn_branches_map=$( git config svn-remote.svn.branches ) 69 | # for now we just assume standard layout and cheat. 70 | upstream="remotes/${svn_branch#branches/}" 71 | return 0 72 | } 73 | 74 | get_upstream () { 75 | # If the upstream is explicitly specified in config, we honor that. 76 | local_branch="$1" 77 | upstream_remote_conf="branch.$local_branch.remote" 78 | upstream_remote=$(git config "$upstream_remote_conf") 79 | if [ -n "$upstream_remote" ]; then 80 | upstream_branch_conf="branch.$local_branch.merge" 81 | upstream_branch=$(git config "$upstream_branch_conf") 82 | if [ -n "$upstream_branch" ]; then 83 | upstream_branch="${upstream_branch#refs/heads/}" 84 | if [ "$upstream_remote" = '.' ]; then 85 | upstream="$upstream_branch" 86 | else 87 | upstream="$upstream_remote/$upstream_branch" 88 | fi 89 | if git rev-parse "$upstream" >/dev/null 2>&1; then 90 | return 91 | fi 92 | else 93 | handle_missing_upstream_branch 94 | fi 95 | fi 96 | 97 | get_svn_upstream 98 | } 99 | 100 | handle_missing_upstream_branch () { 101 | # We know which upstream remote to use, but not which 102 | # branch in that remote, so try using the local branch 103 | # name. 104 | upstream="$upstream_remote/$local_branch" 105 | if git rev-parse "$upstream" >/dev/null 2>&1; then 106 | if [ -t 0 ]; then 107 | # Running interactively 108 | cat < " 115 | read answer 116 | if [ "$answer" = 'yes' ]; then 117 | if git config "$upstream_branch_conf" "$local_branch"; then 118 | echo 119 | echo "Set $upstream_branch_conf=$local_branch" 120 | exit 0 121 | fi 122 | else 123 | echo 124 | echo "$upstream_branch_conf still not set; aborting." 125 | exit 1 126 | fi 127 | else 128 | cat <&2 129 | $upstream_branch_conf was not set but should probably be set to 130 | $local_branch to match the local branch name. Please set manually via 131 | git set-upstream. Aborting. 132 | EOF 133 | exit 1 134 | fi 135 | else 136 | cat <&2 137 | Couldn't guess which branch of upstream '$upstream_remote' to use :-( 138 | Please set manually via git set-upstream. Aborting. 139 | EOF 140 | exit 1 141 | fi 142 | } 143 | 144 | root="`git root`" 145 | if ! [ -d "$root" ]; then 146 | exit 1 147 | fi 148 | 149 | if [ "$1" == '-h' ] || [ "$1" == '--help' ] || [ $# -gt 1 ]; then 150 | usage 0 151 | fi 152 | 153 | local_branch="$1" 154 | if [ $# -eq 0 ]; then 155 | local_branch="`git head`" 156 | fi 157 | 158 | upstream= 159 | get_upstream "$local_branch" 160 | 161 | if [ -n "$upstream" ] && git rev-parse "$upstream" >/dev/null 2>&1; then 162 | echo "$upstream" 163 | exit 0 164 | fi 165 | 166 | if [ -n "$upstream" ]; then 167 | echo "Best guess '$upstream' was invalid." 168 | fi 169 | 170 | cat <&2 171 | Maybe you should run: 172 | 173 | git set-upstream $local_branch \$remote/\$branch 174 | 175 | Couldn't figure out upstream ref; aborting. 176 | EOF 177 | 178 | exit 1 179 | -------------------------------------------------------------------------------- /bin/git-url-rewrite: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PS4="+\D{%Y/%m/%d %T} \${BASH_SOURCE/\$HOME/\~}@\${LINENO}(\${FUNCNAME[0]}): " 3 | 4 | me=`basename $0` 5 | 6 | usage () { 7 | # Call as: usage [EXITCODE] [USAGE MESSAGE] 8 | exit_code=1 9 | if [[ "$1" == [0-9] ]]; then 10 | exit_code="$1" 11 | shift 12 | fi 13 | if [ -n "$1" ]; then 14 | echo "$*" >&2 15 | echo 16 | fi 17 | 18 | cat <&2 19 | Usage: 20 | $me [] Lists all rewrite rules. 21 | $me [] ABBREV Lists rewrite rule(s) for ABBREV 22 | $me [] [-p] ABBREV FULL 23 | Sets rewrite rule for ABBREV to FULL 24 | -p uses pushInsteadOf, not insteadOf. 25 | If FULL is empty string, clears the rule. 26 | 27 | is as for git-config, i.e. --system, --global, --local, 28 | --file . Defaults to --global. 29 | EOF 30 | exit "$exit_code" 31 | } 32 | 33 | get_rewrites () { 34 | git config $config --list | \ 35 | perl -lne ' 36 | if (m!^url\.(.+)\.(push)?insteadOf=(.+)$!i) { 37 | $abbrev = sprintf "%-15s", $3; 38 | $full = $1; 39 | $abbrev =~ s/ {0,5}$/ PUSH/ if $2; 40 | print("$abbrev -> $full"); 41 | }' | \ 42 | sort 43 | } 44 | 45 | get_rewrites_by_abbrev () { 46 | abbrev="$1" 47 | 48 | git config $config --list | \ 49 | perl -lne ' 50 | if (m!^url\.(.+)\.(push)?insteadOf='"${abbrev//!/\!}"'$!i) { 51 | print(($2 ? "PUSH " : "") . $1); 52 | }' 53 | } 54 | 55 | get_rewrite_by_URL () { 56 | URL="$1" 57 | 58 | git config $config url.$URL.$insteadOf 59 | } 60 | 61 | get_and_clear_rewrite () { 62 | abbrev="$1" 63 | full="$2" 64 | 65 | git config $config --list | \ 66 | perl -lne ' 67 | if (m!^(url\.(.+)\.'"$insteadOf"')='"${abbrev//!/\!}"'$!i) { 68 | print $2; 69 | next if $2 eq "'"$full"'"; 70 | system qw(git config '"$config"' --unset), $1; 71 | }' 72 | } 73 | 74 | set_rewrite () { 75 | [ -z "$1" ] && usage "Abbreviation cannot be empty." 76 | abbrev="$1" 77 | full="$2" 78 | 79 | if [ -n "$full" ]; then 80 | existing_abbrev=$( get_rewrite_by_URL "$full" ) 81 | if [ "$abbrev" = "$existing_abbrev" ]; then 82 | : Nothing to change 83 | return 0 84 | fi 85 | if [ -n "$existing_abbrev" ]; then 86 | echo "$existing_abbrev already points to \"$full\"; git won't"\ 87 | "allow pointing something else to it" >&2 88 | exit 1 89 | fi 90 | fi 91 | 92 | existing_full=$( get_and_clear_rewrite "$abbrev" "$full" ) 93 | 94 | if [ -z "$full" ]; then 95 | git config $config --unset "url.$full.$insteadOf" 96 | echo "Cleared ${push:+push }URL rewrite of $abbrev (was $existing_full)" 97 | return 98 | fi 99 | 100 | git config $config "url.$full.$insteadOf" "$abbrev" 101 | 102 | if [ -z "$existing_full" ]; then 103 | echo "Set ${push:+push }URL rewrite of $abbrev -> $full" 104 | elif [ "$full" != "$existing_full" ]; then 105 | echo "Changed ${push:+push }URL rewrite of $abbrev -> $full (was $existing_full)" 106 | fi 107 | } 108 | 109 | parse_opts () { 110 | config= 111 | 112 | while [ $# != 0 ]; do 113 | case "$1" in 114 | -h|--help) 115 | usage 0 116 | ;; 117 | --system|--global|--local) 118 | config="$1" 119 | shift 120 | break 121 | ;; 122 | -f|--file) 123 | config="--file $2" 124 | shift 2 125 | break 126 | ;; 127 | *) 128 | break 129 | ;; 130 | esac 131 | done 132 | 133 | if [ -z "$config" ]; then 134 | #if ! git rev-parse --git-dir 2>/dev/null; then 135 | config=--global 136 | #fi 137 | fi 138 | 139 | insteadOf='insteadOf' 140 | if [ "$1" == '-p' ]; then 141 | push=y 142 | insteadOf='pushInsteadOf' 143 | shift 144 | if [ $# != 2 ]; then 145 | usage "-p only valid when setting" 146 | fi 147 | fi 148 | 149 | ARGV=( "$@" ) 150 | } 151 | 152 | main () { 153 | parse_opts "$@" 154 | 155 | set -- "${ARGV[@]}" 156 | 157 | if [ $# = 0 ]; then 158 | get_rewrites 159 | elif [ $# = 1 ]; then 160 | get_rewrites_by_abbrev "$1" 161 | elif [ $# = 2 ]; then 162 | set_rewrite "$@" 163 | else 164 | usage 1 165 | fi 166 | } 167 | 168 | main "$@" 169 | -------------------------------------------------------------------------------- /bin/git-wip: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Does this repository have any work in progress, 4 | # i.e. uncommitted changes, untracked files, or 5 | # commits out of sync with upstream? 6 | 7 | wip= 8 | 9 | sh=`git compare-upstream --sh` 10 | eval "$sh" 11 | 12 | (( before + after )) || [ -n "`git status -s`" ] 13 | -------------------------------------------------------------------------------- /bin/qg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec qgit --all "$@" & 4 | -------------------------------------------------------------------------------- /bin/svnignore2gitexclude: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | # Convert output of svn propget [--xml] -R svn:ignore 4 | # into .git/info/exclude format. 5 | # 6 | # N.B. when fed plain text, assumes that neither files nor ignore 7 | # values contain the string ' - '. The only way to handle this 8 | # accurately is via --xml. 9 | 10 | use strict; 11 | use warnings; 12 | 13 | sub parse_text { 14 | my ($in) = @_; 15 | my @chunks = sort split /(?:\A|\n\n)(?=^\S+.* - )/m, $in; 16 | 17 | foreach my $i (0 .. $#chunks) { 18 | my $chunk = $chunks[$i]; 19 | my ($dir, $ignores) = $chunk =~ /^(.+?) - (.+)/s; 20 | if (! defined $ignores) { 21 | print "weird chunk: [$chunk]\n"; 22 | next; 23 | } 24 | 25 | output_chunk($dir, $ignores); 26 | 27 | print "\n" unless $i == $#chunks; 28 | } 29 | } 30 | 31 | sub output_chunk { 32 | my ($dir, $ignores) = @_; 33 | $dir = $dir =~ /^\.?$/ ? '' : "/$dir"; 34 | 35 | print "# $dir/\n"; 36 | foreach my $ignore (split /\n/, $ignores) { 37 | print "$dir/$ignore\n"; 38 | } 39 | } 40 | 41 | sub parse_XML { 42 | eval { 43 | require XML::Simple; 44 | import XML::Simple; 45 | }; 46 | if ($@) { 47 | die "Need XML::Simple installed to be able to parse XML form\n"; 48 | } 49 | 50 | my ($in) = @_; 51 | 52 | my $xml = XMLin($in); 53 | die "XML Parse didn't look as expected" 54 | unless $xml->{target} and ref($xml->{target}) eq 'ARRAY'; 55 | my @chunks = sort { $a->{path} cmp $b->{path} } @{ $xml->{target} }; 56 | for my $i (0 ..$#chunks) { 57 | my $chunk = $chunks[$i]; 58 | my $path = $chunk->{path}; 59 | chomp (my $ignore = $chunk->{property}{content}); 60 | if (! defined $ignore) { 61 | warn "Warning: 'svn:ignore' empty or not under version control for $path\n"; 62 | next; 63 | } 64 | output_chunk($path, $ignore); 65 | print "\n" unless $i == $#chunks; 66 | } 67 | } 68 | 69 | undef $/; 70 | my $in = join '', <>; 71 | $in =~ /^<\?xml/ ? parse_XML($in) : parse_text($in); 72 | -------------------------------------------------------------------------------- /bin/tigs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tig status "$@" 4 | --------------------------------------------------------------------------------