git-subtree.shon commit Hmm... can't actually filter rev-list on the subdir name. (2c71b7c)
   1#!/bin/bash
   2#
   3# git-subtree.sh: split/join git repositories in subdirectories of this one
   4#
   5# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
   6#
   7OPTS_SPEC="\
   8git subtree split [--rejoin] [--onto rev] <commit...> -- <path>
   9git subtree merge 
  10
  11git subtree does foo and bar!
  12--
  13h,help   show the help
  14q        quiet
  15v        verbose
  16onto=    existing subtree revision to search for parent
  17rejoin   merge the new branch back into HEAD
  18"
  19eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)
  20. git-sh-setup
  21require_work_tree
  22
  23quiet=
  24command=
  25onto=
  26rejoin=
  27
  28debug()
  29{
  30        if [ -z "$quiet" ]; then
  31                echo "$@" >&2
  32        fi
  33}
  34
  35assert()
  36{
  37        if "$@"; then
  38                :
  39        else
  40                die "assertion failed: " "$@"
  41        fi
  42}
  43
  44
  45#echo "Options: $*"
  46
  47while [ $# -gt 0 ]; do
  48        opt="$1"
  49        shift
  50        case "$opt" in
  51                -q) quiet=1 ;;
  52                --onto) onto="$1"; shift ;;
  53                --rejoin) rejoin=1 ;;
  54                --) break ;;
  55        esac
  56done
  57
  58command="$1"
  59shift
  60case "$command" in
  61        split|merge) ;;
  62        *) die "Unknown command '$command'" ;;
  63esac
  64
  65revs=$(git rev-parse --default HEAD --revs-only "$@") || exit $?
  66dirs="$(git rev-parse --sq --no-revs --no-flags "$@")" || exit $?
  67
  68#echo "dirs is {$dirs}"
  69eval $(echo set -- $dirs)
  70if [ "$#" -ne 1 ]; then
  71        die "Must provide exactly one subtree dir (got $#)"
  72fi
  73dir="$1"
  74
  75debug "command: {$command}"
  76debug "quiet: {$quiet}"
  77debug "revs: {$revs}"
  78debug "dir: {$dir}"
  79
  80cache_setup()
  81{
  82        cachedir="$GIT_DIR/subtree-cache/$$"
  83        rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
  84        mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
  85        debug "Using cachedir: $cachedir" >&2
  86}
  87
  88cache_get()
  89{
  90        for oldrev in $*; do
  91                if [ -r "$cachedir/$oldrev" ]; then
  92                        read newrev <"$cachedir/$oldrev"
  93                        echo $newrev
  94                fi
  95        done
  96}
  97
  98cache_set()
  99{
 100        oldrev="$1"
 101        newrev="$2"
 102        if [ "$oldrev" != "latest_old" \
 103             -a "$oldrev" != "latest_new" \
 104             -a -e "$cachedir/$oldrev" ]; then
 105                die "cache for $oldrev already exists!"
 106        fi
 107        echo "$newrev" >"$cachedir/$oldrev"
 108}
 109
 110find_existing_splits()
 111{
 112        debug "Looking for prior splits..."
 113        dir="$1"
 114        revs="$2"
 115        git log --grep="^git-subtree-dir: $dir\$" \
 116                --pretty=format:'%s%n%n%b%nEND' "$revs" |
 117        while read a b junk; do
 118                case "$a" in
 119                        git-subtree-mainline:) main="$b" ;;
 120                        git-subtree-split:) sub="$b" ;;
 121                        *)
 122                                if [ -n "$main" -a -n "$sub" ]; then
 123                                        debug "  Prior: $main -> $sub"
 124                                        cache_set $main $sub
 125                                        echo "^$main^ ^$sub^"
 126                                        main=
 127                                        sub=
 128                                fi
 129                                ;;
 130                esac
 131        done
 132}
 133
 134copy_commit()
 135{
 136        # We're doing to set some environment vars here, so
 137        # do it in a subshell to get rid of them safely later
 138        git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
 139        (
 140                read GIT_AUTHOR_NAME
 141                read GIT_AUTHOR_EMAIL
 142                read GIT_AUTHOR_DATE
 143                read GIT_COMMITTER_NAME
 144                read GIT_COMMITTER_EMAIL
 145                read GIT_COMMITTER_DATE
 146                export  GIT_AUTHOR_NAME \
 147                        GIT_AUTHOR_EMAIL \
 148                        GIT_AUTHOR_DATE \
 149                        GIT_COMMITTER_NAME \
 150                        GIT_COMMITTER_EMAIL \
 151                        GIT_COMMITTER_DATE
 152                git commit-tree "$2" $3  # reads the rest of stdin
 153        ) || die "Can't copy commit $1"
 154}
 155
 156merge_msg()
 157{
 158        dir="$1"
 159        latest_old="$2"
 160        latest_new="$3"
 161        cat <<-EOF
 162                Split '$dir/' into commit '$latest_new'
 163                
 164                git-subtree-dir: $dir
 165                git-subtree-mainline: $latest_old
 166                git-subtree-split: $latest_new
 167        EOF
 168}
 169
 170cmd_split()
 171{
 172        debug "Splitting $dir..."
 173        cache_setup || exit $?
 174        
 175        if [ -n "$onto" ]; then
 176                echo "Reading history for --onto=$onto..."
 177                git rev-list $onto |
 178                while read rev; do
 179                        # the 'onto' history is already just the subdir, so
 180                        # any parent we find there can be used verbatim
 181                        debug "  cache: $rev"
 182                        cache_set $rev $rev
 183                done
 184        fi
 185        
 186        unrevs="$(find_existing_splits "$dir" "$revs")"
 187        
 188        debug "git rev-list --reverse $revs $unrevs"
 189        git rev-list --reverse --parents $revs $unrevsx |
 190        while read rev parents; do
 191                debug
 192                debug "Processing commit: $rev"
 193                exists=$(cache_get $rev)
 194                if [ -n "$exists" ]; then
 195                        debug "  prior: $exists"
 196                        continue
 197                fi
 198                debug "  parents: $parents"
 199                newparents=$(cache_get $parents)
 200                debug "  newparents: $newparents"
 201                
 202                git ls-tree $rev -- "$dir" |
 203                while read mode type tree name; do
 204                        assert [ "$name" = "$dir" ]
 205                        debug "  tree is: $tree"
 206                        p=""
 207                        for parent in $newparents; do
 208                                p="$p -p $parent"
 209                        done
 210                        
 211                        newrev=$(copy_commit $rev $tree "$p") || exit $?
 212                        debug "  newrev is: $newrev"
 213                        cache_set $rev $newrev
 214                        cache_set latest_new $newrev
 215                        cache_set latest_old $rev
 216                done || exit $?
 217        done || exit $?
 218        latest_new=$(cache_get latest_new)
 219        if [ -z "$latest_new" ]; then
 220                die "No new revisions were found"
 221        fi
 222        
 223        if [ -n "$rejoin" ]; then
 224                debug "Merging split branch into HEAD..."
 225                latest_old=$(cache_get latest_old)
 226                git merge -s ours \
 227                        -m "$(merge_msg $dir $latest_old $latest_new)" \
 228                        $latest_new
 229        fi
 230        echo $latest_new
 231        exit 0
 232}
 233
 234cmd_merge()
 235{
 236        die "merge command not implemented yet"
 237}
 238
 239"cmd_$command"