diff options
Diffstat (limited to 'contrib/subtree')
-rw-r--r-- | contrib/subtree/Makefile | 6 | ||||
-rwxr-xr-x | contrib/subtree/git-subtree.sh | 689 | ||||
-rw-r--r-- | contrib/subtree/git-subtree.txt | 2 | ||||
-rw-r--r-- | contrib/subtree/t/Makefile | 31 | ||||
-rwxr-xr-x | contrib/subtree/t/t7900-subtree.sh | 1334 | ||||
-rw-r--r-- | contrib/subtree/todo | 2 |
6 files changed, 1417 insertions, 647 deletions
diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile index 3071baf493..6afa9aafdf 100644 --- a/contrib/subtree/Makefile +++ b/contrib/subtree/Makefile @@ -37,6 +37,7 @@ GIT_SUBTREE_DOC := git-subtree.1 GIT_SUBTREE_XML := git-subtree.xml GIT_SUBTREE_TXT := git-subtree.txt GIT_SUBTREE_HTML := git-subtree.html +GIT_SUBTREE_TEST := ../../git-subtree all:: $(GIT_SUBTREE) @@ -71,7 +72,10 @@ $(GIT_SUBTREE_HTML): $(GIT_SUBTREE_TXT) $(ASCIIDOC) -b xhtml11 -d manpage -f $(ASCIIDOC_CONF) \ -agit_version=$(GIT_VERSION) $^ -test: +$(GIT_SUBTREE_TEST): $(GIT_SUBTREE) + cp $< $@ + +test: $(GIT_SUBTREE_TEST) $(MAKE) -C t/ test clean: diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index fa1a5839af..dec085a235 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -4,8 +4,9 @@ # # Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com> # -if [ $# -eq 0 ]; then - set -- -h +if test $# -eq 0 +then + set -- -h fi OPTS_SPEC="\ git subtree add --prefix=<prefix> <commit> @@ -26,7 +27,7 @@ b,branch= create a new branch from the split subtree ignore-joins ignore prior --rejoin commits onto= try connecting new tree to an existing one rejoin merge the new branch back into HEAD - options for 'add', 'merge', 'pull' and 'push' + options for 'add', 'merge', and 'pull' squash merge subtree changes as a single commit " eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)" @@ -48,82 +49,144 @@ squash= message= prefix= -debug() -{ - if [ -n "$debug" ]; then - echo "$@" >&2 +debug () { + if test -n "$debug" + then + printf "%s\n" "$*" >&2 fi } -say() -{ - if [ -z "$quiet" ]; then - echo "$@" >&2 +say () { + if test -z "$quiet" + then + printf "%s\n" "$*" >&2 fi } -assert() -{ - if "$@"; then - : - else - die "assertion failed: " "$@" +progress () { + if test -z "$quiet" + then + printf "%s\r" "$*" >&2 fi } +assert () { + if ! "$@" + then + die "assertion failed: " "$@" + fi +} -#echo "Options: $*" -while [ $# -gt 0 ]; do +while test $# -gt 0 +do opt="$1" shift + case "$opt" in - -q) quiet=1 ;; - -d) debug=1 ;; - --annotate) annotate="$1"; shift ;; - --no-annotate) annotate= ;; - -b) branch="$1"; shift ;; - -P) prefix="$1"; shift ;; - -m) message="$1"; shift ;; - --no-prefix) prefix= ;; - --onto) onto="$1"; shift ;; - --no-onto) onto= ;; - --rejoin) rejoin=1 ;; - --no-rejoin) rejoin= ;; - --ignore-joins) ignore_joins=1 ;; - --no-ignore-joins) ignore_joins= ;; - --squash) squash=1 ;; - --no-squash) squash= ;; - --) break ;; - *) die "Unexpected option: $opt" ;; + -q) + quiet=1 + ;; + -d) + debug=1 + ;; + --annotate) + annotate="$1" + shift + ;; + --no-annotate) + annotate= + ;; + -b) + branch="$1" + shift + ;; + -P) + prefix="${1%/}" + shift + ;; + -m) + message="$1" + shift + ;; + --no-prefix) + prefix= + ;; + --onto) + onto="$1" + shift + ;; + --no-onto) + onto= + ;; + --rejoin) + rejoin=1 + ;; + --no-rejoin) + rejoin= + ;; + --ignore-joins) + ignore_joins=1 + ;; + --no-ignore-joins) + ignore_joins= + ;; + --squash) + squash=1 + ;; + --no-squash) + squash= + ;; + --) + break + ;; + *) + die "Unexpected option: $opt" + ;; esac done command="$1" shift + case "$command" in - add|merge|pull) default= ;; - split|push) default="--default HEAD" ;; - *) die "Unknown command '$command'" ;; +add|merge|pull) + default= + ;; +split|push) + default="--default HEAD" + ;; +*) + die "Unknown command '$command'" + ;; esac -if [ -z "$prefix" ]; then +if test -z "$prefix" +then die "You must provide the --prefix option." fi case "$command" in - add) [ -e "$prefix" ] && - die "prefix '$prefix' already exists." ;; - *) [ -e "$prefix" ] || - die "'$prefix' does not exist; use 'git subtree add'" ;; +add) + test -e "$prefix" && + die "prefix '$prefix' already exists." + ;; +*) + test -e "$prefix" || + die "'$prefix' does not exist; use 'git subtree add'" + ;; esac dir="$(dirname "$prefix/.")" -if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then +if test "$command" != "pull" && + test "$command" != "add" && + test "$command" != "push" +then revs=$(git rev-parse $default --revs-only "$@") || exit $? - dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $? - if [ -n "$dirs" ]; then + dirs=$(git rev-parse --no-revs --no-flags "$@") || exit $? + if test -n "$dirs" + then die "Error: Use --prefix instead of bare filenames." fi fi @@ -135,78 +198,82 @@ debug "dir: {$dir}" debug "opts: {$*}" debug -cache_setup() -{ +cache_setup () { cachedir="$GIT_DIR/subtree-cache/$$" - rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir" - mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir" - mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree" + rm -rf "$cachedir" || + die "Can't delete old cachedir: $cachedir" + mkdir -p "$cachedir" || + die "Can't create new cachedir: $cachedir" + mkdir -p "$cachedir/notree" || + die "Can't create new cachedir: $cachedir/notree" debug "Using cachedir: $cachedir" >&2 } -cache_get() -{ - for oldrev in $*; do - if [ -r "$cachedir/$oldrev" ]; then +cache_get () { + for oldrev in "$@" + do + if test -r "$cachedir/$oldrev" + then read newrev <"$cachedir/$oldrev" echo $newrev fi done } -cache_miss() -{ - for oldrev in $*; do - if [ ! -r "$cachedir/$oldrev" ]; then +cache_miss () { + for oldrev in "$@" + do + if ! test -r "$cachedir/$oldrev" + then echo $oldrev fi done } -check_parents() -{ - missed=$(cache_miss $*) - for miss in $missed; do - if [ ! -r "$cachedir/notree/$miss" ]; then +check_parents () { + missed=$(cache_miss "$@") + for miss in $missed + do + if ! test -r "$cachedir/notree/$miss" + then debug " incorrect order: $miss" fi done } -set_notree() -{ +set_notree () { echo "1" > "$cachedir/notree/$1" } -cache_set() -{ +cache_set () { oldrev="$1" newrev="$2" - if [ "$oldrev" != "latest_old" \ - -a "$oldrev" != "latest_new" \ - -a -e "$cachedir/$oldrev" ]; then + if test "$oldrev" != "latest_old" && + test "$oldrev" != "latest_new" && + test -e "$cachedir/$oldrev" + then die "cache for $oldrev already exists!" fi echo "$newrev" >"$cachedir/$oldrev" } -rev_exists() -{ - if git rev-parse "$1" >/dev/null 2>&1; then +rev_exists () { + if git rev-parse "$1" >/dev/null 2>&1 + then return 0 else return 1 fi } -rev_is_descendant_of_branch() -{ +rev_is_descendant_of_branch () { newrev="$1" branch="$2" - branch_hash=$(git rev-parse $branch) - match=$(git rev-list -1 $branch_hash ^$newrev) + branch_hash=$(git rev-parse "$branch") + match=$(git rev-list -1 "$branch_hash" "^$newrev") - if [ -z "$match" ]; then + if test -z "$match" + then return 0 else return 1 @@ -216,15 +283,14 @@ rev_is_descendant_of_branch() # if a commit doesn't have a parent, this might not work. But we only want # to remove the parent from the rev-list, and since it doesn't exist, it won't # be there anyway, so do nothing in that case. -try_remove_previous() -{ - if rev_exists "$1^"; then +try_remove_previous () { + if rev_exists "$1^" + then echo "^$1^" fi } -find_latest_squash() -{ +find_latest_squash () { debug "Looking for latest squash ($dir)..." dir="$1" sq= @@ -232,34 +298,43 @@ find_latest_squash() sub= git log --grep="^git-subtree-dir: $dir/*\$" \ --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD | - while read a b junk; do + while read a b junk + do debug "$a $b $junk" debug "{{$sq/$main/$sub}}" case "$a" in - START) sq="$b" ;; - git-subtree-mainline:) main="$b" ;; - git-subtree-split:) sub="$b" ;; - END) - if [ -n "$sub" ]; then - if [ -n "$main" ]; then - # a rejoin commit? - # Pretend its sub was a squash. - sq="$sub" - fi - debug "Squash found: $sq $sub" - echo "$sq" "$sub" - break + START) + sq="$b" + ;; + git-subtree-mainline:) + main="$b" + ;; + git-subtree-split:) + sub="$(git rev-parse "$b^0")" || + die "could not rev-parse split hash $b from commit $sq" + ;; + END) + if test -n "$sub" + then + if test -n "$main" + then + # a rejoin commit? + # Pretend its sub was a squash. + sq="$sub" fi - sq= - main= - sub= - ;; + debug "Squash found: $sq $sub" + echo "$sq" "$sub" + break + fi + sq= + main= + sub= + ;; esac done } -find_existing_splits() -{ +find_existing_splits () { debug "Looking for prior splits..." dir="$1" revs="$2" @@ -267,38 +342,47 @@ find_existing_splits() sub= git log --grep="^git-subtree-dir: $dir/*\$" \ --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs | - while read a b junk; do + while read a b junk + do case "$a" in - START) sq="$b" ;; - git-subtree-mainline:) main="$b" ;; - git-subtree-split:) sub="$b" ;; - END) - debug " Main is: '$main'" - if [ -z "$main" -a -n "$sub" ]; then - # squash commits refer to a subtree - debug " Squash: $sq from $sub" - cache_set "$sq" "$sub" - fi - if [ -n "$main" -a -n "$sub" ]; then - debug " Prior: $main -> $sub" - cache_set $main $sub - cache_set $sub $sub - try_remove_previous "$main" - try_remove_previous "$sub" - fi - main= - sub= - ;; + START) + sq="$b" + ;; + git-subtree-mainline:) + main="$b" + ;; + git-subtree-split:) + sub="$(git rev-parse "$b^0")" || + die "could not rev-parse split hash $b from commit $sq" + ;; + END) + debug " Main is: '$main'" + if test -z "$main" -a -n "$sub" + then + # squash commits refer to a subtree + debug " Squash: $sq from $sub" + cache_set "$sq" "$sub" + fi + if test -n "$main" -a -n "$sub" + then + debug " Prior: $main -> $sub" + cache_set $main $sub + cache_set $sub $sub + try_remove_previous "$main" + try_remove_previous "$sub" + fi + main= + sub= + ;; esac done } -copy_commit() -{ +copy_commit () { # We're going to set some environment vars here, so # do it in a subshell to get rid of them safely later debug copy_commit "{$1}" "{$2}" "{$3}" - git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%B' "$1" | + git log -1 --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" | ( read GIT_AUTHOR_NAME read GIT_AUTHOR_EMAIL @@ -312,66 +396,69 @@ copy_commit() GIT_COMMITTER_NAME \ GIT_COMMITTER_EMAIL \ GIT_COMMITTER_DATE - (printf "%s" "$annotate"; cat ) | + ( + printf "%s" "$annotate" + cat + ) | git commit-tree "$2" $3 # reads the rest of stdin ) || die "Can't copy commit $1" } -add_msg() -{ +add_msg () { dir="$1" latest_old="$2" latest_new="$3" - if [ -n "$message" ]; then + if test -n "$message" + then commit_message="$message" else commit_message="Add '$dir/' from commit '$latest_new'" fi cat <<-EOF $commit_message - + git-subtree-dir: $dir git-subtree-mainline: $latest_old git-subtree-split: $latest_new EOF } -add_squashed_msg() -{ - if [ -n "$message" ]; then +add_squashed_msg () { + if test -n "$message" + then echo "$message" else echo "Merge commit '$1' as '$2'" fi } -rejoin_msg() -{ +rejoin_msg () { dir="$1" latest_old="$2" latest_new="$3" - if [ -n "$message" ]; then + if test -n "$message" + then commit_message="$message" else commit_message="Split '$dir/' into commit '$latest_new'" fi cat <<-EOF $commit_message - + git-subtree-dir: $dir git-subtree-mainline: $latest_old git-subtree-split: $latest_new EOF } -squash_msg() -{ +squash_msg () { dir="$1" oldsub="$2" newsub="$3" newsub_short=$(git rev-parse --short "$newsub") - - if [ -n "$oldsub" ]; then + + if test -n "$oldsub" + then oldsub_short=$(git rev-parse --short "$oldsub") echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short" echo @@ -380,41 +467,41 @@ squash_msg() else echo "Squashed '$dir/' content from commit $newsub_short" fi - + echo echo "git-subtree-dir: $dir" echo "git-subtree-split: $newsub" } -toptree_for_commit() -{ +toptree_for_commit () { commit="$1" git log -1 --pretty=format:'%T' "$commit" -- || exit $? } -subtree_for_commit() -{ +subtree_for_commit () { commit="$1" dir="$2" git ls-tree "$commit" -- "$dir" | - while read mode type tree name; do - assert [ "$name" = "$dir" ] - assert [ "$type" = "tree" -o "$type" = "commit" ] - [ "$type" = "commit" ] && continue # ignore submodules + while read mode type tree name + do + assert test "$name" = "$dir" + assert test "$type" = "tree" -o "$type" = "commit" + test "$type" = "commit" && continue # ignore submodules echo $tree break done } -tree_changed() -{ +tree_changed () { tree=$1 shift - if [ $# -ne 1 ]; then + if test $# -ne 1 + then return 0 # weird parents, consider it changed else ptree=$(toptree_for_commit $1) - if [ "$ptree" != "$tree" ]; then + if test "$ptree" != "$tree" + then return 0 # changed else return 1 # not changed @@ -422,110 +509,127 @@ tree_changed() fi } -new_squash_commit() -{ +new_squash_commit () { old="$1" oldsub="$2" newsub="$3" tree=$(toptree_for_commit $newsub) || exit $? - if [ -n "$old" ]; then - squash_msg "$dir" "$oldsub" "$newsub" | - git commit-tree "$tree" -p "$old" || exit $? + if test -n "$old" + then + squash_msg "$dir" "$oldsub" "$newsub" | + git commit-tree "$tree" -p "$old" || exit $? else squash_msg "$dir" "" "$newsub" | - git commit-tree "$tree" || exit $? + git commit-tree "$tree" || exit $? fi } -copy_or_skip() -{ +copy_or_skip () { rev="$1" tree="$2" newparents="$3" - assert [ -n "$tree" ] + assert test -n "$tree" identical= nonidentical= p= gotparents= - for parent in $newparents; do + for parent in $newparents + do ptree=$(toptree_for_commit $parent) || exit $? - [ -z "$ptree" ] && continue - if [ "$ptree" = "$tree" ]; then + test -z "$ptree" && continue + if test "$ptree" = "$tree" + then # an identical parent could be used in place of this rev. identical="$parent" else nonidentical="$parent" fi - + # sometimes both old parents map to the same newparent; # eliminate duplicates is_new=1 - for gp in $gotparents; do - if [ "$gp" = "$parent" ]; then + for gp in $gotparents + do + if test "$gp" = "$parent" + then is_new= break fi done - if [ -n "$is_new" ]; then + if test -n "$is_new" + then gotparents="$gotparents $parent" p="$p -p $parent" fi done - - if [ -n "$identical" ]; then + + copycommit= + if test -n "$identical" && test -n "$nonidentical" + then + extras=$(git rev-list --count $identical..$nonidentical) + if test "$extras" -ne 0 + then + # we need to preserve history along the other branch + copycommit=1 + fi + fi + if test -n "$identical" && test -z "$copycommit" + then echo $identical else - copy_commit $rev $tree "$p" || exit $? + copy_commit "$rev" "$tree" "$p" || exit $? fi } -ensure_clean() -{ - if ! git diff-index HEAD --exit-code --quiet 2>&1; then +ensure_clean () { + if ! git diff-index HEAD --exit-code --quiet 2>&1 + then die "Working tree has modifications. Cannot add." fi - if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then + if ! git diff-index --cached HEAD --exit-code --quiet 2>&1 + then die "Index has modifications. Cannot add." fi } -ensure_valid_ref_format() -{ +ensure_valid_ref_format () { git check-ref-format "refs/heads/$1" || - die "'$1' does not look like a ref" + die "'$1' does not look like a ref" } -cmd_add() -{ - if [ -e "$dir" ]; then +cmd_add () { + if test -e "$dir" + then die "'$dir' already exists. Cannot add." fi ensure_clean - - if [ $# -eq 1 ]; then - git rev-parse -q --verify "$1^{commit}" >/dev/null || - die "'$1' does not refer to a commit" - - "cmd_add_commit" "$@" - elif [ $# -eq 2 ]; then - # Technically we could accept a refspec here but we're - # just going to turn around and add FETCH_HEAD under the - # specified directory. Allowing a refspec might be - # misleading because we won't do anything with any other - # branches fetched via the refspec. - ensure_valid_ref_format "$2" - - "cmd_add_repository" "$@" + + if test $# -eq 1 + then + git rev-parse -q --verify "$1^{commit}" >/dev/null || + die "'$1' does not refer to a commit" + + cmd_add_commit "$@" + + elif test $# -eq 2 + then + # Technically we could accept a refspec here but we're + # just going to turn around and add FETCH_HEAD under the + # specified directory. Allowing a refspec might be + # misleading because we won't do anything with any other + # branches fetched via the refspec. + ensure_valid_ref_format "$2" + + cmd_add_repository "$@" else - say "error: parameters were '$@'" - die "Provide either a commit or a repository and commit." + say "error: parameters were '$@'" + die "Provide either a commit or a repository and commit." fi } -cmd_add_repository() -{ +cmd_add_repository () { echo "git fetch" "$@" repository=$1 refspec=$2 @@ -535,60 +639,63 @@ cmd_add_repository() cmd_add_commit "$@" } -cmd_add_commit() -{ +cmd_add_commit () { revs=$(git rev-parse $default --revs-only "$@") || exit $? set -- $revs rev="$1" - + debug "Adding $dir as '$rev'..." git read-tree --prefix="$dir" $rev || exit $? git checkout -- "$dir" || exit $? tree=$(git write-tree) || exit $? - + headrev=$(git rev-parse HEAD) || exit $? - if [ -n "$headrev" -a "$headrev" != "$rev" ]; then + if test -n "$headrev" && test "$headrev" != "$rev" + then headp="-p $headrev" else headp= fi - - if [ -n "$squash" ]; then + + if test -n "$squash" + then rev=$(new_squash_commit "" "" "$rev") || exit $? commit=$(add_squashed_msg "$rev" "$dir" | - git commit-tree $tree $headp -p "$rev") || exit $? + git commit-tree "$tree" $headp -p "$rev") || exit $? else revp=$(peel_committish "$rev") && - commit=$(add_msg "$dir" "$headrev" "$rev" | - git commit-tree $tree $headp -p "$revp") || exit $? + commit=$(add_msg "$dir" $headrev "$rev" | + git commit-tree "$tree" $headp -p "$revp") || exit $? fi git reset "$commit" || exit $? - + say "Added dir '$dir'" } -cmd_split() -{ +cmd_split () { debug "Splitting $dir..." cache_setup || exit $? - - if [ -n "$onto" ]; then + + if test -n "$onto" + then debug "Reading history for --onto=$onto..." git rev-list $onto | - while read rev; do + while read rev + do # the 'onto' history is already just the subdir, so # any parent we find there can be used verbatim debug " cache: $rev" - cache_set $rev $rev + cache_set "$rev" "$rev" done fi - - if [ -n "$ignore_joins" ]; then + + if test -n "$ignore_joins" + then unrevs= else unrevs="$(find_existing_splits "$dir" "$revs")" fi - + # We can't restrict rev-list to only $dir here, because some of our # parents have the $dir contents the root, and those won't match. # (and rev-list --follow doesn't seem to solve this) @@ -597,12 +704,14 @@ cmd_split() revcount=0 createcount=0 eval "$grl" | - while read rev parents; do + while read rev parents + do revcount=$(($revcount + 1)) - say -n "$revcount/$revmax ($createcount)
" + progress "$revcount/$revmax ($createcount)" debug "Processing commit: $rev" - exists=$(cache_get $rev) - if [ -n "$exists" ]; then + exists=$(cache_get "$rev") + if test -n "$exists" + then debug " prior: $exists" continue fi @@ -610,76 +719,89 @@ cmd_split() debug " parents: $parents" newparents=$(cache_get $parents) debug " newparents: $newparents" - - tree=$(subtree_for_commit $rev "$dir") + + tree=$(subtree_for_commit "$rev" "$dir") debug " tree is: $tree" check_parents $parents - + # ugly. is there no better way to tell if this is a subtree # vs. a mainline commit? Does it matter? - if [ -z $tree ]; then - set_notree $rev - if [ -n "$newparents" ]; then - cache_set $rev $rev + if test -z "$tree" + then + set_notree "$rev" + if test -n "$newparents" + then + cache_set "$rev" "$rev" fi continue fi newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $? debug " newrev is: $newrev" - cache_set $rev $newrev - cache_set latest_new $newrev - cache_set latest_old $rev + cache_set "$rev" "$newrev" + cache_set latest_new "$newrev" + cache_set latest_old "$rev" done || exit $? + latest_new=$(cache_get latest_new) - if [ -z "$latest_new" ]; then + if test -z "$latest_new" + then die "No new revisions were found" fi - - if [ -n "$rejoin" ]; then + + if test -n "$rejoin" + then debug "Merging split branch into HEAD..." latest_old=$(cache_get latest_old) git merge -s ours \ - -m "$(rejoin_msg $dir $latest_old $latest_new)" \ - $latest_new >&2 || exit $? - fi - if [ -n "$branch" ]; then - if rev_exists "refs/heads/$branch"; then - if ! rev_is_descendant_of_branch $latest_new $branch; then + --allow-unrelated-histories \ + -m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \ + "$latest_new" >&2 || exit $? + fi + if test -n "$branch" + then + if rev_exists "refs/heads/$branch" + then + if ! rev_is_descendant_of_branch "$latest_new" "$branch" + then die "Branch '$branch' is not an ancestor of commit '$latest_new'." fi action='Updated' else action='Created' fi - git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $? + git update-ref -m 'subtree split' \ + "refs/heads/$branch" "$latest_new" || exit $? say "$action branch '$branch'" fi - echo $latest_new + echo "$latest_new" exit 0 } -cmd_merge() -{ +cmd_merge () { revs=$(git rev-parse $default --revs-only "$@") || exit $? ensure_clean - + set -- $revs - if [ $# -ne 1 ]; then + if test $# -ne 1 + then die "You must provide exactly one revision. Got: '$revs'" fi rev="$1" - - if [ -n "$squash" ]; then + + if test -n "$squash" + then first_split="$(find_latest_squash "$dir")" - if [ -z "$first_split" ]; then + if test -z "$first_split" + then die "Can't squash-merge: '$dir' was never added." fi set $first_split old=$1 sub=$2 - if [ "$sub" = "$rev" ]; then + if test "$sub" = "$rev" + then say "Subtree is already at commit $rev." exit 0 fi @@ -689,25 +811,29 @@ cmd_merge() fi version=$(git version) - if [ "$version" \< "git version 1.7" ]; then - if [ -n "$message" ]; then - git merge -s subtree --message="$message" $rev + if test "$version" \< "git version 1.7" + then + if test -n "$message" + then + git merge -s subtree --message="$message" "$rev" else - git merge -s subtree $rev + git merge -s subtree "$rev" fi else - if [ -n "$message" ]; then - git merge -Xsubtree="$prefix" --message="$message" $rev + if test -n "$message" + then + git merge -Xsubtree="$prefix" \ + --message="$message" "$rev" else git merge -Xsubtree="$prefix" $rev fi fi } -cmd_pull() -{ - if [ $# -ne 2 ]; then - die "You must provide <repository> <ref>" +cmd_pull () { + if test $# -ne 2 + then + die "You must provide <repository> <ref>" fi ensure_clean ensure_valid_ref_format "$2" @@ -717,20 +843,21 @@ cmd_pull() cmd_merge "$@" } -cmd_push() -{ - if [ $# -ne 2 ]; then - die "You must provide <repository> <ref>" +cmd_push () { + if test $# -ne 2 + then + die "You must provide <repository> <ref>" fi ensure_valid_ref_format "$2" - if [ -e "$dir" ]; then - repository=$1 - refspec=$2 - echo "git push using: " $repository $refspec - localrev=$(git subtree split --prefix="$prefix") || die - git push $repository $localrev:refs/heads/$refspec + if test -e "$dir" + then + repository=$1 + refspec=$2 + echo "git push using: " "$repository" "$refspec" + localrev=$(git subtree split --prefix="$prefix") || die + git push "$repository" "$localrev":"refs/heads/$refspec" else - die "'$dir' must already exist. Try 'git subtree add'." + die "'$dir' must already exist. Try 'git subtree add'." fi } diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt index 54e4b4a243..60d76cdddf 100644 --- a/contrib/subtree/git-subtree.txt +++ b/contrib/subtree/git-subtree.txt @@ -146,7 +146,7 @@ OPTIONS OPTIONS FOR add, merge, push, pull ---------------------------------- --squash:: - This option is only valid for add, merge, push and pull + This option is only valid for add, merge, and pull commands. + Instead of merging the entire history from the subtree project, produce diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile index c864810389..276898eb6b 100644 --- a/contrib/subtree/t/Makefile +++ b/contrib/subtree/t/Makefile @@ -13,11 +13,23 @@ TAR ?= $(TAR) RM ?= rm -f PROVE ?= prove DEFAULT_TEST_TARGET ?= test +TEST_LINT ?= test-lint + +ifdef TEST_OUTPUT_DIRECTORY +TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results +else +TEST_RESULTS_DIRECTORY = ../../../t/test-results +endif # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) -T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) +T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) +TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh)) +TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh)) +THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh))) all: $(DEFAULT_TEST_TARGET) @@ -26,20 +38,22 @@ test: pre-clean $(TEST_LINT) prove: pre-clean $(TEST_LINT) @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) - $(MAKE) clean + $(MAKE) clean-except-prove-cache $(T): @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) pre-clean: - $(RM) -r test-results + $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' -clean: - $(RM) -r 'trash directory'.* test-results +clean-except-prove-cache: + $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' $(RM) -r valgrind/bin + +clean: clean-except-prove-cache $(RM) .prove -test-lint: test-lint-duplicates test-lint-executable +test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax test-lint-duplicates: @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ @@ -51,12 +65,15 @@ test-lint-executable: test -z "$$bad" || { \ echo >&2 "non-executable tests:" $$bad; exit 1; } +test-lint-shell-syntax: + @'$(PERL_PATH_SQ)' ../../../t/check-non-portable-shell.pl $(T) $(THELPERS) + aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results $(MAKE) clean aggregate-results: - for f in ../../../t/test-results/t*-*.counts; do \ + for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ echo "$$f"; \ done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 6309d124ca..3c87ebaf57 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -1,10 +1,11 @@ #!/bin/sh # # Copyright (c) 2012 Avery Pennaraum +# Copyright (c) 2015 Alexey Shumkin # test_description='Basic porcelain support for subtrees -This test verifies the basic operation of the merge, pull, add +This test verifies the basic operation of the add, pull, merge and split subcommands of git subtree. ' @@ -13,13 +14,21 @@ export TEST_DIRECTORY . ../../../t/test-lib.sh +subtree_test_create_repo() +{ + test_create_repo "$1" && + ( + cd "$1" && + git config log.date relative + ) +} + create() { - echo "$1" >"$1" + echo "$1" >"$1" && git add "$1" } - check_equal() { test_debug 'echo' @@ -32,438 +41,1053 @@ check_equal() fi } -fixnl() +undo() { - t="" - while read x; do - t="$t$x " - done - echo $t + git reset --hard HEAD~ } -multiline() +# Make sure no patch changes more than one file. +# The original set of commits changed only one file each. +# A multi-file change would imply that we pruned commits +# too aggressively. +join_commits() { - while read x; do - set -- $x - for d in "$@"; do - echo "$d" - done + commit= + all= + while read x y; do + if [ -z "$x" ]; then + continue + elif [ "$x" = "commit:" ]; then + if [ -n "$commit" ]; then + echo "$commit $all" + all= + fi + commit="$y" + else + all="$all $y" + fi done + echo "$commit $all" } -undo() -{ - git reset --hard HEAD~ -} +test_create_commit() ( + repo=$1 && + commit=$2 && + cd "$repo" && + mkdir -p "$(dirname "$commit")" \ + || error "Could not create directory for commit" + echo "$commit" >"$commit" && + git add "$commit" || error "Could not add commit" + git commit -m "$commit" || error "Could not commit" +) last_commit_message() { git log --pretty=format:%s -1 } -test_expect_success 'init subproj' ' - test_create_repo subproj -' - -# To the subproject! -cd subproj - -test_expect_success 'add sub1' ' - create sub1 && - git commit -m "sub1" && - git branch sub1 && - git branch -m master subproj -' - -# Save this hash for testing later. - -subdir_hash=$(git rev-parse HEAD) - -test_expect_success 'add sub2' ' - create sub2 && - git commit -m "sub2" && - git branch sub2 -' - -test_expect_success 'add sub3' ' - create sub3 && - git commit -m "sub3" && - git branch sub3 -' - -# Back to mainline -cd .. - -test_expect_success 'add main4' ' - create main4 && - git commit -m "main4" && - git branch -m master mainline && - git branch subdir -' - -test_expect_success 'fetch subproj history' ' - git fetch ./subproj sub1 && - git branch sub1 FETCH_HEAD -' - -test_expect_success 'no subtree exists in main tree' ' - test_must_fail git subtree merge --prefix=subdir sub1 -' +subtree_test_count=0 +next_test() { + subtree_test_count=$(($subtree_test_count+1)) +} -test_expect_success 'no pull from non-existant subtree' ' - test_must_fail git subtree pull --prefix=subdir ./subproj sub1 -' +# +# Tests for 'git subtree add' +# -test_expect_success 'check if --message works for add' ' - git subtree add --prefix=subdir --message="Added subproject" sub1 && - check_equal ''"$(last_commit_message)"'' "Added subproject" && - undo +next_test +test_expect_success 'no merge from non-existent subtree' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + test_must_fail git subtree merge --prefix="sub dir" FETCH_HEAD + ) ' -test_expect_success 'check if --message works as -m and --prefix as -P' ' - git subtree add -P subdir -m "Added subproject using git subtree" sub1 && - check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" && - undo +next_test +test_expect_success 'no pull from non-existent subtree' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" master + )' + +next_test +test_expect_success 'add subproj as subtree into sub dir/ with --prefix' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD && + check_equal "$(last_commit_message)" "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''" + ) ' -test_expect_success 'check if --message works with squash too' ' - git subtree add -P subdir -m "Added subproject with squash" --squash sub1 && - check_equal ''"$(last_commit_message)"'' "Added subproject with squash" && - undo +next_test +test_expect_success 'add subproj as subtree into sub dir/ with --prefix and --message' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" --message="Added subproject" FETCH_HEAD && + check_equal "$(last_commit_message)" "Added subproject" + ) ' -test_expect_success 'add subproj to mainline' ' - git subtree add --prefix=subdir/ FETCH_HEAD && - check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'" +next_test +test_expect_success 'add subproj as subtree into sub dir/ with --prefix as -P and --message as -m' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add -P "sub dir" -m "Added subproject" FETCH_HEAD && + check_equal "$(last_commit_message)" "Added subproject" + ) ' -# this shouldn't actually do anything, since FETCH_HEAD is already a parent -test_expect_success 'merge fetched subproj' ' - git merge -m "merge -s -ours" -s ours FETCH_HEAD +next_test +test_expect_success 'add subproj as subtree into sub dir/ with --squash and --prefix and --message' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" --message="Added subproject with squash" --squash FETCH_HEAD && + check_equal "$(last_commit_message)" "Added subproject with squash" + ) ' -test_expect_success 'add main-sub5' ' - create subdir/main-sub5 && - git commit -m "main-sub5" -' +# +# Tests for 'git subtree merge' +# -test_expect_success 'add main6' ' - create main6 && - git commit -m "main6 boring" +next_test +test_expect_success 'merge new subproj history into sub dir/ with --prefix' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + check_equal "$(last_commit_message)" "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''" + ) ' -test_expect_success 'add main-sub7' ' - create subdir/main-sub7 && - git commit -m "main-sub7" +next_test +test_expect_success 'merge new subproj history into sub dir/ with --prefix and --message' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" --message="Merged changes from subproject" FETCH_HEAD && + check_equal "$(last_commit_message)" "Merged changes from subproject" + ) ' -test_expect_success 'fetch new subproj history' ' - git fetch ./subproj sub2 && - git branch sub2 FETCH_HEAD +next_test +test_expect_success 'merge new subproj history into sub dir/ with --squash and --prefix and --message' ' + subtree_test_create_repo "$subtree_test_count/sub proj" && + subtree_test_create_repo "$subtree_test_count" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" --message="Merged changes from subproject using squash" --squash FETCH_HEAD && + check_equal "$(last_commit_message)" "Merged changes from subproject using squash" + ) ' -test_expect_success 'check if --message works for merge' ' - git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 && - check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" && - undo +next_test +test_expect_success 'merge the added subproj again, should do nothing' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD && + # this shouldn not actually do anything, since FETCH_HEAD + # is already a parent + result=$(git merge -s ours -m "merge -s -ours" FETCH_HEAD) && + check_equal "${result}" "Already up-to-date." + ) ' -test_expect_success 'check if --message for merge works with squash too' ' - git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 && - check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" && - undo +next_test +test_expect_success 'merge new subproj history into subdir/ with a slash appended to the argument of --prefix' ' + test_create_repo "$test_count" && + test_create_repo "$test_count/subproj" && + test_create_commit "$test_count" main1 && + test_create_commit "$test_count/subproj" sub1 && + ( + cd "$test_count" && + git fetch ./subproj master && + git subtree add --prefix=subdir/ FETCH_HEAD + ) && + test_create_commit "$test_count/subproj" sub2 && + ( + cd "$test_count" && + git fetch ./subproj master && + git subtree merge --prefix=subdir/ FETCH_HEAD && + check_equal "$(last_commit_message)" "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''" + ) ' -test_expect_success 'merge new subproj history into subdir' ' - git subtree merge --prefix=subdir FETCH_HEAD && - git branch pre-split && - check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline" -' +# +# Tests for 'git subtree split' +# -test_expect_success 'Check that prefix argument is required for split' ' - echo "You must provide the --prefix option." > expected && - test_must_fail git subtree split > actual 2>&1 && - test_debug "printf '"'"'expected: '"'"'" && - test_debug "cat expected" && - test_debug "printf '"'"'actual: '"'"'" && - test_debug "cat actual" && - test_cmp expected actual && - rm -f expected actual +next_test +test_expect_success 'split requires option --prefix' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD && + echo "You must provide the --prefix option." > expected && + test_must_fail git subtree split > actual 2>&1 && + test_debug "printf '"expected: "'" && + test_debug "cat expected" && + test_debug "printf '"actual: "'" && + test_debug "cat actual" && + test_cmp expected actual + ) ' -test_expect_success 'Check that the <prefix> exists for a split' ' - echo "'"'"'non-existent-directory'"'"'" does not exist\; use "'"'"'git subtree add'"'"'" > expected && - test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 && - test_debug "printf '"'"'expected: '"'"'" && - test_debug "cat expected" && - test_debug "printf '"'"'actual: '"'"'" && - test_debug "cat actual" && - test_cmp expected actual -# rm -f expected actual +next_test +test_expect_success 'split requires path given by option --prefix must exist' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD && + echo "'\''non-existent-directory'\'' does not exist; use '\''git subtree add'\''" > expected && + test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 && + test_debug "printf '"expected: "'" && + test_debug "cat expected" && + test_debug "printf '"actual: "'" && + test_debug "cat actual" && + test_cmp expected actual + ) ' -test_expect_success 'check if --message works for split+rejoin' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && - git branch spl1 "$spl1" && - check_equal ''"$(last_commit_message)"'' "Split & rejoin" && - undo +next_test +test_expect_success 'split sub dir/ with --rejoin' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && + git subtree split --prefix="sub dir" --annotate="*" --rejoin && + check_equal "$(last_commit_message)" "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" + ) + ' + +next_test +test_expect_success 'split sub dir/ with --rejoin from scratch' ' + subtree_test_create_repo "$subtree_test_count" && + test_create_commit "$subtree_test_count" main1 && + ( + cd "$subtree_test_count" && + mkdir "sub dir" && + echo file >"sub dir"/file && + git add "sub dir/file" && + git commit -m"sub dir file" && + split_hash=$(git subtree split --prefix="sub dir" --rejoin) && + git subtree split --prefix="sub dir" --rejoin && + check_equal "$(last_commit_message)" "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" + ) + ' + +next_test +test_expect_success 'split sub dir/ with --rejoin and --message' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --message="Split & rejoin" --annotate="*" --rejoin && + check_equal "$(last_commit_message)" "Split & rejoin" + ) ' -test_expect_success 'check split with --branch' ' - spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) && - undo && - git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 && - check_equal ''"$(git rev-parse splitbr1)"'' "$spl1" +next_test +test_expect_success 'split "sub dir"/ with --branch' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br && + check_equal "$(git rev-parse subproj-br)" "$split_hash" + ) ' +next_test test_expect_success 'check hash of split' ' - spl1=$(git subtree split --prefix subdir) && - undo && - git subtree split --prefix subdir --branch splitbr1test && - check_equal ''"$(git rev-parse splitbr1test)"'' "$spl1" - git checkout splitbr1test && - new_hash=$(git rev-parse HEAD~2) && - git checkout mainline && - check_equal ''"$new_hash"'' "$subdir_hash" -' - -test_expect_success 'check split with --branch for an existing branch' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && - undo && - git branch splitbr2 sub1 && - git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 && - check_equal ''"$(git rev-parse splitbr2)"'' "$spl1" -' - -test_expect_success 'check split with --branch for an incompatible branch' ' - test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir -' - -test_expect_success 'check split+rejoin' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && - undo && - git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin && - check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'" -' - -test_expect_success 'add main-sub8' ' - create subdir/main-sub8 && - git commit -m "main-sub8" + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br && + check_equal "$(git rev-parse subproj-br)" "$split_hash" && + # Check hash of split + new_hash=$(git rev-parse subproj-br^2) && + ( + cd ./"sub proj" && + subdir_hash=$(git rev-parse HEAD) && + check_equal ''"$new_hash"'' "$subdir_hash" + ) + ) ' -# To the subproject! -cd ./subproj - -test_expect_success 'merge split into subproj' ' - git fetch .. spl1 && - git branch spl1 FETCH_HEAD && - git merge FETCH_HEAD +next_test +test_expect_success 'split "sub dir"/ with --branch for an existing branch' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git branch subproj-br FETCH_HEAD && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br && + check_equal "$(git rev-parse subproj-br)" "$split_hash" + ) ' -test_expect_success 'add sub9' ' - create sub9 && - git commit -m "sub9" +next_test +test_expect_success 'split "sub dir"/ with --branch for an incompatible branch' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git branch init HEAD && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + test_must_fail git subtree split --prefix="sub dir" --branch init + ) ' -# Back to mainline -cd .. - -test_expect_success 'split for sub8' ' - split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"'' - git branch split2 "$split2" -' - -test_expect_success 'add main-sub10' ' - create subdir/main-sub10 && - git commit -m "main-sub10" -' - -test_expect_success 'split for sub10' ' - spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' && - git branch spl3 "$spl3" -' - -# To the subproject! -cd ./subproj - -test_expect_success 'merge split into subproj' ' - git fetch .. spl3 && - git branch spl3 FETCH_HEAD && - git merge FETCH_HEAD && - git branch subproj-merge-spl3 -' - -chkm="main4 main6" -chkms="main-sub10 main-sub5 main-sub7 main-sub8" -chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl) -chks="sub1 sub2 sub3 sub9" -chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl) +# +# Validity checking +# +next_test test_expect_success 'make sure exactly the right set of files ends up in the subproj' ' - subfiles=''"$(git ls-files | fixnl)"'' && - check_equal "$subfiles" "$chkms $chks" -' - -test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' ' - allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' && - check_equal "$allchanges" "$chkms $chks" + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD && + + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chks +TXT +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chkms +TXT +) && + + subfiles=$(git ls-files) && + check_equal "$subfiles" "$chkms +$chks" + ) ' -# Back to mainline -cd .. - -test_expect_success 'pull from subproj' ' - git fetch ./subproj subproj-merge-spl3 && - git branch subproj-merge-spl3 FETCH_HEAD && - git subtree pull --prefix=subdir ./subproj subproj-merge-spl3 +next_test +test_expect_success 'make sure the subproj *only* contains commits that affect the "sub dir"' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD && + + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chks +TXT +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chkms +TXT +) && + allchanges=$(git log --name-only --pretty=format:"" | sort | sed "/^$/d") && + check_equal "$allchanges" "$chkms +$chks" + ) ' +next_test test_expect_success 'make sure exactly the right set of files ends up in the mainline' ' - mainfiles=''"$(git ls-files | fixnl)"'' && - check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub" + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + ( + cd "$subtree_test_count" && + git subtree pull --prefix="sub dir" ./"sub proj" master && + + chkm="main1 +main2" && + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chks +TXT +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chkms +TXT +) && + mainfiles=$(git ls-files) && + check_equal "$mainfiles" "$chkm +$chkms_sub +$chks_sub" +) ' +next_test test_expect_success 'make sure each filename changed exactly once in the entire history' ' - # main-sub?? and /subdir/main-sub?? both change, because those are the - # changes that were split into their own history. And subdir/sub?? never - # change, since they were *only* changed in the subtree branch. - allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' && - check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"'' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git config log.date relative + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + ( + cd "$subtree_test_count" && + git subtree pull --prefix="sub dir" ./"sub proj" master && + + chkm="main1 +main2" && + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chks +TXT +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chkms +TXT +) && + + # main-sub?? and /"sub dir"/main-sub?? both change, because those are the + # changes that were split into their own history. And "sub dir"/sub?? never + # change, since they were *only* changed in the subtree branch. + allchanges=$(git log --name-only --pretty=format:"" | sort | sed "/^$/d") && + expected=''"$(cat <<TXT | sort +$chkms +$chkm +$chks +$chkms_sub +TXT +)"'' && + check_equal "$allchanges" "$expected" + ) ' +next_test test_expect_success 'make sure the --rejoin commits never make it into subproj' ' - check_equal ''"$(git log --pretty=format:'"'%s'"' HEAD^2 | grep -i split)"'' "" + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + ( + cd "$subtree_test_count" && + git subtree pull --prefix="sub dir" ./"sub proj" master && + check_equal "$(git log --pretty=format:"%s" HEAD^2 | grep -i split)" "" + ) ' +next_test test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' ' - # They are meaningless to subproj since one side of the merge refers to the mainline - check_equal ''"$(git log --pretty=format:'"'%s%n%b'"' HEAD^2 | grep "git-subtree.*:")"'' "" -' - -# prepare second pair of repositories -mkdir test2 -cd test2 - -test_expect_success 'init main' ' - test_create_repo main -' - -cd main - -test_expect_success 'add main1' ' - create main1 && - git commit -m "main1" -' - -cd .. - -test_expect_success 'init sub' ' - test_create_repo sub -' - -cd sub - -test_expect_success 'add sub2' ' - create sub2 && - git commit -m "sub2" -' - -cd ../main - -# check if split can find proper base without --onto - -test_expect_success 'add sub as subdir in main' ' - git fetch ../sub master && - git branch sub2 FETCH_HEAD && - git subtree add --prefix subdir sub2 -' - -cd ../sub - -test_expect_success 'add sub3' ' - create sub3 && - git commit -m "sub3" + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + ( + cd "$subtree_test_count" && + git subtree pull --prefix="sub dir" ./"sub proj" master && + + # They are meaningless to subproj since one side of the merge refers to the mainline + check_equal "$(git log --pretty=format:"%s%n%b" HEAD^2 | grep "git-subtree.*:")" "" + ) ' -cd ../main - -test_expect_success 'merge from sub' ' - git fetch ../sub master && - git branch sub3 FETCH_HEAD && - git subtree merge --prefix subdir sub3 -' - -test_expect_success 'add main-sub4' ' - create subdir/main-sub4 && - git commit -m "main-sub4" -' +# +# A new set of tests +# -test_expect_success 'split for main-sub4 without --onto' ' - git subtree split --prefix subdir --branch mainsub4 +next_test +test_expect_success 'make sure "git subtree split" find the correct parent' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git branch subproj-ref FETCH_HEAD && + git subtree merge --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --branch subproj-br && + + # at this point, the new commit parent should be subproj-ref, if it is + # not, something went wrong (the "newparent" of "master~" commit should + # have been sub2, but it was not, because its cache was not set to + # itself) + check_equal "$(git log --pretty=format:%P -1 subproj-br)" "$(git rev-parse subproj-ref)" + ) ' -# at this point, the new commit parent should be sub3 if it is not, -# something went wrong (the "newparent" of "master~" commit should -# have been sub3, but it was not, because its cache was not set to -# itself) - -test_expect_success 'check that the commit parent is sub3' ' - check_equal ''"$(git log --pretty=format:%P -1 mainsub4)"'' ''"$(git rev-parse sub3)"'' +next_test +test_expect_success 'split a new subtree without --onto option' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --branch subproj-br + ) && + mkdir "$subtree_test_count"/"sub dir2" && + test_create_commit "$subtree_test_count" "sub dir2"/main-sub2 && + ( + cd "$subtree_test_count" && + + # also test that we still can split out an entirely new subtree + # if the parent of the first commit in the tree is not empty, + # then the new subtree has accidentally been attached to something + git subtree split --prefix="sub dir2" --branch subproj2-br && + check_equal "$(git log --pretty=format:%P -1 subproj2-br)" "" + ) ' -test_expect_success 'add main-sub5' ' - mkdir subdir2 && - create subdir2/main-sub5 && - git commit -m "main-sub5" +next_test +test_expect_success 'verify one file change per commit' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git branch sub1 FETCH_HEAD && + git subtree add --prefix="sub dir" sub1 + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --branch subproj-br + ) && + mkdir "$subtree_test_count"/"sub dir2" && + test_create_commit "$subtree_test_count" "sub dir2"/main-sub2 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir2" --branch subproj2-br && + + x= && + git log --pretty=format:"commit: %H" | join_commits | + ( + while read commit a b; do + test_debug "echo Verifying commit $commit" + test_debug "echo a: $a" + test_debug "echo b: $b" + check_equal "$b" "" + x=1 + done + check_equal "$x" 1 + ) + ) ' -test_expect_success 'split for main-sub5 without --onto' ' - # also test that we still can split out an entirely new subtree - # if the parent of the first commit in the tree is not empty, - # then the new subtree has accidentally been attached to something - git subtree split --prefix subdir2 --branch mainsub5 && - check_equal ''"$(git log --pretty=format:%P -1 mainsub5)"'' "" +next_test +test_expect_success 'push split to subproj' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd $subtree_test_count/"sub proj" && + git branch sub-branch-1 && + cd .. && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count" && + git subtree push ./"sub proj" --prefix "sub dir" sub-branch-1 && + cd ./"sub proj" && + git checkout sub-branch-1 && + check_equal "$(last_commit_message)" "sub dir/main-sub3" + ) ' -# make sure no patch changes more than one file. The original set of commits -# changed only one file each. A multi-file change would imply that we pruned -# commits too aggressively. -joincommits() -{ - commit= - all= - while read x y; do - #echo "{$x}" >&2 - if [ -z "$x" ]; then - continue - elif [ "$x" = "commit:" ]; then - if [ -n "$commit" ]; then - echo "$commit $all" - all= - fi - commit="$y" - else - all="$all $y" - fi - done - echo "$commit $all" -} +# +# This test covers 2 cases in subtree split copy_or_skip code +# 1) Merges where one parent is a superset of the changes of the other +# parent regarding changes to the subtree, in this case the merge +# commit should be copied +# 2) Merges where only one parent operate on the subtree, and the merge +# commit should be skipped +# +# (1) is checked by ensuring subtree_tip is a descendent of subtree_branch +# (2) should have a check added (not_a_subtree_change shouldn't be present +# on the produced subtree) +# +# Other related cases which are not tested (or currently handled correctly) +# - Case (1) where there are more than 2 parents, it will sometimes correctly copy +# the merge, and sometimes not +# - Merge commit where both parents have same tree as the merge, currently +# will always be skipped, even if they reached that state via different +# set of commits. +# -test_expect_success 'verify one file change per commit' ' - x= && - list=''"$(git log --pretty=format:'"'commit: %H'"' | joincommits)"'' && -# test_debug "echo HERE" && -# test_debug "echo ''"$list"''" && - (git log --pretty=format:'"'commit: %H'"' | joincommits | - ( while read commit a b; do - test_debug "echo Verifying commit "''"$commit"'' - test_debug "echo a: "''"$a"'' - test_debug "echo b: "''"$b"'' - check_equal "$b" "" - x=1 - done - check_equal "$x" 1 - )) +next_test +test_expect_success 'subtree descendant check' ' + subtree_test_create_repo "$subtree_test_count" && + test_create_commit "$subtree_test_count" folder_subtree/a && + ( + cd "$subtree_test_count" && + git branch branch + ) && + test_create_commit "$subtree_test_count" folder_subtree/0 && + test_create_commit "$subtree_test_count" folder_subtree/b && + cherry=$(cd "$subtree_test_count"; git rev-parse HEAD) && + ( + cd "$subtree_test_count" && + git checkout branch + ) && + test_create_commit "$subtree_test_count" commit_on_branch && + ( + cd "$subtree_test_count" && + git cherry-pick $cherry && + git checkout master && + git merge -m "merge should be kept on subtree" branch && + git branch no_subtree_work_branch + ) && + test_create_commit "$subtree_test_count" folder_subtree/d && + ( + cd "$subtree_test_count" && + git checkout no_subtree_work_branch + ) && + test_create_commit "$subtree_test_count" not_a_subtree_change && + ( + cd "$subtree_test_count" && + git checkout master && + git merge -m "merge should be skipped on subtree" no_subtree_work_branch && + + git subtree split --prefix folder_subtree/ --branch subtree_tip master && + git subtree split --prefix folder_subtree/ --branch subtree_branch branch && + check_equal $(git rev-list --count subtree_tip..subtree_branch) 0 + ) ' test_done diff --git a/contrib/subtree/todo b/contrib/subtree/todo index 7e44b0024f..0d0e777651 100644 --- a/contrib/subtree/todo +++ b/contrib/subtree/todo @@ -12,8 +12,6 @@ exactly the right subtree structure, rather than using subtree merge...) - add a 'push' subcommand to parallel 'pull' - add a 'log' subcommand to see what's new in a subtree? add to-submodule and from-submodule commands |