├── README ├── README.textile ├── Rakefile ├── git-PS1 ├── git-addremove ├── git-all-commits ├── git-apply-feature ├── git-build ├── git-changelog ├── git-children-of ├── git-current ├── git-diff-directory ├── git-empty-branch ├── git-erase-reflog ├── git-external-ediff ├── git-find ├── git-find-blob ├── git-find-children ├── git-find-fetch ├── git-flush ├── git-full-reset ├── git-hunt-and-seek ├── git-incoming ├── git-index ├── git-iterate ├── git-last ├── git-maxpack ├── git-opendiff ├── git-outgoing ├── git-pack-config ├── git-patch ├── git-publish ├── git-publish-branch ├── git-publish-repo ├── git-push-all ├── git-push-branch ├── git-rank-contributors ├── git-record ├── git-remove-empty-commits ├── git-retrack ├── git-rm-conflicts ├── git-sh ├── git-show-merges ├── git-signed-tag ├── git-snapshot ├── git-switch ├── git-sync ├── git-trash ├── git-unpack ├── git-unpack-config ├── git-unwip ├── git-update ├── git-wip ├── git-wt-add └── git-wtf /README: -------------------------------------------------------------------------------- 1 | git-incoming # shows incoming commits from the tracked (or specified) branch 2 | git-incoming-short # same but shorter 3 | git-last # git last 3 (show the last 3 commits) 4 | git-outgoing # show outgoing commits to the tracked (or specified) branch 5 | git-outgoing-short # same but shorter 6 | git-patch # format a patch and put it into ~/Documents/Patches//.patch 7 | git-push-all # push to all remotes 8 | git-switch # like checkout, but with auto-stash 9 | git-pack-config # put .git/config into the repository for portable configs 10 | git-unpack-config # put .git/config back into place 11 | git-wip # store your existing changes as a "work in progress" commit to facilitate rebasing 12 | git-unwip # run this after rebasing to turn the wip commit back into unstaged changes 13 | git-wtf # show the current status of your git repository, including feature branches 14 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Git Utils 2 | 3 | !!! No Maintenance Intended http://unmaintained.tech 4 | 5 | h3. git-PS1 6 | 7 | Shows the current branch in the bash prompt 8 | 9 | h3. git-incoming 10 | 11 | Shows incoming commits from the tracked (or specified) branch 12 | 13 | h3. git-incoming-short 14 | 15 | Same but shorter 16 | 17 | h3. git-iterate 18 | 19 | Runs a given script over every revision in the current repository 20 | 21 | h3. git-last 22 | 23 | git last 3 (show the last 3 commits) 24 | 25 | h3. git-outgoing 26 | 27 | Show outgoing commits to the tracked (or specified) branch 28 | 29 | h3. git-outgoing-short 30 | 31 | Same but shorter 32 | 33 | h3. git-patch 34 | 35 | Format a patch and put it into ~/Documents/Patches//.patch 36 | 37 | h3. git-push-all 38 | 39 | Push to all remotes 40 | 41 | h3. git-switch 42 | 43 | Like checkout, but with auto-stash 44 | 45 | h3. git-pack-config 46 | 47 | Put .git/config into the repository for portable configs 48 | 49 | h3. git-unpack-config 50 | 51 | Put .git/config back into place 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default do 2 | git = `which git`.strip 3 | raise "Could not find git - is it in the path? Try 'which git'." unless File.exists?(git) 4 | 5 | git_path = File.dirname(git) 6 | raise "Permission denied - #{git_path} is not writable. Try 'sudo rake' or set the permissions accordingly." unless File.writable?(git_path) 7 | 8 | Dir['git-*'].each do |file| 9 | cp file, git_path 10 | end 11 | end -------------------------------------------------------------------------------- /git-PS1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Taken from http://kerneltrap.org/mailarchive/git/2006/11/16/230518 4 | # 5 | # In your .profile, add: 6 | # 7 | # export PS1='$(git-PS1 "[\u@\h \W]\$ ")' 8 | # 9 | BRANCH=$(git symbolic-ref HEAD 2>/dev/null) || { echo "$@" ; exit ; } 10 | BRANCH=${BRANCH#refs/heads/} 11 | 12 | RELATIVE=$(git rev-parse --show-prefix) 13 | RELATIVE="${RELATIVE//%\/}" 14 | 15 | LOCATION="${PWD%/$RELATIVE}" 16 | 17 | echo "[$BRANCH!${LOCATION/*\/} ${RELATIVE:+/$RELATIVE}]$ " -------------------------------------------------------------------------------- /git-addremove: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git add -A $@ 4 | git ls-files --deleted -z $@ | xargs -0 git rm 5 | -------------------------------------------------------------------------------- /git-all-commits: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find .git/objects -type f | \ 4 | while read file; do 5 | if echo $file | egrep -q '\.idx$'; then 6 | git show-index < $file | awk '{print $2}' 7 | elif echo $file | egrep -q '[0-9a-f]{38}$'; then 8 | echo $(basename $(dirname $file))$(basename $file) 9 | fi 10 | done | \ 11 | while read hash; do 12 | if [ "$(git cat-file -t $hash)" = commit ]; then 13 | echo $hash 14 | fi 15 | done 16 | -------------------------------------------------------------------------------- /git-apply-feature: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git merge --no-ff $1 -------------------------------------------------------------------------------- /git-build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git clean -f -x -d 4 | git checkout $1 5 | 6 | rm -fr /usr/local/stow/git-$1 7 | 8 | make prefix=/usr/local/stow/git-$1 -j3 install 9 | 10 | git checkout origin/man 11 | 12 | rsync -av man1/ /usr/local/stow/git-$1/share/man/man1/ 13 | rsync -av man5/ /usr/local/stow/git-$1/share/man/man5/ 14 | rsync -av man7/ /usr/local/stow/git-$1/share/man/man7/ 15 | 16 | git clean -f -x -d 17 | git checkout master 18 | chown -R johnw . 19 | 20 | git reset --hard HEAD 21 | git merge origin/master 22 | 23 | cd /usr/local/stow 24 | stow -D git-* 25 | 26 | stow git-$1 27 | -------------------------------------------------------------------------------- /git-changelog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # git-changelog 4 | # 5 | # version 1.0, by John Wiegley 6 | # 7 | # The purpose of this code is to turn "git log" output into a complete 8 | # ChangeLog, for projects who wish to begin using a ChangeLog, but haven't 9 | # been. 10 | 11 | import datetime 12 | import string 13 | import sys 14 | import re 15 | 16 | from subprocess import * 17 | 18 | p = Popen("git log --stat %s" % string.join(sys.argv[1:], " "), 19 | shell = True, stdout = PIPE).stdout 20 | 21 | line = p.readline() 22 | while line: 23 | match = re.match("commit ([0-9a-f]+)", line) 24 | assert match 25 | hash_id = match.group(1) 26 | 27 | line = p.readline() 28 | 29 | match = re.match("Author: (.+)", line) 30 | assert match 31 | author = match.group(1) 32 | author = re.sub(" <", " <", author) 33 | 34 | line = p.readline() 35 | 36 | match = re.match("Date: +(.+?) [-+][0-9]{4}", line) 37 | assert match 38 | # Tue Sep 30 05:43:49 2003 +0000 39 | date = datetime.datetime.strptime(match.group(1), '%a %b %d %H:%M:%S %Y') 40 | 41 | line = p.readline() # absorb separator 42 | line = p.readline() 43 | 44 | log_text = "" 45 | while line and line != '\n': 46 | if not log_text: 47 | log_text += line[4:] 48 | else: 49 | log_text += "\t" + line[4:] 50 | 51 | line = p.readline() 52 | 53 | line = p.readline() 54 | 55 | files = [] 56 | while line and line != '\n': 57 | match = re.match(" (.+?) +\\|", line) 58 | if match: 59 | files.append(match.group(1)) 60 | line = p.readline() 61 | else: 62 | break 63 | 64 | line = p.readline() 65 | 66 | fp = Popen("fmt", shell = True, stdin = PIPE, stdout = PIPE) 67 | 68 | fp.stdin.write("\t* %s: %s\n" % (string.join(files, ",\n\t"), log_text)) 69 | fp.stdin.close() 70 | log_text = fp.stdout.read() 71 | 72 | del fp 73 | 74 | print "%s %s\n\n%s\t* commit %s\n" % \ 75 | (date.strftime("%Y-%m-%d"), author, log_text, hash_id) 76 | 77 | line = p.readline() 78 | 79 | # git-changelog ends here 80 | -------------------------------------------------------------------------------- /git-children-of: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | commit=$1 4 | branch=$2 5 | if [[ -z "$branch" ]]; then 6 | branch=HEAD 7 | fi 8 | 9 | git rev-list --children $branch --not $commit^@ | \ 10 | awk "/^$commit/ { print \$2 }" 11 | -------------------------------------------------------------------------------- /git-current: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -z "$1" ]]; then 4 | ancestor=master 5 | else 6 | ancestor=$1 7 | shift 1 8 | fi 9 | current="$ancestor" 10 | 11 | ancestor=$(git rev-parse $ancestor) 12 | 13 | for head in $(git rev-parse --branches); do 14 | if [[ $head != $ancestor ]]; then 15 | if git rev-list -30 $head | grep -q $ancestor; then 16 | current="$current $(git describe --all --abbrev=0 $head | sed 's/heads\///')" 17 | fi 18 | fi 19 | done 20 | 21 | git show-branch $current 22 | -------------------------------------------------------------------------------- /git-diff-directory: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stat=true 4 | if [[ "$1" == "-p" ]]; then 5 | stat=false 6 | shift 1 7 | fi 8 | 9 | HERE=$(pwd) 10 | 11 | (cd "$1" && git --git-dir=$HERE/.git diff ${2:-HEAD}) | \ 12 | if [[ $stat == true ]]; then \ 13 | diffstat | grep -v only$; \ 14 | else \ 15 | cat; \ 16 | fi 17 | -------------------------------------------------------------------------------- /git-empty-branch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git stash 4 | git symbolic-ref HEAD refs/heads/$1 5 | rm .git/index 6 | git clean -f -d 7 | -------------------------------------------------------------------------------- /git-erase-reflog: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git reflog expire --expire=0 "$@" 3 | -------------------------------------------------------------------------------- /git-external-ediff: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | [ $# -eq 7 ] && emacsclient --eval "(ediff \"$2\" \"$PWD/$5\")" 4 | -------------------------------------------------------------------------------- /git-find: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # git-find, version 1.0 4 | # 5 | # by John Wiegley 6 | # 7 | # usage: git find ... 8 | # 9 | # Performs an exhaustive search of your object database to inform you exactly 10 | # how the given commit or tag fits in your repository. It tells you all the 11 | # parents and children of the commit/tag, and every name it can be referenced 12 | # by. It will tell you if it can be reached only by a reflog entry, or the 13 | # stash, or if it's descendend from a dangling commit. 14 | # 15 | # TODO: Add support for searching for blobs and trees as well. 16 | # 17 | # NOTE: This script can be very slow. 18 | 19 | class GitObject 20 | attr_reader :ident 21 | 22 | def initialize(ident) 23 | @ident = ident 24 | end 25 | end 26 | 27 | class Blob < GitObject 28 | attr_reader :names 29 | 30 | def initialize(ident) 31 | super ident 32 | @names = [] 33 | end 34 | 35 | def add_name(name) 36 | @names.push(name) 37 | end 38 | end 39 | 40 | class Tree < GitObject 41 | attr_reader :names 42 | 43 | def initialize(ident) 44 | super ident 45 | @names = [] 46 | end 47 | 48 | def add_name(name) 49 | @names.push(name) 50 | end 51 | end 52 | 53 | class Commit < GitObject 54 | attr_reader :names 55 | attr_reader :parents 56 | attr_reader :children 57 | 58 | def initialize(ident, *parents) 59 | super ident 60 | @names = [] 61 | @parents = parents 62 | @children = [] 63 | end 64 | 65 | def add_name(name) 66 | @names.push(name) 67 | end 68 | 69 | def add_parent(parent) 70 | unless @parents.include?(parent) 71 | @parents.push(parent) 72 | parent.add_child(self) 73 | end 74 | end 75 | 76 | def add_child(child) 77 | unless @children.include?(child) 78 | @children.push(child) 79 | end 80 | end 81 | 82 | def assign_name(name) 83 | if @names.include?(name) 84 | return 85 | end 86 | add_name(name) 87 | 88 | offset = 0 89 | @parents.each do |parent| 90 | if offset == 0 91 | if name =~ /(.+?)~([0-9]+)$/ 92 | child_name = $1 + "~" + ($2.to_i + 1).to_s 93 | else 94 | child_name = name + "~1" 95 | end 96 | else 97 | child_name = name + "^" + (offset + 1).to_s 98 | end 99 | parent.assign_name(child_name) 100 | offset += 1 101 | end 102 | end 103 | end 104 | 105 | class Tag < GitObject 106 | attr_reader :name 107 | attr_reader :commit 108 | 109 | def initialize(ident, name, commit) 110 | super ident 111 | @name = name 112 | @commit = commit 113 | end 114 | end 115 | 116 | class Ref 117 | attr_reader :name 118 | attr_reader :commit 119 | 120 | def initialize(name, commit) 121 | @name = name 122 | @commit = commit 123 | end 124 | end 125 | 126 | $objects = {} 127 | 128 | # Build up a full commit history for every ref, and each reflog entry for each 129 | # ref. 130 | 131 | def get_ref_history(ref) 132 | first_obj = true 133 | 134 | `git rev-list --parents #{ref}`.split("\n").each do |entry| 135 | commit, *parents = entry.split(' ') 136 | 137 | if $objects.has_key?(commit) 138 | obj = $objects[commit] 139 | else 140 | obj = Commit.new(commit) 141 | $objects[commit] = obj 142 | end 143 | 144 | if first_obj and ref =~ /\/tags\// 145 | id = `git rev-parse #{ref}`.chomp 146 | $objects[id] = Tag.new(id, ref, obj) 147 | first_obj = false 148 | end 149 | 150 | if obj.parents.size == 0 151 | parents.each do |parent| 152 | if $objects.has_key?(parent) 153 | parent_obj = $objects[parent] 154 | else 155 | parent_obj = Commit.new(parent) 156 | $objects[parent] = parent_obj 157 | end 158 | obj.add_parent(parent_obj) 159 | end 160 | end 161 | end 162 | end 163 | 164 | def process_names(ref, name = nil, hash = nil) 165 | # Assign symbolic names to each commit 166 | if not hash 167 | hash = `git rev-parse #{ref}`.chomp 168 | end 169 | raise "Cannot locate object #{ref} => #{hash}" if not $objects.has_key?(hash) 170 | obj = $objects[hash] 171 | if obj.is_a? Tag 172 | obj.commit.assign_name(name ? name : ref) 173 | else 174 | obj.assign_name(name ? name : ref) 175 | end 176 | end 177 | 178 | $refs = `git rev-parse --symbolic --all`.split 179 | $refs.push("HEAD") 180 | 181 | puts "Processing refs history ..." 182 | $refs.each { |ref| get_ref_history(ref) } 183 | $refs.each { |ref| process_names(ref) } 184 | 185 | $hashes = ARGV[0].map { |ref| `git rev-parse #{ref}`.chomp } 186 | 187 | $search_reflogs = false 188 | $hashes.each do |hash| 189 | unless $objects.has_key?(hash) 190 | $search_reflogs = true 191 | end 192 | end 193 | 194 | if $search_reflogs 195 | puts "Processing reflogs ..." 196 | $refs.each do |ref| 197 | unless ref == "refs/stash" 198 | # Handle each reflog entry 199 | `git reflog show --abbrev=40 #{ref}`.split("\n").each do |line| 200 | if line =~ /^([0-9a-f]{40}) ([^@]+@\{[0-9]+\}): / 201 | hash = $1 202 | unless $objects.has_key?(hash) 203 | puts " unreachable reflog #{$2}" 204 | get_ref_history $2 205 | process_names $2, nil, hash 206 | end 207 | end 208 | end 209 | end 210 | end 211 | end 212 | 213 | $search_dangling = false 214 | $hashes.each do |hash| 215 | unless $objects.has_key?(hash) 216 | $search_dangling = true 217 | end 218 | end 219 | 220 | if $search_dangling 221 | puts "Finding unreachable objects ..." 222 | `git fsck --full --unreachable`.split("\n").each do |item| 223 | if item =~ /unreachable (blob|tag|commit|tree) ([a-f0-9]{40})/ 224 | puts " " + item 225 | if $1 == "blob" 226 | $objects[$2] = Blob.new($2) 227 | elsif $1 == "tag" 228 | commit = Commit.new(`git rev-list -1 #{$2}`.chomp) 229 | $objects[$2] = Tag.new($2, "", commit) 230 | get_ref_history $2 231 | process_names $2, "dangling-tag-#{$2}" 232 | elsif $1 == "commit" 233 | get_ref_history $2 234 | process_names $2, "dangling-commit-#{$2}" 235 | elsif $1 == "tree" 236 | $objects[$2] = Tree.new($2) 237 | end 238 | end 239 | end 240 | end 241 | 242 | ARGV[0].each do |ref| 243 | hash = `git rev-parse #{ref}`.chomp 244 | 245 | object = $objects[hash] 246 | 247 | puts "" 248 | if object.is_a? Tag 249 | puts "Tag #{ref} => #{hash}" 250 | object = object.commit 251 | puts " commit #{object.ident}" 252 | else 253 | puts "Commit #{ref} => #{hash}" 254 | end 255 | 256 | object.parents.each { |parent| puts " parent #{parent.ident}" } 257 | object.children.each { |child| puts " child #{child.ident}" } 258 | object.names.each { |name| puts " name #{name}" } 259 | end 260 | -------------------------------------------------------------------------------- /git-find-blob: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | filename=$1 4 | 5 | want=$(git-hash-object "$filename") 6 | 7 | git-rev-list --since="6 months ago" HEAD | while read commit ; do 8 | git-ls-tree -r $commit | while read perm type hash filename; do 9 | if test "$want" = "$hash"; then 10 | echo matched $filename in commit $commit 11 | fi 12 | done 13 | done 14 | -------------------------------------------------------------------------------- /git-find-children: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# == 0 ]]; then 4 | echo usage: git find-children REFS... 5 | fi 6 | 7 | for ref in "$@"; do 8 | for sha1 in $(git rev-list "$@") 9 | do 10 | done 11 | done 12 | -------------------------------------------------------------------------------- /git-find-fetch: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # usage: git-find-fetch [--mirror] [--repack] DIRS... 4 | # 5 | # by John Wiegley 6 | # 7 | # The purpose of this script is to walk recursively through a set of 8 | # directories, fetching updates for any and all Git repositories found 9 | # therein. 10 | # 11 | # The script is smart about using git-svn or gc-utils, if those are needed to 12 | # get updates. Further, it calls "git remote update" for each repository, to 13 | # ensure all its remotes are also updated. 14 | # 15 | # Lastly, if you have a remote called "mirror" in any repository, all its refs 16 | # will be "push --mirror"'d to that remote under the assumption that you want 17 | # the mirror to receive all the updates you just fetch'd. 18 | # 19 | # Options: 20 | # 21 | # --mirror Only push to your mirrors, do not fetch. 22 | # --repack Repack and prune repositories after updating them. 23 | 24 | mirror_only=false 25 | if [[ "$1" == --mirror ]]; then 26 | mirror_only=true 27 | shift 1 28 | fi 29 | 30 | repack=false 31 | if [[ "$1" == --repack ]]; then 32 | repack=true 33 | shift 1 34 | fi 35 | 36 | find "$@" \( -name .git -o -name '*.git' \) -type d | \ 37 | while read repo_dir 38 | do 39 | if [[ -f "$repo_dir"/config ]] 40 | then 41 | if [[ $mirror_only == false ]]; then 42 | # If this is a git-svn repo, use git svn fetch 43 | if grep -q '^\[svn-remote ' "$repo_dir"/config 44 | then 45 | echo git svn fetch: $repo_dir 46 | GIT_DIR="$repo_dir" git svn fetch 47 | 48 | # If this is a gc-utils repo, use gc-utils update 49 | elif grep -q '^\[gc-utils\]' "$repo_dir"/config 50 | then 51 | echo gc-utils update: $repo_dir 52 | (cd "$repo_dir"; gc-utils update) 53 | 54 | fi 55 | 56 | GIT_DIR="$repo_dir" git remote update 57 | fi 58 | 59 | for remote in $(GIT_DIR="$repo_dir" git remote) 60 | do 61 | if [[ $remote == mirror ]]; then 62 | echo git push: $repo_dir -- $remote 63 | GIT_DIR="$repo_dir" git push -f --mirror $remote 64 | fi 65 | done 66 | 67 | if [[ $repack == true ]]; then 68 | GIT_DIR="$repo_dir" git fsck --full && \ 69 | GIT_DIR="$repo_dir" git repack -a -d -f --window=200 --depth=50 && \ 70 | GIT_DIR="$repo_dir" git prune 71 | fi 72 | fi 73 | done 74 | -------------------------------------------------------------------------------- /git-flush: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The job of git-flush is to recompactify your repository to be as small 4 | # as possible, by dropping all reflogs, stashes, and other cruft that may 5 | # be bloating your pack files. 6 | 7 | rm -fr .git/refs/original 8 | perl -i -ne 'print unless /refs\/original/;' .git/info/refs .git/packed-refs 9 | 10 | git reflog expire --expire=0 --all 11 | 12 | if [ "$1" = "-f" ]; then 13 | git stash clear 14 | fi 15 | 16 | git repack -adf #--window=200 --depth=50 17 | git prune 18 | -------------------------------------------------------------------------------- /git-full-reset: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git reset --hard HEAD && git clean -fdx 4 | -------------------------------------------------------------------------------- /git-hunt-and-seek: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for hash in $(grep ^commit | awk '{print $2}'); do 4 | git diff-directory $1 $hash 5 | done 6 | -------------------------------------------------------------------------------- /git-incoming: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_BRANCH=$(git branch &>/dev/null; if [ $? -eq 0 ]; then echo "$(git branch | grep '^*' |sed s/\*\ //)"; fi) 4 | 5 | if [ "${CURRENT_BRANCH}" != "" ]; then 6 | 7 | TARGET="${1}" 8 | 9 | if [ "${TARGET}" == "" ]; then 10 | TRACKING_REPOSITORY="$(git config branch.${CURRENT_BRANCH}.remote)" 11 | 12 | # there is a tracking repository 13 | if [ "${TRACKING_REPOSITORY}" != "" ]; then 14 | REMOTE_REPOSITORY="${TRACKING_REPOSITORY}" 15 | REMOTE_BRANCH="$(git config branch.${CURRENT_BRANCH}.merge | cut -d"/" -f3)" 16 | TARGET="${REMOTE_REPOSITORY}/${REMOTE_BRANCH}" 17 | fi 18 | fi 19 | 20 | echo "From: ${TARGET}" 21 | echo "" 22 | 23 | git log ..${TARGET} 24 | fi 25 | -------------------------------------------------------------------------------- /git-index: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git-diff --cached $* 3 | -------------------------------------------------------------------------------- /git-iterate: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # git-iterate 4 | # 5 | # Runs a script for every commit in a git repository 6 | # 7 | # Example: git-iterate 'echo `flog -s app` `flog -s spec`' 8 | # 9 | # FIXME: unsafe regex 10 | git-filter-branch --tree-filter "$1" | sed s/Rewrite.*\)//g 11 | -------------------------------------------------------------------------------- /git-last: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | REVISIONS=${1} 3 | git log -n ${REVISIONS} 4 | -------------------------------------------------------------------------------- /git-maxpack: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git gc 4 | git prune 5 | git repack -a -d --depth=250 --window=250 6 | -------------------------------------------------------------------------------- /git-opendiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ######################################################################################################## 4 | # Usage: # 5 | # gitopendiff [-r:] [repository] # 6 | # # 7 | # Example: # 8 | # gitopendiff # == gitopendiff -rHEAD^:HEAD . # 9 | # gitopendiff -rHEAD^^:HEAD # == gitopendiff -rHEAD^^:HEAD . # 10 | # gitopendiff -master:origin/master # == gitopendiff -rmaster:origin/master . # 11 | # gitopendiff /path/to/git/repository # == gitopendiff -rHEAD^:HEAD /path/to/git/repository # 12 | # gitopendiff -rHEAD^:HEAD /path/to/git/repository # == gitopendiff -rHEAD^:HEAD /path/to/git/repository # 13 | # # 14 | # Authored by Simon Menke, with minor optimizations by Jonathan del Strother # 15 | ######################################################################################################## 16 | 17 | def checkout(origin, rev) 18 | tmp = "/tmp/git_opendiff_#{rand}.tmp/" 19 | Dir.mkdir(tmp) 20 | Dir.chdir(origin) do 21 | # The clean, porcelainy way would be "git archive --format=tar '#{rev}' | (cd #{tmp} && tar xf -)" 22 | # I've found the plumbing to be marginally faster : 23 | system "git read-tree '#{rev}'; git checkout-index --prefix=#{tmp} -a; git read-tree HEAD" 24 | end 25 | return tmp 26 | end 27 | 28 | def diff(origin, revs={}) 29 | revs = {:left=>"HEAD^",:right=>"HEAD"}.merge(revs) 30 | left = checkout origin, revs[:left] 31 | right = checkout origin, revs[:right] 32 | system "opendiff '#{left}' '#{right}'" 33 | end 34 | 35 | revs = {} 36 | git_dir = Dir.pwd 37 | ARGV.each do |arg| 38 | if arg[0,2] == "-r" 39 | rev_strings = arg[2..-1].split(':') 40 | case rev_strings.size 41 | when 1 42 | revs[:left] = rev_strings[0] unless rev_strings[0].empty? 43 | when 2 44 | revs[:left] = rev_strings[0] unless rev_strings[0].empty? 45 | revs[:right] = rev_strings[1] unless rev_strings[1].empty? 46 | else 47 | revs = nil 48 | end 49 | else 50 | git_dir = arg unless arg.empty? 51 | end 52 | end 53 | 54 | diff(git_dir, revs) 55 | -------------------------------------------------------------------------------- /git-outgoing: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_BRANCH=$(git branch &>/dev/null; if [ $? -eq 0 ]; then echo "$(git branch | grep '^*' |sed s/\*\ //)"; fi) 4 | 5 | if [ "${CURRENT_BRANCH}" != "" ]; then 6 | 7 | TARGET="${1}" 8 | 9 | if [ "${TARGET}" == "" ]; then 10 | TRACKING_REPOSITORY="$(git config branch.${CURRENT_BRANCH}.remote)" 11 | 12 | # there is a tracking repository 13 | if [ "${TRACKING_REPOSITORY}" != "" ]; then 14 | REMOTE_REPOSITORY="${TRACKING_REPOSITORY}" 15 | REMOTE_BRANCH="$(git config branch.${CURRENT_BRANCH}.merge | cut -d"/" -f3)" 16 | TARGET="${REMOTE_REPOSITORY}/${REMOTE_BRANCH}" 17 | fi 18 | fi 19 | 20 | echo "To: ${TARGET}" 21 | echo "" 22 | 23 | git log ${TARGET}.. 24 | fi 25 | -------------------------------------------------------------------------------- /git-pack-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cp .git/config .gitconfig 3 | -------------------------------------------------------------------------------- /git-patch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_BRANCH=$(git branch &>/dev/null; if [ $? -eq 0 ]; then echo "$(git branch | grep '^*' |sed s/\*\ //)"; fi) 4 | CURRENT_DIR=$(basename $(pwd)) 5 | 6 | PATCH_DIR="${HOME}/Documents/Patches/${CURRENT_DIR}" 7 | 8 | mkdir -p ${PATCH_DIR} 9 | 10 | git format-patch master --stdout > ${PATCH_DIR}/${CURRENT_BRANCH}.diff 11 | 12 | -------------------------------------------------------------------------------- /git-publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SERVER=$1 4 | PATHNAME=$2 5 | 6 | ssh $SERVER "mkdir $PATHNAME; GIT_DIR=$PATHNAME git init" 7 | 8 | git remote add $3 git+ssh://$SERVER$PATHNAME 9 | 10 | git push origin master:refs/heads/master 11 | -------------------------------------------------------------------------------- /git-publish-branch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ## git-publish-branch: a simple script to ease the unnecessarily complex 4 | ## task of "publishing" a branch, i.e., taking a local branch, creating a 5 | ## reference to it on a remote repo, and setting up the local branch to 6 | ## track the remote one, all in one go. you can even delete that remote 7 | ## reference. 8 | ## 9 | ## Usage: git publish-branch [-d] [repository] 10 | ## 11 | ## '-d' signifies deletion. is the branch to publish, and 12 | ## [repository] defaults to "origin". The remote branch name will be the 13 | ## same as the local branch name. Don't make life unnecessarily complex 14 | ## for yourself. 15 | ## 16 | ## Note that unpublishing a branch doesn't delete the local branch. 17 | ## Safety first! 18 | ## 19 | ## git-publish-branch Copyright 2008 William Morgan . 20 | ## This program is free software: you can redistribute it and/or modify 21 | ## it under the terms of the GNU General Public License as published by 22 | ## the Free Software Foundation, either version 3 of the License, or (at 23 | ## your option) any later version. 24 | ## 25 | ## This program is distributed in the hope that it will be useful, 26 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | ## GNU General Public License for more details. 29 | ## 30 | ## You can find the GNU General Public License at: 31 | ## http://www.gnu.org/licenses/ 32 | 33 | def exec cmd 34 | puts cmd 35 | system cmd or die unless $fake 36 | end 37 | 38 | def die s=nil 39 | $stderr.puts s if s 40 | exit(-1) 41 | end 42 | 43 | head = `git symbolic-ref HEAD`.chomp.gsub(/refs\/heads\//, "") 44 | delete = ARGV.delete "-d" 45 | $fake = ARGV.delete "-n" 46 | branch = (ARGV.shift || head).gsub(/refs\/heads\//, "") 47 | remote = ARGV.shift || "origin" 48 | local_ref = `git show-ref heads/#{branch}` 49 | remote_ref = `git show-ref remotes/#{remote}/#{branch}` 50 | remote_config = `git config branch.#{branch}.merge` 51 | 52 | if delete 53 | ## we don't do any checking here because the remote branch might actually 54 | ## exist, whether we actually know about it or not. 55 | exec "git push #{remote} :refs/heads/#{branch}" 56 | 57 | unless local_ref.empty? 58 | exec "git config --unset branch.#{branch}.remote" 59 | exec "git config --unset branch.#{branch}.merge" 60 | end 61 | else 62 | die "No local branch #{branch} exists!" if local_ref.empty? 63 | die "A remote branch #{branch} on #{remote} already exists!" unless remote_ref.empty? 64 | die "Local branch #{branch} is already a tracking branch!" unless remote_config.empty? 65 | 66 | exec "git push #{remote} #{branch}:refs/heads/#{branch}" 67 | exec "git config branch.#{branch}.remote #{remote}" 68 | exec "git config branch.#{branch}.merge refs/heads/#{branch}" 69 | end 70 | 71 | -------------------------------------------------------------------------------- /git-publish-repo: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for i in "$@" ; do rsync -av $i/.git jw:/srv/git/$i.git ; done 4 | -------------------------------------------------------------------------------- /git-push-all: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | for remote in $(git remote); do git push $remote; done 3 | -------------------------------------------------------------------------------- /git-push-branch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git push origin $1:refs/heads/$1 3 | -------------------------------------------------------------------------------- /git-rank-contributors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ## git-rank-contributors: a simple script to trace through the logs and 4 | ## rank contributors by the total size of the diffs they're responsible for. 5 | ## A change counts twice as much as a plain addition or deletion. 6 | ## 7 | ## Output may or may not be suitable for inclusion in a CREDITS file. 8 | ## Probably not without some editing, because people often commit from more 9 | ## than one address. 10 | ## 11 | ## git-rank-contributors Copyright 2008 William Morgan . 12 | ## This program is free software: you can redistribute it and/or modify 13 | ## it under the terms of the GNU General Public License as published by 14 | ## the Free Software Foundation, either version 3 of the License, or (at 15 | ## your option) any later version. 16 | ## 17 | ## This program is distributed in the hope that it will be useful, 18 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ## GNU General Public License for more details. 21 | ## 22 | ## You can find the GNU General Public License at: 23 | ## http://www.gnu.org/licenses/ 24 | 25 | class String 26 | def obfuscate; gsub(/@/, " at the ").gsub(/\.(\w+)(>|$)/, ' dot \1s\2') end 27 | end 28 | 29 | lines = {} 30 | verbose = ARGV.delete("-v") 31 | obfuscate = ARGV.delete("-o") 32 | 33 | author = nil 34 | state = :pre_author 35 | `git log -p --no-color`.each do |l| 36 | case 37 | when (state == :pre_author || state == :post_author) && l =~ /Author: (.*)$/ 38 | author = $1 39 | state = :post_author 40 | lines[author] ||= 0 41 | when state == :post_author && l =~ /^\+\+\+/ 42 | state = :in_diff 43 | when state == :in_diff && l =~ /^[\+\-]/ 44 | lines[author] += 1 45 | when state == :in_diff && l =~ /^commit / 46 | state = :pre_author 47 | end 48 | end 49 | 50 | lines.sort_by { |a, c| -c }.each do |a, c| 51 | a = a.obfuscate if obfuscate 52 | if verbose 53 | puts "#{a}: #{c} lines of diff" 54 | else 55 | puts a 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /git-record: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # git-darcs-record, emulate "darcs record" interface on top of a git repository 5 | # 6 | # Usage: 7 | # git-darcs-record first asks for any new file (previously 8 | # untracked) to be added to the index. 9 | # git-darcs-record then asks for each hunk to be recorded in 10 | # the next commit. File deletion and binary blobs are supported 11 | # git-darcs-record finally asks for a small commit message and 12 | # executes the 'git commit' command with the newly created 13 | # changeset in the index 14 | 15 | 16 | # Copyright (C) 2007 Raphaël Slinckx 17 | # 18 | # This program is free software; you can redistribute it and/or 19 | # modify it under the terms of the GNU General Public License 20 | # as published by the Free Software Foundation; either version 2 21 | # of the License, or (at your option) any later version. 22 | # 23 | # This program is distributed in the hope that it will be useful, 24 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | # GNU General Public License for more details. 27 | # 28 | # You should have received a copy of the GNU General Public License 29 | # along with this program; if not, write to the Free Software 30 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 31 | 32 | import re, pprint, sys, os 33 | 34 | BINARY = re.compile("GIT binary patch") 35 | HEADER = re.compile("diff --git a/(.*) b/(.*)") 36 | class Hunk: 37 | def __init__(self, lines, binary): 38 | self.diff = None 39 | self.lines = lines 40 | self.keep = False 41 | self.binary = binary 42 | 43 | def format(self): 44 | output = self.diff.header.modified + "\n" 45 | if self.diff.header.deleted: 46 | output = "Removed file: " + output 47 | if self.binary: 48 | output = "Binary file changed: " + output 49 | 50 | if not self.binary: 51 | output += "\n".join(self.lines) + "\n" 52 | 53 | return output 54 | 55 | class Header: 56 | def __init__(self, lines): 57 | self.lines = lines 58 | self.modified = None 59 | self.deleted = False 60 | 61 | # Extract useful info from header from git 62 | for line in lines: 63 | if HEADER.match(line): 64 | match = HEADER.match(line) 65 | self.modified = match.group(1) 66 | if line.startswith("deleted "): 67 | self.deleted = True 68 | 69 | # Make sure we know what file we are modifying 70 | assert self.modified 71 | 72 | class Diff: 73 | def __init__(self, header, hunks): 74 | self.header = header 75 | self.hunks = hunks 76 | # Put a reference to ourselves in the hunks 77 | for hunk in self.hunks: 78 | hunk.diff = self 79 | self.keep = False 80 | 81 | def filter(self): 82 | output = '\n'.join(self.header.lines) + "\n" 83 | for hunk in self.hunks: 84 | if not hunk.keep: 85 | continue 86 | output += '\n'.join(hunk.lines) + "\n" 87 | return output 88 | 89 | @classmethod 90 | def filter_diffs(kls, diffs): 91 | output = "" 92 | for diff in diffs: 93 | if not diff.keep: 94 | continue 95 | output += diff.filter() 96 | return output 97 | 98 | @classmethod 99 | def parse(kls, lines): 100 | in_header = True 101 | binary = False 102 | header = [] 103 | hunks = [] 104 | current_hunk = [] 105 | for line in lines: 106 | if in_header and (line[0] not in (" ", "@", "\\") or line.startswith("+++ ") or line.startswith("--- ")) and not BINARY.match(line): 107 | header.append(line) 108 | elif BINARY.match(line): 109 | in_header = False 110 | binary = True 111 | header.append(line) 112 | elif len(line) >= 1 and line[0] == "@": 113 | in_header = False 114 | if current_hunk: 115 | hunks.append(Hunk(current_hunk, binary)) 116 | current_hunk = [] 117 | current_hunk.append(line) 118 | else: 119 | current_hunk.append(line) 120 | if current_hunk: 121 | hunks.append(Hunk(current_hunk, binary)) 122 | return Diff(Header(header), hunks) 123 | 124 | @classmethod 125 | def split(kls, lines): 126 | diffs = [] 127 | current_diff = [] 128 | for line in lines: 129 | if line.startswith("diff --git "): 130 | if current_diff: 131 | diffs.append(current_diff) 132 | current_diff = [] 133 | current_diff.append(line) 134 | else: 135 | current_diff.append(line) 136 | if current_diff: 137 | diffs.append(current_diff) 138 | return [Diff.parse(lines) for lines in diffs] 139 | 140 | def read_answer(question, allowed_responses=["Y", "n", "d", "a"]): 141 | #Make sure there is alway a default selection 142 | assert [r for r in allowed_responses if r.isupper()] 143 | 144 | while True: 145 | resp = raw_input("%s [%s] : " % (question, "".join(allowed_responses))) 146 | if resp in [r.lower() for r in allowed_responses]: 147 | break 148 | elif resp == "": 149 | resp = [r for r in allowed_responses if r.isupper()][0].lower() 150 | break 151 | print 'Unexpected answer: %r' % resp 152 | return resp 153 | 154 | 155 | def setup_git_dir(): 156 | global GIT_DIR 157 | GIT_DIR = os.getcwd() 158 | while not os.path.exists(os.path.join(GIT_DIR, ".git")): 159 | GIT_DIR = os.path.dirname(GIT_DIR) 160 | if GIT_DIR == "/": 161 | return False 162 | os.chdir(GIT_DIR) 163 | return True 164 | 165 | def git_get_untracked_files(): 166 | return [f.strip() for f in os.popen("git ls-files --others --exclude-from='%s' --exclude-per-directory=.gitignore" % (os.path.join(GIT_DIR, ".git", "info", "exclude"))).readlines()] 167 | 168 | def git_track_file(f): 169 | os.spawnvp(os.P_WAIT, "git", ["git", "add", f]) 170 | 171 | def git_diff(): 172 | return os.popen("git diff -u --no-color --binary").readlines() 173 | 174 | def git_apply(patch): 175 | stdin, stdout = os.popen2(["git", "apply", "--cached", "-"]) 176 | stdin.write(patch) 177 | stdin.close() 178 | output = stdout.read() 179 | stdout.close() 180 | os.wait() 181 | return output 182 | 183 | def git_status(): 184 | os.spawnvp(os.P_WAIT, "git", ["git", "status"]) 185 | 186 | def git_commit(msg): 187 | os.spawnvp(os.P_WAIT, "git", ["git", "commit", "-m", patch_name]) 188 | 189 | # Main loop ------------------------ 190 | if not setup_git_dir(): 191 | print "Must be in a git (sub-)directory! Exiting..." 192 | sys.exit() 193 | 194 | # Ask for new files ---------------- 195 | git_untracked_files = git_get_untracked_files() 196 | git_track_files = [] 197 | all = False 198 | done = False 199 | for i, f in enumerate(git_untracked_files): 200 | if not all: 201 | print "Add file: ", f 202 | resp = read_answer("Shall I add this file? (%d/%d)" % (i+1, len(git_untracked_files))) 203 | else: 204 | resp = "y" 205 | 206 | if resp == "y": 207 | git_track_files.append(f) 208 | elif resp == "a": 209 | git_track_files.append(f) 210 | all = True 211 | elif resp == "d": 212 | done = True 213 | break 214 | 215 | # Ask for each hunk of the diff 216 | diffs = Diff.split([line[:-1] for line in git_diff()]) 217 | total_hunks = sum([len(diff.hunks) for diff in diffs]) 218 | 219 | n_hunk = 1 220 | for diff in diffs: 221 | if done: 222 | break 223 | for hunk in diff.hunks: 224 | # Check if we are in override mode 225 | if not all: 226 | print 227 | print hunk.format() 228 | resp = read_answer('Shall I record this change? (%d/%d)' % (n_hunk, total_hunks)) 229 | # Otherwise say 'y' to all remaining patches 230 | else: 231 | resp = "y" 232 | 233 | if resp == "y": 234 | diff.keep = True 235 | hunk.keep = True 236 | elif resp == "a": 237 | diff.keep = True 238 | hunk.keep = True 239 | all = True 240 | elif resp == "d": 241 | done = True 242 | break 243 | 244 | n_hunk += 1 245 | 246 | # Add new files to track 247 | for f in git_track_files: 248 | git_track_file(f) 249 | 250 | # Generate a new patch to be used with git apply 251 | new_patch = Diff.filter_diffs(diffs) 252 | if new_patch: 253 | print git_apply(new_patch) 254 | 255 | if new_patch or git_track_files: 256 | git_status() 257 | patch_name = raw_input("What is the patch name? ") 258 | git_commit(patch_name) 259 | else: 260 | print "Ok, if you don't want to record anything, that's fine!" 261 | -------------------------------------------------------------------------------- /git-remove-empty-commits: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi' "$@" 3 | -------------------------------------------------------------------------------- /git-retrack: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Based on an alias written by doener on #git. 4 | 5 | # usage: git retrack REMOTE URL BRANCH 6 | 7 | branch=$(git rev-parse --symbolic-full-name HEAD | sed 's/refs\/heads\///') 8 | 9 | git remote add -f $1 "$2" || echo But that\'s perfectly OK. 10 | 11 | git config branch.$branch.remote "$1" 12 | git config branch.$branch.merge "refs/heads/${3:-$branch}" 13 | -------------------------------------------------------------------------------- /git-rm-conflicts: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git-rm-conflicts, version 1.3 4 | # 5 | # by John Wiegley 6 | # 7 | # Run without arguments to see usage. 8 | 9 | if [[ -z "$1" ]]; then 10 | cat < 21 | $ git rm-conflicts ours FILE1 FILE2 22 | 23 | 24 | NOTE: You almost never want to use this command, but instead should 25 | check why exactly your files are conflicting. Use of this script is 26 | almost guaranteed to throw away code you don't want to throw away!! 27 | But if you need it, here it is. 28 | EOF 29 | exit 1 30 | fi 31 | 32 | recursed=false 33 | if [[ "$1" == --recursed ]]; then 34 | recursed=true 35 | shift 1 36 | fi 37 | 38 | if [[ "$1" == ours ]]; then 39 | shift 1 40 | wipe=ours 41 | elif [[ "$1" == theirs ]]; then 42 | shift 1 43 | wipe=theirs 44 | else 45 | echo error: Must specify which set of changes to remove: ours or theirs 46 | exit 1 47 | fi 48 | 49 | for item in "$@"; do 50 | if [[ -d "$item" && $recursed == false ]]; then 51 | if [[ $(basename "$item") != .git ]]; then 52 | git ls-files -z -u "$item" | xargs -0 $0 --recursed $wipe 53 | fi 54 | elif [[ -f "$item" ]]; then 55 | echo Removing $wipe from: $item 56 | if [[ $wipe == ours ]]; then 57 | perl -i -ne 'print unless /<<<<<>>>>>/;' "$item" 58 | elif [[ $wipe == theirs ]]; then 59 | perl -i -ne 'print unless /<<<<<>>>>>/;' "$item" 60 | fi 61 | fi 62 | done 63 | -------------------------------------------------------------------------------- /git-sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # A customized bash environment suitable for git work. 4 | # 5 | # Copyright (C) 2008 Ryan Tomayko 6 | # Copyright (C) 2008 Aristotle Pagaltzis 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with this program; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | # Distributed under the GNU General Public License, version 2.0. 22 | 23 | # use to install the sh alias 24 | [[ $1 = '--configure' && $# = 1 ]] && { 25 | set -e 26 | git config --global alias.sh '!git-sh' 27 | echo "alias 'sh' added to ~/.gitconfig" 28 | exit 0 29 | } 30 | 31 | # we expect to be sourced into an interactive shell. when executed as a 32 | # command, kick off a new shell and source us. this is a pretty cool hack; 33 | # make it better. 34 | [ "$0" = 'bash' ] || 35 | exec /usr/bin/env bash --rcfile "$@" "$0" 36 | 37 | # source the user's .bashrc file 38 | [ -r ~/.bashrc ] && { 39 | pushd ~ > /dev/null 40 | . .bashrc 41 | popd > /dev/null 42 | } 43 | 44 | 45 | # create aliases and configure bash completion for most porcelain commands 46 | 47 | _git_cmd_cfg=( 48 | 'add alias' 49 | 'am alias stdcmpl' 50 | 'annotate alias' 51 | 'apply alias stdcmpl' 52 | 'archive alias' 53 | 'bisect alias stdcmpl' 54 | 'blame alias' 55 | 'branch alias stdcmpl' 56 | 'bundle alias stdcmpl' 57 | 'cat-file alias' 58 | 'checkout alias stdcmpl' 59 | 'cherry alias stdcmpl' 60 | 'cherry-pick alias stdcmpl' 61 | 'clean alias' 62 | 'clone alias' 63 | 'commit alias stdcmpl' 64 | 'config alias stdcmpl' 65 | 'describe alias stdcmpl' 66 | 'diff alias stdcmpl' 67 | 'fetch alias stdcmpl' 68 | 'format-patch alias stdcmpl' 69 | 'fsck alias' 70 | 'gc alias stdcmpl' 71 | 'gui alias' 72 | 'init alias' 73 | 'instaweb alias' 74 | 'log alias logcmpl' 75 | 'lost-found alias' 76 | 'ls-files alias' 77 | 'ls-remote alias stdcmpl' 78 | 'ls-tree alias stdcmpl' 79 | 'merge alias stdcmpl' 80 | 'merge-base stdcmpl' 81 | 'mergetool alias' 82 | 'mv alias' 83 | 'name-rev stdcmpl' 84 | 'patch-id alias' 85 | 'peek-remote alias' 86 | 'prune alias' 87 | 'pull alias stdcmpl' 88 | 'push alias stdcmpl' 89 | 'quiltimport alias' 90 | 'rebase alias stdcmpl' 91 | 'remote alias stdcmpl' 92 | 'repack alias' 93 | 'repo-config alias' 94 | 'request-pull alias' 95 | 'reset alias stdcmpl' 96 | 'rev-list alias' 97 | 'rev-parse alias' 98 | 'revert alias' 99 | 'rm alias' 100 | 'send-email alias' 101 | 'send-pack alias' 102 | 'shortlog stdcmpl' 103 | 'show alias stdcmpl' 104 | 'show-branch logcmpl' 105 | 'stash alias stdcmpl' 106 | 'status alias' 107 | 'stripspace alias' 108 | 'submodule alias stdcmpl' 109 | 'svn alias' 110 | 'symbolic-ref alias' 111 | 'tag alias stdcmpl' 112 | 'tar-tree alias' 113 | 'var alias' 114 | 'whatchanged alias logcmpl' 115 | ) 116 | 117 | for cfg in "${_git_cmd_cfg[@]}" ; do 118 | read cmd opts <<< $cfg 119 | for opt in $opts ; do 120 | case $opt in 121 | alias) alias $cmd="git $cmd" ;; 122 | stdcmpl) complete -o default -o nospace -F _git_${cmd//-/_} $cmd ;; 123 | logcmpl) complete -o default -o nospace -F _git_log $cmd ;; 124 | esac 125 | done 126 | done 127 | 128 | 129 | # PROMPT ======================================================================= 130 | 131 | # grab colors from git's config. 132 | # 133 | # TODO we should probably add our own "color.sh.THING" values to the config and 134 | # a switch that let's you enable / disable colors entirely. 135 | COLOR_RESET="\[\033[0;39;49m\]" 136 | COLOR_BRANCH='\[$(git config --get-color color.branch.current)\]' 137 | COLOR_WORKDIR='\[$(git config --get-color color.diff.meta)\]' 138 | 139 | _git_prompt_setup() { 140 | br=$(git symbolic-ref HEAD 2>/dev/null) 141 | br=${br#refs/heads/} 142 | rel=$(git rev-parse --show-prefix 2>/dev/null) 143 | rel="${rel%/}" 144 | loc="${PWD%/$rel}" 145 | } 146 | 147 | _git_prompt_plain() { 148 | _git_prompt_setup 149 | PS1="git:$br!${loc/*\/}${rel:+/$rel}> " 150 | } 151 | 152 | _git_prompt_color() { 153 | _git_prompt_setup 154 | PS1="${COLOR_BRANCH}${br}${COLOR_RESET}!${COLOR_WORKDIR}${loc/*\/}${rel:+/$rel}${COLOR_RESET}> " 155 | } 156 | 157 | PROMPT_COMMAND=_git_prompt_color 158 | 159 | # try to provide a decent help command 160 | 161 | _help_display() { 162 | # show git's inbuilt help, after some tweaking... 163 | git --help | 164 | grep -v 'usage: git ' | 165 | sed "s/See 'git help/See 'help/" 166 | 167 | # show aliases from ~/.gitshrc 168 | [ -r ~/.gitshrc ] && { 169 | echo ; echo 'Aliases from ~/.gitshrc' 170 | perl -ne's/alias // or next; s/=/\t\t\t/; print' ~/.gitshrc 171 | } 172 | } 173 | 174 | help() { 175 | local _git_pager=$(git config core.pager) 176 | [ $# = 1 ] && 177 | git help $1 || 178 | (_help_display | ${_git_pager:-${PAGER:-less}}) 179 | } 180 | complete -o default -o nospace -F _git help 181 | 182 | # vim: tw=80 183 | # 184 | # bash completion support for core Git. 185 | # 186 | # Copyright (C) 2006,2007 Shawn O. Pearce 187 | # Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/). 188 | # Distributed under the GNU General Public License, version 2.0. 189 | # 190 | # The contained completion routines provide support for completing: 191 | # 192 | # *) local and remote branch names 193 | # *) local and remote tag names 194 | # *) .git/remotes file names 195 | # *) git 'subcommands' 196 | # *) tree paths within 'ref:path/to/file' expressions 197 | # *) common --long-options 198 | # 199 | # To use these routines: 200 | # 201 | # 1) Copy this file to somewhere (e.g. ~/.git-completion.sh). 202 | # 2) Added the following line to your .bashrc: 203 | # source ~/.git-completion.sh 204 | # 205 | # 3) You may want to make sure the git executable is available 206 | # in your PATH before this script is sourced, as some caching 207 | # is performed while the script loads. If git isn't found 208 | # at source time then all lookups will be done on demand, 209 | # which may be slightly slower. 210 | # 211 | # 4) Consider changing your PS1 to also show the current branch: 212 | # PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' 213 | # 214 | # The argument to __git_ps1 will be displayed only if you 215 | # are currently in a git repository. The %s token will be 216 | # the name of the current branch. 217 | # 218 | # To submit patches: 219 | # 220 | # *) Read Documentation/SubmittingPatches 221 | # *) Send all patches to the current maintainer: 222 | # 223 | # "Shawn O. Pearce" 224 | # 225 | # *) Always CC the Git mailing list: 226 | # 227 | # git@vger.kernel.org 228 | # 229 | 230 | __gitdir () 231 | { 232 | if [ -z "$1" ]; then 233 | if [ -n "$__git_dir" ]; then 234 | echo "$__git_dir" 235 | elif [ -d .git ]; then 236 | echo .git 237 | else 238 | git rev-parse --git-dir 2>/dev/null 239 | fi 240 | elif [ -d "$1/.git" ]; then 241 | echo "$1/.git" 242 | else 243 | echo "$1" 244 | fi 245 | } 246 | 247 | __git_ps1 () 248 | { 249 | local g="$(git rev-parse --git-dir 2>/dev/null)" 250 | if [ -n "$g" ]; then 251 | local r 252 | local b 253 | if [ -d "$g/../.dotest" ] 254 | then 255 | r="|AM/REBASE" 256 | b="$(git symbolic-ref HEAD 2>/dev/null)" 257 | elif [ -f "$g/.dotest-merge/interactive" ] 258 | then 259 | r="|REBASE-i" 260 | b="$(cat $g/.dotest-merge/head-name)" 261 | elif [ -d "$g/.dotest-merge" ] 262 | then 263 | r="|REBASE-m" 264 | b="$(cat $g/.dotest-merge/head-name)" 265 | elif [ -f "$g/MERGE_HEAD" ] 266 | then 267 | r="|MERGING" 268 | b="$(git symbolic-ref HEAD 2>/dev/null)" 269 | else 270 | if [ -f $g/BISECT_LOG ] 271 | then 272 | r="|BISECTING" 273 | fi 274 | if ! b="$(git symbolic-ref HEAD 2>/dev/null)" 275 | then 276 | b="$(cut -c1-7 $g/HEAD)..." 277 | fi 278 | fi 279 | 280 | if [ -n "$1" ]; then 281 | printf "$1" "${b##refs/heads/}$r" 282 | else 283 | printf " (%s)" "${b##refs/heads/}$r" 284 | fi 285 | fi 286 | } 287 | 288 | __gitcomp () 289 | { 290 | local all c s=$'\n' IFS=' '$'\t'$'\n' 291 | local cur="${COMP_WORDS[COMP_CWORD]}" 292 | if [ $# -gt 2 ]; then 293 | cur="$3" 294 | fi 295 | for c in $1; do 296 | case "$c$4" in 297 | --*=*) all="$all$c$4$s" ;; 298 | *.) all="$all$c$4$s" ;; 299 | *) all="$all$c$4 $s" ;; 300 | esac 301 | done 302 | IFS=$s 303 | COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur")) 304 | return 305 | } 306 | 307 | __git_heads () 308 | { 309 | local cmd i is_hash=y dir="$(__gitdir "$1")" 310 | if [ -d "$dir" ]; then 311 | for i in $(git --git-dir="$dir" \ 312 | for-each-ref --format='%(refname)' \ 313 | refs/heads ); do 314 | echo "${i#refs/heads/}" 315 | done 316 | return 317 | fi 318 | for i in $(git-ls-remote "$1" 2>/dev/null); do 319 | case "$is_hash,$i" in 320 | y,*) is_hash=n ;; 321 | n,*^{}) is_hash=y ;; 322 | n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; 323 | n,*) is_hash=y; echo "$i" ;; 324 | esac 325 | done 326 | } 327 | 328 | __git_tags () 329 | { 330 | local cmd i is_hash=y dir="$(__gitdir "$1")" 331 | if [ -d "$dir" ]; then 332 | for i in $(git --git-dir="$dir" \ 333 | for-each-ref --format='%(refname)' \ 334 | refs/tags ); do 335 | echo "${i#refs/tags/}" 336 | done 337 | return 338 | fi 339 | for i in $(git-ls-remote "$1" 2>/dev/null); do 340 | case "$is_hash,$i" in 341 | y,*) is_hash=n ;; 342 | n,*^{}) is_hash=y ;; 343 | n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; 344 | n,*) is_hash=y; echo "$i" ;; 345 | esac 346 | done 347 | } 348 | 349 | __git_refs () 350 | { 351 | local cmd i is_hash=y dir="$(__gitdir "$1")" 352 | if [ -d "$dir" ]; then 353 | if [ -e "$dir/HEAD" ]; then echo HEAD; fi 354 | for i in $(git --git-dir="$dir" \ 355 | for-each-ref --format='%(refname)' \ 356 | refs/tags refs/heads refs/remotes); do 357 | case "$i" in 358 | refs/tags/*) echo "${i#refs/tags/}" ;; 359 | refs/heads/*) echo "${i#refs/heads/}" ;; 360 | refs/remotes/*) echo "${i#refs/remotes/}" ;; 361 | *) echo "$i" ;; 362 | esac 363 | done 364 | return 365 | fi 366 | for i in $(git-ls-remote "$dir" 2>/dev/null); do 367 | case "$is_hash,$i" in 368 | y,*) is_hash=n ;; 369 | n,*^{}) is_hash=y ;; 370 | n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; 371 | n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; 372 | n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;; 373 | n,*) is_hash=y; echo "$i" ;; 374 | esac 375 | done 376 | } 377 | 378 | __git_refs2 () 379 | { 380 | local i 381 | for i in $(__git_refs "$1"); do 382 | echo "$i:$i" 383 | done 384 | } 385 | 386 | __git_refs_remotes () 387 | { 388 | local cmd i is_hash=y 389 | for i in $(git-ls-remote "$1" 2>/dev/null); do 390 | case "$is_hash,$i" in 391 | n,refs/heads/*) 392 | is_hash=y 393 | echo "$i:refs/remotes/$1/${i#refs/heads/}" 394 | ;; 395 | y,*) is_hash=n ;; 396 | n,*^{}) is_hash=y ;; 397 | n,refs/tags/*) is_hash=y;; 398 | n,*) is_hash=y; ;; 399 | esac 400 | done 401 | } 402 | 403 | __git_remotes () 404 | { 405 | local i ngoff IFS=$'\n' d="$(__gitdir)" 406 | shopt -q nullglob || ngoff=1 407 | shopt -s nullglob 408 | for i in "$d/remotes"/*; do 409 | echo ${i#$d/remotes/} 410 | done 411 | [ "$ngoff" ] && shopt -u nullglob 412 | for i in $(git --git-dir="$d" config --list); do 413 | case "$i" in 414 | remote.*.url=*) 415 | i="${i#remote.}" 416 | echo "${i/.url=*/}" 417 | ;; 418 | esac 419 | done 420 | } 421 | 422 | __git_merge_strategies () 423 | { 424 | if [ -n "$__git_merge_strategylist" ]; then 425 | echo "$__git_merge_strategylist" 426 | return 427 | fi 428 | sed -n "/^all_strategies='/{ 429 | s/^all_strategies='// 430 | s/'// 431 | p 432 | q 433 | }" "$(git --exec-path)/git-merge" 434 | } 435 | __git_merge_strategylist= 436 | __git_merge_strategylist="$(__git_merge_strategies 2>/dev/null)" 437 | 438 | __git_complete_file () 439 | { 440 | local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}" 441 | case "$cur" in 442 | ?*:*) 443 | ref="${cur%%:*}" 444 | cur="${cur#*:}" 445 | case "$cur" in 446 | ?*/*) 447 | pfx="${cur%/*}" 448 | cur="${cur##*/}" 449 | ls="$ref:$pfx" 450 | pfx="$pfx/" 451 | ;; 452 | *) 453 | ls="$ref" 454 | ;; 455 | esac 456 | COMPREPLY=($(compgen -P "$pfx" \ 457 | -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \ 458 | | sed '/^100... blob /s,^.* ,, 459 | /^040000 tree /{ 460 | s,^.* ,, 461 | s,$,/, 462 | } 463 | s/^.* //')" \ 464 | -- "$cur")) 465 | ;; 466 | *) 467 | __gitcomp "$(__git_refs)" 468 | ;; 469 | esac 470 | } 471 | 472 | __git_complete_revlist () 473 | { 474 | local pfx cur="${COMP_WORDS[COMP_CWORD]}" 475 | case "$cur" in 476 | *...*) 477 | pfx="${cur%...*}..." 478 | cur="${cur#*...}" 479 | __gitcomp "$(__git_refs)" "$pfx" "$cur" 480 | ;; 481 | *..*) 482 | pfx="${cur%..*}.." 483 | cur="${cur#*..}" 484 | __gitcomp "$(__git_refs)" "$pfx" "$cur" 485 | ;; 486 | *.) 487 | __gitcomp "$cur." 488 | ;; 489 | *) 490 | __gitcomp "$(__git_refs)" 491 | ;; 492 | esac 493 | } 494 | 495 | __git_commands () 496 | { 497 | if [ -n "$__git_commandlist" ]; then 498 | echo "$__git_commandlist" 499 | return 500 | fi 501 | local i IFS=" "$'\n' 502 | for i in $(git help -a|egrep '^ ') 503 | do 504 | case $i in 505 | *--*) : helper pattern;; 506 | applymbox) : ask gittus;; 507 | applypatch) : ask gittus;; 508 | archimport) : import;; 509 | cat-file) : plumbing;; 510 | check-attr) : plumbing;; 511 | check-ref-format) : plumbing;; 512 | commit-tree) : plumbing;; 513 | cvsexportcommit) : export;; 514 | cvsimport) : import;; 515 | cvsserver) : daemon;; 516 | daemon) : daemon;; 517 | diff-files) : plumbing;; 518 | diff-index) : plumbing;; 519 | diff-tree) : plumbing;; 520 | fast-import) : import;; 521 | fsck-objects) : plumbing;; 522 | fetch-pack) : plumbing;; 523 | fmt-merge-msg) : plumbing;; 524 | for-each-ref) : plumbing;; 525 | hash-object) : plumbing;; 526 | http-*) : transport;; 527 | index-pack) : plumbing;; 528 | init-db) : deprecated;; 529 | local-fetch) : plumbing;; 530 | mailinfo) : plumbing;; 531 | mailsplit) : plumbing;; 532 | merge-*) : plumbing;; 533 | mktree) : plumbing;; 534 | mktag) : plumbing;; 535 | pack-objects) : plumbing;; 536 | pack-redundant) : plumbing;; 537 | pack-refs) : plumbing;; 538 | parse-remote) : plumbing;; 539 | patch-id) : plumbing;; 540 | peek-remote) : plumbing;; 541 | prune) : plumbing;; 542 | prune-packed) : plumbing;; 543 | quiltimport) : import;; 544 | read-tree) : plumbing;; 545 | receive-pack) : plumbing;; 546 | reflog) : plumbing;; 547 | repo-config) : deprecated;; 548 | rerere) : plumbing;; 549 | rev-list) : plumbing;; 550 | rev-parse) : plumbing;; 551 | runstatus) : plumbing;; 552 | sh-setup) : internal;; 553 | shell) : daemon;; 554 | send-pack) : plumbing;; 555 | show-index) : plumbing;; 556 | ssh-*) : transport;; 557 | stripspace) : plumbing;; 558 | svn) : import export;; 559 | symbolic-ref) : plumbing;; 560 | tar-tree) : deprecated;; 561 | unpack-file) : plumbing;; 562 | unpack-objects) : plumbing;; 563 | update-index) : plumbing;; 564 | update-ref) : plumbing;; 565 | update-server-info) : daemon;; 566 | upload-archive) : plumbing;; 567 | upload-pack) : plumbing;; 568 | write-tree) : plumbing;; 569 | verify-tag) : plumbing;; 570 | *) echo $i;; 571 | esac 572 | done 573 | } 574 | __git_commandlist= 575 | __git_commandlist="$(__git_commands 2>/dev/null)" 576 | 577 | __git_aliases () 578 | { 579 | local i IFS=$'\n' 580 | for i in $(git --git-dir="$(__gitdir)" config --list); do 581 | case "$i" in 582 | alias.*) 583 | i="${i#alias.}" 584 | echo "${i/=*/}" 585 | ;; 586 | esac 587 | done 588 | } 589 | 590 | __git_aliased_command () 591 | { 592 | local word cmdline=$(git --git-dir="$(__gitdir)" \ 593 | config --get "alias.$1") 594 | for word in $cmdline; do 595 | if [ "${word##-*}" ]; then 596 | echo $word 597 | return 598 | fi 599 | done 600 | } 601 | 602 | __git_whitespacelist="nowarn warn error error-all strip" 603 | 604 | _git_am () 605 | { 606 | local cur="${COMP_WORDS[COMP_CWORD]}" 607 | if [ -d .dotest ]; then 608 | __gitcomp "--skip --resolved" 609 | return 610 | fi 611 | case "$cur" in 612 | --whitespace=*) 613 | __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" 614 | return 615 | ;; 616 | --*) 617 | __gitcomp " 618 | --signoff --utf8 --binary --3way --interactive 619 | --whitespace= 620 | " 621 | return 622 | esac 623 | COMPREPLY=() 624 | } 625 | 626 | _git_apply () 627 | { 628 | local cur="${COMP_WORDS[COMP_CWORD]}" 629 | case "$cur" in 630 | --whitespace=*) 631 | __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" 632 | return 633 | ;; 634 | --*) 635 | __gitcomp " 636 | --stat --numstat --summary --check --index 637 | --cached --index-info --reverse --reject --unidiff-zero 638 | --apply --no-add --exclude= 639 | --whitespace= --inaccurate-eof --verbose 640 | " 641 | return 642 | esac 643 | COMPREPLY=() 644 | } 645 | 646 | _git_add () 647 | { 648 | local cur="${COMP_WORDS[COMP_CWORD]}" 649 | case "$cur" in 650 | --*) 651 | __gitcomp "--interactive --refresh" 652 | return 653 | esac 654 | COMPREPLY=() 655 | } 656 | 657 | _git_bisect () 658 | { 659 | local i c=1 command 660 | while [ $c -lt $COMP_CWORD ]; do 661 | i="${COMP_WORDS[c]}" 662 | case "$i" in 663 | start|bad|good|reset|visualize|replay|log) 664 | command="$i" 665 | break 666 | ;; 667 | esac 668 | c=$((++c)) 669 | done 670 | 671 | if [ $c -eq $COMP_CWORD -a -z "$command" ]; then 672 | __gitcomp "start bad good reset visualize replay log" 673 | return 674 | fi 675 | 676 | case "$command" in 677 | bad|good|reset) 678 | __gitcomp "$(__git_refs)" 679 | ;; 680 | *) 681 | COMPREPLY=() 682 | ;; 683 | esac 684 | } 685 | 686 | _git_branch () 687 | { 688 | __gitcomp "$(__git_refs)" 689 | } 690 | 691 | _git_bundle () 692 | { 693 | local mycword="$COMP_CWORD" 694 | case "${COMP_WORDS[0]}" in 695 | git) 696 | local cmd="${COMP_WORDS[2]}" 697 | mycword="$((mycword-1))" 698 | ;; 699 | git-bundle*) 700 | local cmd="${COMP_WORDS[1]}" 701 | ;; 702 | esac 703 | case "$mycword" in 704 | 1) 705 | __gitcomp "create list-heads verify unbundle" 706 | ;; 707 | 2) 708 | # looking for a file 709 | ;; 710 | *) 711 | case "$cmd" in 712 | create) 713 | __git_complete_revlist 714 | ;; 715 | esac 716 | ;; 717 | esac 718 | } 719 | 720 | _git_checkout () 721 | { 722 | __gitcomp "$(__git_refs)" 723 | } 724 | 725 | _git_cherry () 726 | { 727 | __gitcomp "$(__git_refs)" 728 | } 729 | 730 | _git_cherry_pick () 731 | { 732 | local cur="${COMP_WORDS[COMP_CWORD]}" 733 | case "$cur" in 734 | --*) 735 | __gitcomp "--edit --no-commit" 736 | ;; 737 | *) 738 | __gitcomp "$(__git_refs)" 739 | ;; 740 | esac 741 | } 742 | 743 | _git_commit () 744 | { 745 | local cur="${COMP_WORDS[COMP_CWORD]}" 746 | case "$cur" in 747 | --*) 748 | __gitcomp " 749 | --all --author= --signoff --verify --no-verify 750 | --edit --amend --include --only 751 | " 752 | return 753 | esac 754 | COMPREPLY=() 755 | } 756 | 757 | _git_describe () 758 | { 759 | __gitcomp "$(__git_refs)" 760 | } 761 | 762 | _git_diff () 763 | { 764 | local cur="${COMP_WORDS[COMP_CWORD]}" 765 | case "$cur" in 766 | --*) 767 | __gitcomp "--cached --stat --numstat --shortstat --summary 768 | --patch-with-stat --name-only --name-status --color 769 | --no-color --color-words --no-renames --check 770 | --full-index --binary --abbrev --diff-filter 771 | --find-copies-harder --pickaxe-all --pickaxe-regex 772 | --text --ignore-space-at-eol --ignore-space-change 773 | --ignore-all-space --exit-code --quiet --ext-diff 774 | --no-ext-diff" 775 | return 776 | ;; 777 | esac 778 | __git_complete_file 779 | } 780 | 781 | _git_diff_tree () 782 | { 783 | __gitcomp "$(__git_refs)" 784 | } 785 | 786 | _git_fetch () 787 | { 788 | local cur="${COMP_WORDS[COMP_CWORD]}" 789 | 790 | case "${COMP_WORDS[0]},$COMP_CWORD" in 791 | git-fetch*,1) 792 | __gitcomp "$(__git_remotes)" 793 | ;; 794 | git,2) 795 | __gitcomp "$(__git_remotes)" 796 | ;; 797 | *) 798 | case "$cur" in 799 | *:*) 800 | __gitcomp "$(__git_refs)" "" "${cur#*:}" 801 | ;; 802 | *) 803 | local remote 804 | case "${COMP_WORDS[0]}" in 805 | git-fetch) remote="${COMP_WORDS[1]}" ;; 806 | git) remote="${COMP_WORDS[2]}" ;; 807 | esac 808 | __gitcomp "$(__git_refs2 "$remote")" 809 | ;; 810 | esac 811 | ;; 812 | esac 813 | } 814 | 815 | _git_format_patch () 816 | { 817 | local cur="${COMP_WORDS[COMP_CWORD]}" 818 | case "$cur" in 819 | --*) 820 | __gitcomp " 821 | --stdout --attach --thread 822 | --output-directory 823 | --numbered --start-number 824 | --numbered-files 825 | --keep-subject 826 | --signoff 827 | --in-reply-to= 828 | --full-index --binary 829 | --not --all 830 | " 831 | return 832 | ;; 833 | esac 834 | __git_complete_revlist 835 | } 836 | 837 | _git_gc () 838 | { 839 | local cur="${COMP_WORDS[COMP_CWORD]}" 840 | case "$cur" in 841 | --*) 842 | __gitcomp "--prune --aggressive" 843 | return 844 | ;; 845 | esac 846 | COMPREPLY=() 847 | } 848 | 849 | _git_ls_remote () 850 | { 851 | __gitcomp "$(__git_remotes)" 852 | } 853 | 854 | _git_ls_tree () 855 | { 856 | __git_complete_file 857 | } 858 | 859 | _git_log () 860 | { 861 | local cur="${COMP_WORDS[COMP_CWORD]}" 862 | case "$cur" in 863 | --pretty=*) 864 | __gitcomp " 865 | oneline short medium full fuller email raw 866 | " "" "${cur##--pretty=}" 867 | return 868 | ;; 869 | --date=*) 870 | __gitcomp " 871 | relative iso8601 rfc2822 short local default 872 | " "" "${cur##--date=}" 873 | return 874 | ;; 875 | --*) 876 | __gitcomp " 877 | --max-count= --max-age= --since= --after= 878 | --min-age= --before= --until= 879 | --root --topo-order --date-order --reverse 880 | --no-merges --follow 881 | --abbrev-commit --abbrev= 882 | --relative-date --date= 883 | --author= --committer= --grep= 884 | --all-match 885 | --pretty= --name-status --name-only --raw 886 | --not --all 887 | --left-right --cherry-pick 888 | " 889 | return 890 | ;; 891 | esac 892 | __git_complete_revlist 893 | } 894 | 895 | _git_merge () 896 | { 897 | local cur="${COMP_WORDS[COMP_CWORD]}" 898 | case "${COMP_WORDS[COMP_CWORD-1]}" in 899 | -s|--strategy) 900 | __gitcomp "$(__git_merge_strategies)" 901 | return 902 | esac 903 | case "$cur" in 904 | --strategy=*) 905 | __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" 906 | return 907 | ;; 908 | --*) 909 | __gitcomp " 910 | --no-commit --no-summary --squash --strategy 911 | " 912 | return 913 | esac 914 | __gitcomp "$(__git_refs)" 915 | } 916 | 917 | _git_merge_base () 918 | { 919 | __gitcomp "$(__git_refs)" 920 | } 921 | 922 | _git_name_rev () 923 | { 924 | __gitcomp "--tags --all --stdin" 925 | } 926 | 927 | _git_pull () 928 | { 929 | local cur="${COMP_WORDS[COMP_CWORD]}" 930 | 931 | case "${COMP_WORDS[0]},$COMP_CWORD" in 932 | git-pull*,1) 933 | __gitcomp "$(__git_remotes)" 934 | ;; 935 | git,2) 936 | __gitcomp "$(__git_remotes)" 937 | ;; 938 | *) 939 | local remote 940 | case "${COMP_WORDS[0]}" in 941 | git-pull) remote="${COMP_WORDS[1]}" ;; 942 | git) remote="${COMP_WORDS[2]}" ;; 943 | esac 944 | __gitcomp "$(__git_refs "$remote")" 945 | ;; 946 | esac 947 | } 948 | 949 | _git_push () 950 | { 951 | local cur="${COMP_WORDS[COMP_CWORD]}" 952 | 953 | case "${COMP_WORDS[0]},$COMP_CWORD" in 954 | git-push*,1) 955 | __gitcomp "$(__git_remotes)" 956 | ;; 957 | git,2) 958 | __gitcomp "$(__git_remotes)" 959 | ;; 960 | *) 961 | case "$cur" in 962 | *:*) 963 | local remote 964 | case "${COMP_WORDS[0]}" in 965 | git-push) remote="${COMP_WORDS[1]}" ;; 966 | git) remote="${COMP_WORDS[2]}" ;; 967 | esac 968 | __gitcomp "$(__git_refs "$remote")" "" "${cur#*:}" 969 | ;; 970 | +*) 971 | __gitcomp "$(__git_refs)" + "${cur#+}" 972 | ;; 973 | *) 974 | __gitcomp "$(__git_refs)" 975 | ;; 976 | esac 977 | ;; 978 | esac 979 | } 980 | 981 | _git_rebase () 982 | { 983 | local cur="${COMP_WORDS[COMP_CWORD]}" 984 | if [ -d .dotest ] || [ -d .git/.dotest-merge ]; then 985 | __gitcomp "--continue --skip --abort" 986 | return 987 | fi 988 | case "${COMP_WORDS[COMP_CWORD-1]}" in 989 | -s|--strategy) 990 | __gitcomp "$(__git_merge_strategies)" 991 | return 992 | esac 993 | case "$cur" in 994 | --strategy=*) 995 | __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" 996 | return 997 | ;; 998 | --*) 999 | __gitcomp "--onto --merge --strategy" 1000 | return 1001 | esac 1002 | __gitcomp "$(__git_refs)" 1003 | } 1004 | 1005 | _git_config () 1006 | { 1007 | local cur="${COMP_WORDS[COMP_CWORD]}" 1008 | local prv="${COMP_WORDS[COMP_CWORD-1]}" 1009 | case "$prv" in 1010 | branch.*.remote) 1011 | __gitcomp "$(__git_remotes)" 1012 | return 1013 | ;; 1014 | branch.*.merge) 1015 | __gitcomp "$(__git_refs)" 1016 | return 1017 | ;; 1018 | remote.*.fetch) 1019 | local remote="${prv#remote.}" 1020 | remote="${remote%.fetch}" 1021 | __gitcomp "$(__git_refs_remotes "$remote")" 1022 | return 1023 | ;; 1024 | remote.*.push) 1025 | local remote="${prv#remote.}" 1026 | remote="${remote%.push}" 1027 | __gitcomp "$(git --git-dir="$(__gitdir)" \ 1028 | for-each-ref --format='%(refname):%(refname)' \ 1029 | refs/heads)" 1030 | return 1031 | ;; 1032 | pull.twohead|pull.octopus) 1033 | __gitcomp "$(__git_merge_strategies)" 1034 | return 1035 | ;; 1036 | color.branch|color.diff|color.status) 1037 | __gitcomp "always never auto" 1038 | return 1039 | ;; 1040 | color.*.*) 1041 | __gitcomp " 1042 | black red green yellow blue magenta cyan white 1043 | bold dim ul blink reverse 1044 | " 1045 | return 1046 | ;; 1047 | *.*) 1048 | COMPREPLY=() 1049 | return 1050 | ;; 1051 | esac 1052 | case "$cur" in 1053 | --*) 1054 | __gitcomp " 1055 | --global --system --file= 1056 | --list --replace-all 1057 | --get --get-all --get-regexp 1058 | --add --unset --unset-all 1059 | --remove-section --rename-section 1060 | " 1061 | return 1062 | ;; 1063 | branch.*.*) 1064 | local pfx="${cur%.*}." 1065 | cur="${cur##*.}" 1066 | __gitcomp "remote merge" "$pfx" "$cur" 1067 | return 1068 | ;; 1069 | branch.*) 1070 | local pfx="${cur%.*}." 1071 | cur="${cur#*.}" 1072 | __gitcomp "$(__git_heads)" "$pfx" "$cur" "." 1073 | return 1074 | ;; 1075 | remote.*.*) 1076 | local pfx="${cur%.*}." 1077 | cur="${cur##*.}" 1078 | __gitcomp " 1079 | url fetch push skipDefaultUpdate 1080 | receivepack uploadpack tagopt 1081 | " "$pfx" "$cur" 1082 | return 1083 | ;; 1084 | remote.*) 1085 | local pfx="${cur%.*}." 1086 | cur="${cur#*.}" 1087 | __gitcomp "$(__git_remotes)" "$pfx" "$cur" "." 1088 | return 1089 | ;; 1090 | esac 1091 | __gitcomp " 1092 | apply.whitespace 1093 | core.fileMode 1094 | core.gitProxy 1095 | core.ignoreStat 1096 | core.preferSymlinkRefs 1097 | core.logAllRefUpdates 1098 | core.loosecompression 1099 | core.repositoryFormatVersion 1100 | core.sharedRepository 1101 | core.warnAmbiguousRefs 1102 | core.compression 1103 | core.legacyHeaders 1104 | core.packedGitWindowSize 1105 | core.packedGitLimit 1106 | clean.requireForce 1107 | color.branch 1108 | color.branch.current 1109 | color.branch.local 1110 | color.branch.remote 1111 | color.branch.plain 1112 | color.diff 1113 | color.diff.plain 1114 | color.diff.meta 1115 | color.diff.frag 1116 | color.diff.old 1117 | color.diff.new 1118 | color.diff.commit 1119 | color.diff.whitespace 1120 | color.pager 1121 | color.status 1122 | color.status.header 1123 | color.status.added 1124 | color.status.changed 1125 | color.status.untracked 1126 | diff.renameLimit 1127 | diff.renames 1128 | fetch.unpackLimit 1129 | format.headers 1130 | format.subjectprefix 1131 | gitcvs.enabled 1132 | gitcvs.logfile 1133 | gitcvs.allbinary 1134 | gitcvs.dbname gitcvs.dbdriver gitcvs.dbuser gitcvs.dvpass 1135 | gc.packrefs 1136 | gc.reflogexpire 1137 | gc.reflogexpireunreachable 1138 | gc.rerereresolved 1139 | gc.rerereunresolved 1140 | http.sslVerify 1141 | http.sslCert 1142 | http.sslKey 1143 | http.sslCAInfo 1144 | http.sslCAPath 1145 | http.maxRequests 1146 | http.lowSpeedLimit 1147 | http.lowSpeedTime 1148 | http.noEPSV 1149 | i18n.commitEncoding 1150 | i18n.logOutputEncoding 1151 | log.showroot 1152 | merge.tool 1153 | merge.summary 1154 | merge.verbosity 1155 | pack.window 1156 | pack.depth 1157 | pack.windowMemory 1158 | pack.compression 1159 | pack.deltaCacheSize 1160 | pack.deltaCacheLimit 1161 | pull.octopus 1162 | pull.twohead 1163 | repack.useDeltaBaseOffset 1164 | show.difftree 1165 | showbranch.default 1166 | tar.umask 1167 | transfer.unpackLimit 1168 | receive.unpackLimit 1169 | receive.denyNonFastForwards 1170 | user.name 1171 | user.email 1172 | user.signingkey 1173 | whatchanged.difftree 1174 | branch. remote. 1175 | " 1176 | } 1177 | 1178 | _git_remote () 1179 | { 1180 | local i c=1 command 1181 | while [ $c -lt $COMP_CWORD ]; do 1182 | i="${COMP_WORDS[c]}" 1183 | case "$i" in 1184 | add|rm|show|prune|update) command="$i"; break ;; 1185 | esac 1186 | c=$((++c)) 1187 | done 1188 | 1189 | if [ $c -eq $COMP_CWORD -a -z "$command" ]; then 1190 | __gitcomp "add rm show prune update" 1191 | return 1192 | fi 1193 | 1194 | case "$command" in 1195 | rm|show|prune) 1196 | __gitcomp "$(__git_remotes)" 1197 | ;; 1198 | update) 1199 | local i c='' IFS=$'\n' 1200 | for i in $(git --git-dir="$(__gitdir)" config --list); do 1201 | case "$i" in 1202 | remotes.*) 1203 | i="${i#remotes.}" 1204 | c="$c ${i/=*/}" 1205 | ;; 1206 | esac 1207 | done 1208 | __gitcomp "$c" 1209 | ;; 1210 | *) 1211 | COMPREPLY=() 1212 | ;; 1213 | esac 1214 | } 1215 | 1216 | _git_reset () 1217 | { 1218 | local cur="${COMP_WORDS[COMP_CWORD]}" 1219 | case "$cur" in 1220 | --*) 1221 | __gitcomp "--mixed --hard --soft" 1222 | return 1223 | ;; 1224 | esac 1225 | __gitcomp "$(__git_refs)" 1226 | } 1227 | 1228 | _git_shortlog () 1229 | { 1230 | local cur="${COMP_WORDS[COMP_CWORD]}" 1231 | case "$cur" in 1232 | --*) 1233 | __gitcomp " 1234 | --max-count= --max-age= --since= --after= 1235 | --min-age= --before= --until= 1236 | --no-merges 1237 | --author= --committer= --grep= 1238 | --all-match 1239 | --not --all 1240 | --numbered --summary 1241 | " 1242 | return 1243 | ;; 1244 | esac 1245 | __git_complete_revlist 1246 | } 1247 | 1248 | _git_show () 1249 | { 1250 | local cur="${COMP_WORDS[COMP_CWORD]}" 1251 | case "$cur" in 1252 | --pretty=*) 1253 | __gitcomp " 1254 | oneline short medium full fuller email raw 1255 | " "" "${cur##--pretty=}" 1256 | return 1257 | ;; 1258 | --*) 1259 | __gitcomp "--pretty=" 1260 | return 1261 | ;; 1262 | esac 1263 | __git_complete_file 1264 | } 1265 | 1266 | _git_stash () 1267 | { 1268 | __gitcomp 'list show apply clear' 1269 | } 1270 | 1271 | _git_submodule () 1272 | { 1273 | local i c=1 command 1274 | while [ $c -lt $COMP_CWORD ]; do 1275 | i="${COMP_WORDS[c]}" 1276 | case "$i" in 1277 | add|status|init|update) command="$i"; break ;; 1278 | esac 1279 | c=$((++c)) 1280 | done 1281 | 1282 | if [ $c -eq $COMP_CWORD -a -z "$command" ]; then 1283 | local cur="${COMP_WORDS[COMP_CWORD]}" 1284 | case "$cur" in 1285 | --*) 1286 | __gitcomp "--quiet --cached" 1287 | ;; 1288 | *) 1289 | __gitcomp "add status init update" 1290 | ;; 1291 | esac 1292 | return 1293 | fi 1294 | } 1295 | 1296 | _git_tag () 1297 | { 1298 | local i c=1 f=0 1299 | while [ $c -lt $COMP_CWORD ]; do 1300 | i="${COMP_WORDS[c]}" 1301 | case "$i" in 1302 | -d|-v) 1303 | __gitcomp "$(__git_tags)" 1304 | return 1305 | ;; 1306 | -f) 1307 | f=1 1308 | ;; 1309 | esac 1310 | c=$((++c)) 1311 | done 1312 | 1313 | case "${COMP_WORDS[COMP_CWORD-1]}" in 1314 | -m|-F) 1315 | COMPREPLY=() 1316 | ;; 1317 | -*|tag|git-tag) 1318 | if [ $f = 1 ]; then 1319 | __gitcomp "$(__git_tags)" 1320 | else 1321 | COMPREPLY=() 1322 | fi 1323 | ;; 1324 | *) 1325 | __gitcomp "$(__git_refs)" 1326 | ;; 1327 | esac 1328 | } 1329 | 1330 | _git () 1331 | { 1332 | local i c=1 command __git_dir 1333 | 1334 | while [ $c -lt $COMP_CWORD ]; do 1335 | i="${COMP_WORDS[c]}" 1336 | case "$i" in 1337 | --git-dir=*) __git_dir="${i#--git-dir=}" ;; 1338 | --bare) __git_dir="." ;; 1339 | --version|--help|-p|--paginate) ;; 1340 | *) command="$i"; break ;; 1341 | esac 1342 | c=$((++c)) 1343 | done 1344 | 1345 | if [ $c -eq $COMP_CWORD -a -z "$command" ]; then 1346 | case "${COMP_WORDS[COMP_CWORD]}" in 1347 | --*=*) COMPREPLY=() ;; 1348 | --*) __gitcomp " 1349 | --no-pager 1350 | --git-dir= 1351 | --bare 1352 | --version 1353 | --exec-path 1354 | " 1355 | ;; 1356 | *) __gitcomp "$(__git_commands) $(__git_aliases)" ;; 1357 | esac 1358 | return 1359 | fi 1360 | 1361 | local expansion=$(__git_aliased_command "$command") 1362 | [ "$expansion" ] && command="$expansion" 1363 | 1364 | case "$command" in 1365 | am) _git_am ;; 1366 | add) _git_add ;; 1367 | apply) _git_apply ;; 1368 | bisect) _git_bisect ;; 1369 | bundle) _git_bundle ;; 1370 | branch) _git_branch ;; 1371 | checkout) _git_checkout ;; 1372 | cherry) _git_cherry ;; 1373 | cherry-pick) _git_cherry_pick ;; 1374 | commit) _git_commit ;; 1375 | config) _git_config ;; 1376 | describe) _git_describe ;; 1377 | diff) _git_diff ;; 1378 | fetch) _git_fetch ;; 1379 | format-patch) _git_format_patch ;; 1380 | gc) _git_gc ;; 1381 | log) _git_log ;; 1382 | ls-remote) _git_ls_remote ;; 1383 | ls-tree) _git_ls_tree ;; 1384 | merge) _git_merge;; 1385 | merge-base) _git_merge_base ;; 1386 | name-rev) _git_name_rev ;; 1387 | pull) _git_pull ;; 1388 | push) _git_push ;; 1389 | rebase) _git_rebase ;; 1390 | remote) _git_remote ;; 1391 | reset) _git_reset ;; 1392 | shortlog) _git_shortlog ;; 1393 | show) _git_show ;; 1394 | show-branch) _git_log ;; 1395 | stash) _git_stash ;; 1396 | submodule) _git_submodule ;; 1397 | tag) _git_tag ;; 1398 | whatchanged) _git_log ;; 1399 | *) COMPREPLY=() ;; 1400 | esac 1401 | } 1402 | 1403 | _gitk () 1404 | { 1405 | local cur="${COMP_WORDS[COMP_CWORD]}" 1406 | case "$cur" in 1407 | --*) 1408 | __gitcomp "--not --all" 1409 | return 1410 | ;; 1411 | esac 1412 | __git_complete_revlist 1413 | } 1414 | 1415 | complete -o default -o nospace -F _git git 1416 | complete -o default -o nospace -F _gitk gitk 1417 | complete -o default -o nospace -F _git_am git-am 1418 | complete -o default -o nospace -F _git_apply git-apply 1419 | complete -o default -o nospace -F _git_bisect git-bisect 1420 | complete -o default -o nospace -F _git_branch git-branch 1421 | complete -o default -o nospace -F _git_bundle git-bundle 1422 | complete -o default -o nospace -F _git_checkout git-checkout 1423 | complete -o default -o nospace -F _git_cherry git-cherry 1424 | complete -o default -o nospace -F _git_cherry_pick git-cherry-pick 1425 | complete -o default -o nospace -F _git_commit git-commit 1426 | complete -o default -o nospace -F _git_describe git-describe 1427 | complete -o default -o nospace -F _git_diff git-diff 1428 | complete -o default -o nospace -F _git_fetch git-fetch 1429 | complete -o default -o nospace -F _git_format_patch git-format-patch 1430 | complete -o default -o nospace -F _git_gc git-gc 1431 | complete -o default -o nospace -F _git_log git-log 1432 | complete -o default -o nospace -F _git_ls_remote git-ls-remote 1433 | complete -o default -o nospace -F _git_ls_tree git-ls-tree 1434 | complete -o default -o nospace -F _git_merge git-merge 1435 | complete -o default -o nospace -F _git_merge_base git-merge-base 1436 | complete -o default -o nospace -F _git_name_rev git-name-rev 1437 | complete -o default -o nospace -F _git_pull git-pull 1438 | complete -o default -o nospace -F _git_push git-push 1439 | complete -o default -o nospace -F _git_rebase git-rebase 1440 | complete -o default -o nospace -F _git_config git-config 1441 | complete -o default -o nospace -F _git_remote git-remote 1442 | complete -o default -o nospace -F _git_reset git-reset 1443 | complete -o default -o nospace -F _git_shortlog git-shortlog 1444 | complete -o default -o nospace -F _git_show git-show 1445 | complete -o default -o nospace -F _git_stash git-stash 1446 | complete -o default -o nospace -F _git_submodule git-submodule 1447 | complete -o default -o nospace -F _git_log git-show-branch 1448 | complete -o default -o nospace -F _git_tag git-tag 1449 | complete -o default -o nospace -F _git_log git-whatchanged 1450 | 1451 | # The following are necessary only for Cygwin, and only are needed 1452 | # when the user has tab-completed the executable name and consequently 1453 | # included the '.exe' suffix. 1454 | # 1455 | if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then 1456 | complete -o default -o nospace -F _git_add git-add.exe 1457 | complete -o default -o nospace -F _git_apply git-apply.exe 1458 | complete -o default -o nospace -F _git git.exe 1459 | complete -o default -o nospace -F _git_branch git-branch.exe 1460 | complete -o default -o nospace -F _git_bundle git-bundle.exe 1461 | complete -o default -o nospace -F _git_cherry git-cherry.exe 1462 | complete -o default -o nospace -F _git_describe git-describe.exe 1463 | complete -o default -o nospace -F _git_diff git-diff.exe 1464 | complete -o default -o nospace -F _git_format_patch git-format-patch.exe 1465 | complete -o default -o nospace -F _git_log git-log.exe 1466 | complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe 1467 | complete -o default -o nospace -F _git_merge_base git-merge-base.exe 1468 | complete -o default -o nospace -F _git_name_rev git-name-rev.exe 1469 | complete -o default -o nospace -F _git_push git-push.exe 1470 | complete -o default -o nospace -F _git_config git-config 1471 | complete -o default -o nospace -F _git_shortlog git-shortlog.exe 1472 | complete -o default -o nospace -F _git_show git-show.exe 1473 | complete -o default -o nospace -F _git_log git-show-branch.exe 1474 | complete -o default -o nospace -F _git_tag git-tag.exe 1475 | complete -o default -o nospace -F _git_log git-whatchanged.exe 1476 | fi 1477 | 1478 | # CONFIG ============================================================== 1479 | 1480 | # source the user's rc file: 1481 | [ -r ~/.gitshrc ] && . ~/.gitshrc 1482 | -------------------------------------------------------------------------------- /git-show-merges: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ## git-show-merges: a simple script to show you which topic branches have 4 | ## been merged into the current branch, and which haven't. (Or, specify 5 | ## the set of merge branches you're interested in on the command line.) 6 | ## 7 | ## git-show-merges Copyright 2008 William Morgan . 8 | ## This program is free software: you can redistribute it and/or modify 9 | ## it under the terms of the GNU General Public License as published by 10 | ## the Free Software Foundation, either version 3 of the License, or (at 11 | ## your option) any later version. 12 | ## 13 | ## This program is distributed in the hope that it will be useful, 14 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ## GNU General Public License for more details. 17 | ## 18 | ## You can find the GNU General Public License at: 19 | ## http://www.gnu.org/licenses/ 20 | heads = if ARGV.empty? 21 | [`git symbolic-ref HEAD`.chomp] 22 | else 23 | ARGV 24 | end.map { |r| r.gsub(/refs\/heads\//, "") } 25 | 26 | branches = `git show-ref --heads`. 27 | scan(/^\S+ refs\/heads\/(\S+)$/). 28 | map { |a| a.first } 29 | 30 | unknown = heads - branches 31 | unless unknown.empty? 32 | $stderr.puts "Unknown branch: #{unknown.first}" 33 | exit(-1) 34 | end 35 | 36 | branches -= heads 37 | 38 | heads.each do |h| 39 | merged = branches.select { |b| `git log #{h}..#{b}` == "" } 40 | unmerged = branches - merged 41 | 42 | puts "merged into #{h}:" 43 | merged.each { |b| puts " #{b}" } 44 | puts 45 | puts "not merged into #{h}: " 46 | unmerged.each { |b| puts " #{b}" } 47 | 48 | puts 49 | end 50 | -------------------------------------------------------------------------------- /git-signed-tag: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git tag -s -m "$1" $1 $2 4 | -------------------------------------------------------------------------------- /git-snapshot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | (git stash && git stash apply) > /dev/null 3 | -------------------------------------------------------------------------------- /git-switch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "${1}" == "" ]; then 4 | echo "git-switch " 5 | fi 6 | 7 | 8 | NEW_BRANCH=${1} 9 | CURRENT_BRANCH=$(git branch &>/dev/null; if [ $? -eq 0 ]; then echo "$(git branch | grep '^*' |sed s/\*\ //)"; fi) 10 | 11 | git stash save autostash &>/dev/null 12 | git checkout ${NEW_BRANCH} 13 | 14 | if [ "$?" == "1" ]; then 15 | git branch ${NEW_BRANCH} 16 | git checkout ${NEW_BRANCH} 17 | fi 18 | 19 | AUTOSTASH=$(git stash list | grep "${NEW_BRANCH}: autostash" | tail -n 1 | cut -d":" -f1) 20 | 21 | if [ "${AUTOSTASH}" != "" ]; then 22 | git stash apply ${AUTOSTASH} && git stash drop ${AUTOSTASH} 23 | fi -------------------------------------------------------------------------------- /git-sync: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIRS="$@" 4 | if [[ -z "$DIRS" ]]; then 5 | DIRS="." 6 | fi 7 | 8 | find "$DIRS" \( -name .git -o -name '*.git' \) -type d | \ 9 | while read repo_dir 10 | do 11 | if [[ -f "$repo_dir"/config ]] 12 | then 13 | # If this is a git-svn repo, use git svn fetch 14 | if grep -q '^\[svn-remote ' "$repo_dir"/config 15 | then 16 | echo git svn fetch: $repo_dir 17 | GIT_DIR="$repo_dir" git svn fetch 18 | 19 | # If this is a gc-utils repo, use gc-utils update 20 | elif grep -q '^\[gc-utils\]' "$repo_dir"/config 21 | then 22 | echo gc-utils update: $repo_dir 23 | (cd "$repo_dir"; gc-utils update) 24 | 25 | else 26 | for remote in $(GIT_DIR="$repo_dir" git remote) 27 | do 28 | if [[ $remote != mirror ]]; then 29 | echo git fetch: $repo_dir -- $remote 30 | GIT_DIR="$repo_dir" git fetch $remote 31 | fi 32 | done 33 | fi 34 | 35 | for remote in $(GIT_DIR="$repo_dir" git remote) 36 | do 37 | if [[ $remote == mirror ]]; then 38 | echo git push: $repo_dir -- $remote 39 | GIT_DIR="$repo_dir" git push --mirror $remote 40 | fi 41 | done 42 | fi 43 | done 44 | -------------------------------------------------------------------------------- /git-trash: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git commit -a -m "Abandoned changes" && git reset --hard HEAD^ 4 | -------------------------------------------------------------------------------- /git-unpack: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir /tmp/tmpgit.$$ 4 | GIT_DIR=/tmp/tmpgit.$$ git init 5 | 6 | for pack in .git/objects/pack/*.pack; do 7 | GIT_DIR=/tmp/tmpgit.$$ git unpack-objects < $pack 8 | done 9 | 10 | rsync -a --delete /tmp/tmpgit.$$/objects/ .git/objects/ 11 | 12 | rm -fr /tmp/tmpgit.$$ 13 | -------------------------------------------------------------------------------- /git-unpack-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cp .gitconfig .git/config 3 | -------------------------------------------------------------------------------- /git-unwip: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git reset head^ 3 | -------------------------------------------------------------------------------- /git-update: -------------------------------------------------------------------------------- 1 | git stash && git pull && git stash apply 2 | -------------------------------------------------------------------------------- /git-wip: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git addremove 3 | git commit -m "wip" 4 | -------------------------------------------------------------------------------- /git-wt-add: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ## git-wt-add: A darcs-style interactive staging script for git. As the 4 | ## name implies, git-wt-add walks you through unstaged changes on a 5 | ## hunk-by-hunk basis and allows you to pick the ones you'd like staged. 6 | ## 7 | ## git-wt-add Copyright 2007 William Morgan . 8 | ## This program is free software: you can redistribute it and/or modify 9 | ## it under the terms of the GNU General Public License as published by 10 | ## the Free Software Foundation, either version 3 of the License, or 11 | ## (at your option) any later version. 12 | ## 13 | ## This program is distributed in the hope that it will be useful, 14 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ## GNU General Public License for more details. 17 | ## 18 | ## You can find the GNU General Public License at: 19 | ## http://www.gnu.org/licenses/ 20 | 21 | COLOR = /\e\[\d*m/ 22 | 23 | class Hunk 24 | attr_reader :file, :file_header, :diff 25 | attr_accessor :disposition 26 | 27 | def initialize file, file_header, diff 28 | @file = file 29 | @file_header = file_header 30 | @diff = diff 31 | @disposition = :unknown 32 | end 33 | 34 | def self.make_from diff 35 | ret = [] 36 | state = :outside 37 | file_header = hunk = file = nil 38 | 39 | diff.each do |l| # a little state machine to parse git diff output 40 | reprocess = false 41 | begin 42 | reprocess = false 43 | case 44 | when state == :outside && l =~ /^(#{COLOR})*diff --git a\/(.+) b\/(\2)/ 45 | file = $2 46 | file_header = "" 47 | when state == :outside && l =~ /^(#{COLOR})*index / 48 | when state == :outside && l =~ /^(#{COLOR})*(---|\+\+\+) / 49 | file_header += l + "\n" 50 | when state == :outside && l =~ /^(#{COLOR})*@@ / 51 | state = :in_hunk 52 | hunk = l + "\n" 53 | when state == :in_hunk && l =~ /^(#{COLOR})*(@@ |diff --git a)/ 54 | ret << Hunk.new(file, file_header, hunk) 55 | state = :outside 56 | reprocess = true 57 | when state == :in_hunk 58 | hunk += l + "\n" 59 | else 60 | raise "unparsable diff input: #{l.inspect}" 61 | end 62 | end while reprocess 63 | end 64 | 65 | ## add the final hunk 66 | ret << Hunk.new(file, file_header, hunk) if hunk 67 | 68 | ret 69 | end 70 | end 71 | 72 | def help 73 | puts <: accept the current default (which is capitalized) 91 | EOS 92 | end 93 | 94 | def walk_through hunks 95 | skip_files, record_files = {}, {} 96 | skip_rest = record_rest = false 97 | 98 | while hunks.any? { |h| h.disposition == :unknown } 99 | pos = 0 100 | until pos >= hunks.length 101 | h = hunks[pos] 102 | if h.disposition != :unknown 103 | pos += 1 104 | next 105 | elsif skip_rest || skip_files[h.file] 106 | h.disposition = :ignore 107 | pos += 1 108 | next 109 | elsif record_rest || record_files[h.file] 110 | h.disposition = :record 111 | pos += 1 112 | next 113 | end 114 | 115 | puts "Hunk from #{h.file}" 116 | puts h.diff 117 | print "Shall I stage this change? (#{pos + 1}/#{hunks.length}) [ynWsfqadk], or ? for help: " 118 | c = $stdin.getc 119 | puts 120 | case c 121 | when ?y: h.disposition = :record 122 | when ?n: h.disposition = :ignore 123 | when ?w, ?\ : h.disposition = :unknown 124 | when ?s 125 | h.disposition = :ignore 126 | skip_files[h.file] = true 127 | when ?f 128 | h.disposition = :record 129 | record_files[h.file] = true 130 | when ?d: skip_rest = true 131 | when ?a: record_rest = true 132 | when ?q: exit 133 | when ?k 134 | if pos > 0 135 | hunks[pos - 1].disposition = :unknown 136 | pos -= 2 # double-bah 137 | end 138 | else 139 | help 140 | pos -= 1 # bah 141 | end 142 | 143 | pos += 1 144 | puts 145 | end 146 | end 147 | end 148 | 149 | def make_patch hunks 150 | patch = "" 151 | did_header = {} 152 | hunks.each do |h| 153 | next unless h.disposition == :record 154 | unless did_header[h.file] 155 | patch += h.file_header 156 | did_header[h.file] = true 157 | end 158 | patch += h.diff 159 | end 160 | 161 | patch.gsub COLOR, "" 162 | end 163 | 164 | ### execution starts here ### 165 | 166 | diff = `git diff`.split(/\r?\n/) 167 | if diff.empty? 168 | puts "No unstaged changes." 169 | exit 170 | end 171 | hunks = Hunk.make_from diff 172 | 173 | ## unix-centric! 174 | state = `stty -g` 175 | begin 176 | `stty -icanon` # immediate keypress mode 177 | walk_through hunks 178 | ensure 179 | `stty #{state}` 180 | end 181 | 182 | patch = make_patch hunks 183 | if patch.empty? 184 | puts "No changes selected for staging." 185 | else 186 | IO.popen("git apply --cached", "w") { |f| f.puts patch } 187 | puts < 0 ? children.map { |c| c.leaves }.flatten : self 23 | end 24 | end 25 | 26 | def die s 27 | $stderr.puts "Error: #{s}" 28 | exit(-1) 29 | end 30 | 31 | COLOR_ATTRIBUTES = { 32 | 0 => black, 33 | 1 => red, 34 | 2 => green, 35 | 3 => yellow, 36 | 4 => blue, 37 | 5 => magenta, 38 | 6 => cyan, 39 | 7 => dark, 40 | 8 => bold, 41 | 9 => reset 42 | } if HAS_COLOR 43 | 44 | def cputs(string='') 45 | if HAS_COLOR 46 | COLOR_ATTRIBUTES.each { |num, color| string.gsub!("&|#{num}", color) } 47 | else 48 | string.gsub!(/&\|\d/, '') 49 | end 50 | puts string 51 | end 52 | 53 | def run_command(command) 54 | output = IO.popen(command, 'r').read 55 | 56 | if $debug 57 | cputs "-- begin run_command --" 58 | cputs "executing: #{command}" 59 | cputs "output:" 60 | cputs output 61 | cputs "-- end run_command --" 62 | end 63 | 64 | output 65 | end 66 | 67 | def commits_between from, to 68 | commits = run_command(%{ git log --pretty=format:"%h-%ae-%s" #{from}..#{to} }).split(/[\r\n]+/).map do |raw_commit| 69 | raw_commit_parts = raw_commit.split('-') 70 | 71 | { 72 | :hash => raw_commit_parts.shift, 73 | :author => raw_commit_parts.shift.split('@').first, 74 | :message => raw_commit_parts.join('-') 75 | } 76 | end 77 | 78 | max_author_size = commits.map { |c| c[:author].length }.sort.last 79 | 80 | commits.map do |commit| 81 | "[&|2%s&|9] &|8&|4%#{max_author_size}s&|9 %s" % [commit[:hash], commit[:author], commit[:message]] 82 | end 83 | end 84 | 85 | begin 86 | $config = YAML::load_file(CONFIG_FILENAME) 87 | raise 'invalid configuration format' unless $config["version_branches"].length > 0 88 | rescue 89 | # cputs <<-CONFIG.gsub(/^ /, '') 90 | # Create a .git-wtf in your git repository directory that looks like: 91 | # -- 92 | # version_branches: 93 | # - stable-2.0 94 | # CONFIG 95 | # exit 1 96 | $config = { 'version_branches' => [] } 97 | end 98 | 99 | current_branch = File.read(File.join('.git', 'HEAD')).chomp.split('/').last 100 | 101 | $branches = Dir[File.join('.git', 'refs', 'heads', '*')].inject({}) do |hash, ref| 102 | name = File.basename(ref) 103 | rev = File.read(ref).chomp 104 | current = (name == current_branch) 105 | version = $config["version_branches"].include?(name) 106 | remote = run_command("git config --get branch.#{name}.remote").chomp 107 | merge = run_command("git config --get branch.#{name}.merge").chomp.split('/').last 108 | 109 | hash.update({ name => { 110 | :name => name, 111 | :rev => rev, 112 | :current => current, 113 | :version => version, 114 | :merge => { :remote => remote, :branch => merge } 115 | }}) 116 | end 117 | 118 | $remotes = Dir[File.join('.git', 'refs', 'remotes', '*', '*')].inject({}) do |hash, ref| 119 | ref_parts = ref.split('/') 120 | name = ref_parts.pop 121 | remote = ref_parts.pop 122 | rev = File.read(ref).chomp 123 | hash[remote] ||= {} 124 | hash[remote][name] = { :name => name, :remote => remote, :rev => rev } 125 | hash 126 | end 127 | 128 | $branches.each do |name, branch| 129 | $branches[name][:parent] = begin 130 | merge = branch[:merge] 131 | remote = merge[:remote] == '.' ? $branches : $remotes[merge[:remote]] 132 | remote ? remote[merge[:branch]] : nil 133 | end 134 | end 135 | 136 | repo = { 137 | :master => ($branches.values + $remotes['origin'].values).detect { |b| b[:name] == 'master' }, 138 | :current => $branches.values.detect { |b| b[:current] }, 139 | :versions => $branches.values.select { |b| b[:version] }, 140 | :features => $branches.values.select { |b| !(b[:version] || b[:name] == 'master') }, 141 | :remotes => $remotes 142 | } 143 | 144 | def branch_sync_status(local, remote, show_outgoing=true, show_incoming=true, good="in sync", bad="out of sync") 145 | outgoing = show_outgoing ? commits_between(remote[:rev], local[:rev]) : [] 146 | incoming = show_incoming ? commits_between(local[:rev], remote[:rev]) : [] 147 | 148 | sync = (incoming.length == 0 && outgoing.length == 0) 149 | 150 | verb = case 151 | when incoming.length > 0 && 152 | outgoing.length > 0 then 'merge' 153 | when incoming.length > 0 then 'pull' 154 | when outgoing.length > 0 then 'push' 155 | else nil 156 | end 157 | 158 | cputs sync ? "[x] #{good}" : "[ ] #{bad}" 159 | incoming.each { |c| cputs " &|8&|3<&|9 #{c}" } 160 | outgoing.each { |c| cputs " &|8&|3>&|9 #{c}" } 161 | end 162 | 163 | def name(branch) 164 | if $remotes.leaves.include?(branch) 165 | "#{branch[:remote]}/#{branch[:name]}" 166 | else 167 | "#{branch[:name]}" 168 | end 169 | end 170 | 171 | cputs "Local Branch: #{name(repo[:current])}" 172 | branch_sync_status(repo[:current], repo[:current][:parent], true, true, "in sync with remote", "out of sync with remote (#{repo[:current][:parent][:remote]}/#{repo[:current][:parent][:name]})") if repo[:current][:parent] 173 | cputs 174 | 175 | if repo[:current] == repo[:master] 176 | if repo[:versions].length > 0 177 | cputs "Version branches:" 178 | repo[:versions].each do |branch| 179 | branch_sync_status(repo[:current], branch, false, true, "#{branch[:name]} is merged in", "#{branch[:name]} needs to be merged in") 180 | end 181 | cputs 182 | end 183 | elsif repo[:versions].include?(repo[:current]) 184 | master = repo[:master] 185 | cputs "Master Branch: #{name(master)}" 186 | branch_sync_status(repo[:current], master, true, false, "in sync", "#{name(repo[:current])} needs to be merged into master") 187 | cputs 188 | end 189 | 190 | if repo[:features].length > 0 191 | cputs "Feature branches:" 192 | repo[:features].each do |branch| 193 | branch_sync_status(repo[:current], branch, false, true, "#{branch[:name]} is merged in", "#{branch[:name]} needs to be merged in") 194 | end 195 | end --------------------------------------------------------------------------------