ffffb5ed881b8267c61fc0a4f92dc761b32f0e32
   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                (echo -n '*'; cat ) |  # FIXME
 153                git commit-tree "$2" $3  # reads the rest of stdin
 154        ) || die "Can't copy commit $1"
 155}
 156
 157merge_msg()
 158{
 159        dir="$1"
 160        latest_old="$2"
 161        latest_new="$3"
 162        cat <<-EOF
 163                Split '$dir/' into commit '$latest_new'
 164                
 165                git-subtree-dir: $dir
 166                git-subtree-mainline: $latest_old
 167                git-subtree-split: $latest_new
 168        EOF
 169}
 170
 171tree_for_commit()
 172{
 173        git ls-tree "$1" -- "$dir" |
 174        while read mode type tree name; do
 175                assert [ "$name" = "$dir" ]
 176                echo $tree
 177                break
 178        done
 179}
 180
 181tree_changed()
 182{
 183        tree=$1
 184        shift
 185        if [ $# -ne 1 ]; then
 186                return 0   # weird parents, consider it changed
 187        else
 188                ptree=$(tree_for_commit $1)
 189                if [ "$ptree" != "$tree" ]; then
 190                        return 0   # changed
 191                else
 192                        return 1   # not changed
 193                fi
 194        fi
 195}
 196
 197cmd_split()
 198{
 199        debug "Splitting $dir..."
 200        cache_setup || exit $?
 201        
 202        if [ -n "$onto" ]; then
 203                debug "Reading history for --onto=$onto..."
 204                git rev-list $onto |
 205                while read rev; do
 206                        # the 'onto' history is already just the subdir, so
 207                        # any parent we find there can be used verbatim
 208                        debug "  cache: $rev"
 209                        cache_set $rev $rev
 210                done
 211        fi
 212        
 213        unrevs="$(find_existing_splits "$dir" "$revs")"
 214        
 215        debug "git rev-list --reverse $revs $unrevs"
 216        git rev-list --reverse --parents $revs $unrevsx |
 217        while read rev parents; do
 218                debug
 219                debug "Processing commit: $rev"
 220                exists=$(cache_get $rev)
 221                if [ -n "$exists" ]; then
 222                        debug "  prior: $exists"
 223                        continue
 224                fi
 225                debug "  parents: $parents"
 226                newparents=$(cache_get $parents)
 227                debug "  newparents: $newparents"
 228                
 229                tree=$(tree_for_commit $rev)
 230                debug "  tree is: $tree"
 231                [ -z $tree ] && continue
 232
 233                p=""
 234                for parent in $newparents; do
 235                        p="$p -p $parent"
 236                done
 237                        
 238                if tree_changed $tree $parents; then
 239                        newrev=$(copy_commit $rev $tree "$p") || exit $?
 240                else
 241                        newrev="$newparents"
 242                fi
 243                debug "  newrev is: $newrev"
 244                cache_set $rev $newrev
 245                cache_set latest_new $newrev
 246                cache_set latest_old $rev
 247        done || exit $?
 248        latest_new=$(cache_get latest_new)
 249        if [ -z "$latest_new" ]; then
 250                die "No new revisions were found"
 251        fi
 252        
 253        if [ -n "$rejoin" ]; then
 254                debug "Merging split branch into HEAD..."
 255                latest_old=$(cache_get latest_old)
 256                git merge -s ours \
 257                        -m "$(merge_msg $dir $latest_old $latest_new)" \
 258                        $latest_new >&2
 259        fi
 260        echo $latest_new
 261        exit 0
 262}
 263
 264cmd_merge()
 265{
 266        die "merge command not implemented yet"
 267}
 268
 269"cmd_$command"