diff --git a/contrib/subtree/.gitignore b/contrib/subtree/.gitignore index 7e77c9d0229e94..30adcd16e71c65 100644 --- a/contrib/subtree/.gitignore +++ b/contrib/subtree/.gitignore @@ -3,3 +3,6 @@ git-subtree.xml git-subtree.1 mainline subproj +submodule* +launchpad +.gittrees diff --git a/contrib/subtree/README b/contrib/subtree/README deleted file mode 100644 index c686b4a69b1257..00000000000000 --- a/contrib/subtree/README +++ /dev/null @@ -1,8 +0,0 @@ - -Please read git-subtree.txt for documentation. - -Please don't contact me using github mail; it's slow, ugly, and worst of -all, redundant. Email me instead at apenwarr@gmail.com and I'll be happy to -help. - -Avery diff --git a/contrib/subtree/README.md b/contrib/subtree/README.md new file mode 100644 index 00000000000000..21f57bbe2e8cce --- /dev/null +++ b/contrib/subtree/README.md @@ -0,0 +1,39 @@ +# git-subtree + +git-subtree allows subprojects to be included within a sub-directory of a main project, optionally including the sub-project's entire history. + +## Installation + +Choose **one** of the following ways to install git-subtree: + +1. Copy the file `git-subtree.sh` to where all other git scripts are stored (`git --exec-path` will tell you this). +1. Run `install.sh` in a Git-enabled shell (that's "Git Bash" on Windows). +1. Run `make install` in a Cygwin-enabled shell. + +Any *one* of these actions makes the `git subtree` command available (note: space instead of dash). + +To additionally install the man page: + + make doc + cp git-subtree.1 /usr/share/man/man1/ + + +## Usage + +See `git-subtree.txt` for details. + +## Known issues + +See `todo`. + +## License + +You may use this software under the terms of the GNU General Public License (GPL), Version 2. + +See `COPYING`. + +## Credits + +Originally authored by Avery Pennarun, + +Please do not contact the author using github mail. Instead, diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 920c664bb7c7a4..d46e19b0742d91 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -8,11 +8,17 @@ if [ $# -eq 0 ]; then set -- -h fi OPTS_SPEC="\ -git subtree add --prefix= +git subtree add --prefix= git subtree merge --prefix= -git subtree pull --prefix= -git subtree push --prefix= +git subtree pull --prefix= [ [...]] +git subtree pull-all +git subtree push-all +git subtree push --prefix= [ [...]] +git subtree list git subtree split --prefix= +git subtree from-submodule --prefix= +git subtree prune +git subtree diff --prefix= [ [...]] -- h,help show the help q quiet @@ -25,6 +31,8 @@ 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 'push' +f,force use force push options for 'add', 'merge', 'pull' and 'push' squash merge subtree changes as a single commit " @@ -81,8 +89,9 @@ while [ $# -gt 0 ]; do --annotate) annotate="$1"; shift ;; --no-annotate) annotate= ;; -b) branch="$1"; shift ;; - -P) prefix="$1"; shift ;; + -P|--prefix) prefix="$1"; shift ;; -m) message="$1"; shift ;; + -f|--force) force=1 ;; --no-prefix) prefix= ;; --onto) onto="$1"; shift ;; --no-onto) onto= ;; @@ -97,19 +106,26 @@ while [ $# -gt 0 ]; do esac done +# Remove trailing slash +prefix="${prefix%/}"; + command="$1" shift case "$command" in - add|merge|pull) default= ;; - split|push) default="--default HEAD" ;; + add|merge|pull|pull-all|push-all|from-submodule|prune) default= ;; + split|push|diff|list) default="--default HEAD" ;; *) die "Unknown command '$command'" ;; esac -if [ -z "$prefix" ]; then +if [ -z "$prefix" -a "$command" != "pull-all" -a "$command" != "push-all" -a "$command" != "list" -a "$command" != "prune" ]; then die "You must provide the --prefix option." fi case "$command" in + pull-all);; + push-all);; + list);; + prune);; add) [ -e "$prefix" ] && die "prefix '$prefix' already exists." ;; *) [ -e "$prefix" ] || @@ -118,7 +134,7 @@ esac dir="$(dirname "$prefix/.")" -if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then +if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" -a "$command" != "pull-all" -a "$command" != "diff" ]; then revs=$(git rev-parse $default --revs-only "$@") || exit $? dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $? if [ -n "$dirs" ]; then @@ -515,6 +531,14 @@ cmd_add_repository() revs=FETCH_HEAD set -- $revs cmd_add_commit "$@" + + # now add it to our list of repos + git config -f .gittrees --unset subtree.$dir.url + git config -f .gittrees --add subtree.$dir.url $repository + git config -f .gittrees --unset subtree.$dir.path + git config -f .gittrees --add subtree.$dir.path $dir + git config -f .gittrees --unset subtree.$dir.branch + git config -f .gittrees --add subtree.$dir.branch $refspec } cmd_add_commit() @@ -580,7 +604,8 @@ cmd_split() eval "$grl" | while read rev parents; do revcount=$(($revcount + 1)) - say -n "$revcount/$revmax ($createcount) " + say -n "$revcount/$revmax ($createcount) +" debug "Processing commit: $rev" exists=$(cache_get $rev) if [ -n "$exists" ]; then @@ -687,26 +712,176 @@ cmd_merge() cmd_pull() { - ensure_clean - git fetch "$@" || exit $? - revs=FETCH_HEAD - set -- $revs - cmd_merge "$@" + if [ $# -gt 2 ]; then + die "You should provide either or " + fi + if [ -e "$dir" ]; then + ensure_clean + if [ $# -eq 1 ]; then + repository=$(git config -f .gittrees subtree.$prefix.url) + refspec=$1 + elif [ $# -eq 2 ]; then + repository=$1 + refspec=$2 + else + repository=$(git config -f .gittrees subtree.$prefix.url) + refspec=$(git config -f .gittrees subtree.$prefix.branch) + fi + git fetch $repository $refspec || exit $? + echo "git fetch using: " $repository $refspec + revs=FETCH_HEAD + set -- $revs + cmd_merge "$@" + else + die "'$dir' must already exist. Try 'git subtree add'." + fi } +cmd_diff() +{ + if [ -e "$dir" ]; then + if [ $# -eq 1 ]; then + repository=$(git config -f .gittrees subtree.$prefix.url) + refspec=$1 + elif [ $# -eq 2 ]; then + repository=$1 + refspec=$2 + else + repository=$(git config -f .gittrees subtree.$prefix.url) + refspec=$(git config -f .gittrees subtree.$prefix.branch) + fi + # this is ugly, but I don't know of a better way to do it. My git-fu is weak. + # git diff-tree expects a treeish, but I have only a repository and branch name. + # I don't know how to turn that into a treeish without creating a remote. + # Please change this if you know a better way! + tmp_remote=__diff-tmp + git remote rm $tmp_remote > /dev/null 2>&1 + git remote add -t $refspec $tmp_remote $repository > /dev/null + # we fetch as a separate step so we can pass -q (quiet), which isn't an option for "git remote" + # could this instead be "git fetch -q $repository $refspec" and leave aside creating the remote? + # Still need a treeish for the diff-tree command... + git fetch -q $tmp_remote + git diff-tree -p refs/remotes/$tmp_remote/$refspec + git remote rm $tmp_remote > /dev/null 2>&1 + else + die "Cannot resolve directory '$dir'. Please point to an existing subtree directory to diff. Try 'git subtree add' to add a subtree." + fi +} cmd_push() { - if [ $# -ne 2 ]; then - die "You must provide " + if [ $# -gt 2 ]; then + die "You shold provide either or " fi if [ -e "$dir" ]; then - repository=$1 - refspec=$2 - echo "git push using: " $repository $refspec - git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec + if [ $# -eq 1 ]; then + repository=$(git config -f .gittrees subtree.$prefix.url) + refspec=$1 + elif [ $# -eq 2 ]; then + repository=$1 + refspec=$2 + else + repository=$(git config -f .gittrees subtree.$prefix.url) + refspec=$(git config -f .gittrees subtree.$prefix.branch) + fi + + push_opts= + if [ "$force" == "1" ]; then + push_opts="$push_opts --force" + fi + + echo "git push using: " $repository $refspec + rev=$(git subtree split --prefix=$prefix) + if [ -n "$rev" ]; then + git push $push_opts $repository $rev:refs/heads/$refspec + else + die "Couldn't push, 'git subtree split' failed." + fi else die "'$dir' must already exist. Try 'git subtree add'." fi } +subtree_list() +{ + git config -f .gittrees -l | grep subtree | grep path | grep -o '=.*' | grep -o '[^=].*' | + while read path; do + repository=$(git config -f .gittrees subtree.$path.url) + refspec=$(git config -f .gittrees subtree.$path.branch) + echo " $path (merged from $repository branch $refspec) " + done +} + +cmd_list() +{ + subtree_list +} + +cmd_from-submodule() +{ + ensure_clean + + local submodule_sha=$(git submodule status $prefix | cut -d ' ' -f 2) + local submodule_orig_repo=$(git config --file .gitmodules submodule.$prefix.url) + + # Remove references to submodule. + git config --remove-section submodule.$prefix + git config --file .gitmodules --remove-section submodule.$prefix + git add .gitmodules + + # Move submodule aside. + local tmp_repo="$(mktemp -d /tmp/git-subtree.XXXXX)" + rm -r $tmp_repo + mv $prefix $tmp_repo + git rm $prefix + + # Commit changes. + git commit -m "Remove '$prefix/' submodule" + + # subtree add from submodule repo. + # TODO: Could be determin HEAD to be a specific branch + cmd_add_repository $tmp_repo HEAD + + # Update .gittrees with the original repo url + git config --file .gittrees --unset subtree.$prefix.url + git config --file .gittrees subtree.$prefix.url $submodule_orig_repo + + # Remove submodule repo. + rm -rf $tmp_repo +} + +cmd_prune() +{ + git config -f .gittrees -l | grep subtree | grep path | grep -o '=.*' | grep -o '[^=].*' | + while read path; do + if [ ! -e "$path" ]; then + echo "pruning $path" + git config -f .gittrees --remove-section subtree.$path + fi + done +} + +cmd_pull-all() +{ + git config -f .gittrees -l | grep subtree | grep path | grep -o '=.*' | grep -o '[^=].*' | + while read path; do + if [ -n "$squash" ]; then + git subtree pull -P $path --squash $(git config -f .gittrees subtree.$path.url) $(git config -f .gittrees subtree.$path.branch) || exit $? + else + git subtree pull -P $path $(git config -f .gittrees subtree.$path.url) $(git config -f .gittrees subtree.$path.branch) || exit $? + fi + done +} + +cmd_push-all() +{ + git config -f .gittrees -l | grep subtree | grep path | grep -o '=.*' | grep -o '[^=].*' | + while read path; do + if [ -n "$squash" ]; then + git subtree push -P $path --squash $(git config -f .gittrees subtree.$path.url) $(git config -f .gittrees subtree.$path.branch) || exit $? + else + git subtree push -P $path $(git config -f .gittrees subtree.$path.url) $(git config -f .gittrees subtree.$path.branch) || exit $? + fi + done +} + "cmd_$command" "$@" diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt index 0c44fda011bcfd..e23973278f746a 100644 --- a/contrib/subtree/git-subtree.txt +++ b/contrib/subtree/git-subtree.txt @@ -9,12 +9,17 @@ git-subtree - Merge subtrees together and split repository into subtrees SYNOPSIS -------- [verse] -'git subtree' add -P -'git subtree' pull -P -'git subtree' push -P -'git subtree' merge -P -'git subtree' split -P [OPTIONS] [] - +'git subtree' add --prefix= +'git subtree' merge --prefix= +'git subtree' pull --prefix= [ [...]] +'git subtree' pull-all +'git subtree' push-all +'git subtree' push --prefix= [ [...]] +'git subtree' list +'git subtree' split --prefix= +'git subtree' from-submodule --prefix= +'git subtree' prune +'git subtree' diff --prefix= [ [...]] DESCRIPTION ----------- @@ -91,13 +96,22 @@ pull:: Exactly like 'merge', but parallels 'git pull' in that it fetches the given commit from the specified remote repository. - + +pull-all:: + Perform a pull operation on all in .gittrees registered subtrees. + push:: Does a 'split' (see above) using the supplied and then does a 'git push' to push the result to the repository and refspec. This can be used to push your subtree to different branches of the remote repository. +push-all:: + Perform a pull operation on all in .gittrees registered subtrees. + +list:: + Show a list of the in .gittrees registered subtrees + split:: Extract a new, synthetic project history from the history of the subtree. The new history @@ -122,6 +136,16 @@ split:: Note that if you use '--squash' when you merge, you should usually not just '--rejoin' when you split. +from-submodule:: + Convert a git submodule to a subtree. + The module is removed from the .gitmodules file and + the repo contents are integrated as a subtree. + +prune:: + Cleanup .gittrees entries for which the subtree nolonger exists. + +diff:: + TO BE DOCUMENTED OPTIONS ------- @@ -254,6 +278,13 @@ OPTIONS FOR split '--rejoin' when you split, because you don't want the subproject's history to be part of your project anyway. +OPTIONS FOR push +---------------- +-f:: +--force:: + Uses 'git push --force'. + + EXAMPLE 1. Add command ---------------------- @@ -269,7 +300,7 @@ git-extensions repository in ~/git-extensions/: name You can omit the --squash flag, but doing so will increase the number -of commits that are incldued in your local repository. +of commits that are included in your local repository. We now have a ~/git-extensions/git-subtree directory containing code from the master branch of git://github.com/apenwarr/git-subtree.git diff --git a/contrib/subtree/test.sh b/contrib/subtree/test.sh new file mode 100755 index 00000000000000..3dc4b38ebc2e92 --- /dev/null +++ b/contrib/subtree/test.sh @@ -0,0 +1,402 @@ +#!/bin/bash +. shellopts.sh +set -e + +create() +{ + echo "$1" >"$1" + git add "$1" +} + +check() +{ + echo + echo "check:" "$@" + if "$@"; then + echo ok + return 0 + else + echo FAILED + exit 1 + fi +} + +check_not() +{ + echo + echo "check: NOT " "$@" + if "$@"; then + echo FAILED + exit 1 + else + echo ok + return 0 + fi +} + +check_equal() +{ + echo + echo "check a:" "{$1}" + echo " b:" "{$2}" + if [ "$1" = "$2" ]; then + return 0 + else + echo FAILED + exit 1 + fi +} + +fixnl() +{ + t="" + while read x; do + t="$t$x " + done + echo $t +} + +multiline() +{ + while read x; do + set -- $x + for d in "$@"; do + echo "$d" + done + done +} + +undo() +{ + git reset --hard HEAD~ +} + +last_commit_message() +{ + git log --pretty=format:%s -1 +} + +rm -rf mainline subproj +mkdir mainline subproj + +cd subproj +git init + +create sub1 +git commit -m 'sub1' +git branch sub1 +git branch -m master subproj +check true + +create sub2 +git commit -m 'sub2' +git branch sub2 + +create sub3 +git commit -m 'sub3' +git branch sub3 + +cd ../mainline +git init +create main4 +git commit -m 'main4' +git branch -m master mainline +git branch subdir + +git fetch ../subproj sub1 +git branch sub1 FETCH_HEAD + +# check if --message works for add +check_not git subtree merge --prefix=subdir sub1 +check_not git subtree pull --prefix=subdir ../subproj sub1 +git subtree add --prefix=subdir --message="Added subproject" sub1 +check_equal "$(last_commit_message)" "Added subproject" +undo + +# 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 + +# 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 + +git subtree add --prefix=subdir/ FETCH_HEAD +check_equal "$(last_commit_message)" "Add 'subdir/' from commit '$(git rev-parse sub1)'" + +# this shouldn't actually do anything, since FETCH_HEAD is already a parent +git merge -m 'merge -s -ours' -s ours FETCH_HEAD + +create subdir/main-sub5 +git commit -m 'main-sub5' + +create main6 +git commit -m 'main6 boring' + +create subdir/main-sub7 +git commit -m 'main-sub7' + +git fetch ../subproj sub2 +git branch sub2 FETCH_HEAD + +# 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 + +# 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 + +git subtree merge --prefix=subdir FETCH_HEAD +git branch pre-split +check_equal "$(last_commit_message)" "Merge commit '$(git rev-parse sub2)' into mainline" + +# Check that prefix argument is required for split (exits with warning and exit status = 1) +! result=$(git subtree split 2>&1) +check_equal "You must provide the --prefix option." "$result" + +# Check that the exists for a split. +! result=$(git subtree split --prefix=non-existent-directory 2>&1) +check_equal "'non-existent-directory' does not exist; use 'git subtree add'" \ + "$result" + +# check if --message works for split+rejoin +spl1=$(git subtree split --annotate='*' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) +echo "spl1={$spl1}" +git branch spl1 "$spl1" +check_equal "$(last_commit_message)" "Split & rejoin" +undo + +# check split with --branch +git subtree split --annotate='*' --prefix subdir --onto FETCH_HEAD --branch splitbr1 +check_equal "$(git rev-parse splitbr1)" "$spl1" + +# check split with --branch for an existing branch +git branch splitbr2 sub1 +git subtree split --annotate='*' --prefix subdir --onto FETCH_HEAD --branch splitbr2 +check_equal "$(git rev-parse splitbr2)" "$spl1" + +# check split with --branch for an incompatible branch +result=$(git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir || echo "caught error") +check_equal "$result" "caught error" + + +git subtree split --annotate='*' --prefix subdir --onto FETCH_HEAD --rejoin +check_equal "$(last_commit_message)" "Split 'subdir/' into commit '$spl1'" + +create subdir/main-sub8 +git commit -m 'main-sub8' + +cd ../subproj +git fetch ../mainline spl1 +git branch spl1 FETCH_HEAD +git merge FETCH_HEAD + +create sub9 +git commit -m 'sub9' + +cd ../mainline +split2=$(git subtree split --annotate='*' --prefix subdir/ --rejoin) +git branch split2 "$split2" + +create subdir/main-sub10 +git commit -m 'main-sub10' + +spl3=$(git subtree split --annotate='*' --prefix subdir --rejoin) +git branch spl3 "$spl3" + +cd ../subproj +git fetch ../mainline 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) + +# make sure exactly the right set of files ends up in the subproj +subfiles=$(git ls-files | fixnl) +check_equal "$subfiles" "$chkms $chks" + +# 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" + +cd ../mainline +git fetch ../subproj subproj-merge-spl3 +git branch subproj-merge-spl3 FETCH_HEAD +git subtree pull --prefix=subdir ../subproj subproj-merge-spl3 + +# 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" + +# 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)" + +# make sure the --rejoin commits never make it into subproj +check_equal "$(git log --pretty=format:'%s' HEAD^2 | grep -i split)" "" + +# make sure no 'git subtree' tagged commits make it into subproj. (They're +# 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.*:')" "" + + +# check if split can find proper base without --onto +# prepare second pair of repositories +mkdir test2 +cd test2 + +mkdir main +cd main +git init +create main1 +git commit -m "main1" + +cd .. +mkdir sub +cd sub +git init +create sub2 +git commit -m "sub2" + +cd ../main +git fetch ../sub master +git branch sub2 FETCH_HEAD +git subtree add --prefix subdir sub2 + +cd ../sub +create sub3 +git commit -m "sub3" + +cd ../main +git fetch ../sub master +git branch sub3 FETCH_HEAD +git subtree merge --prefix subdir sub3 + +create subdir/main-sub4 +git commit -m "main-sub4" +git subtree split --prefix subdir --branch mainsub4 + +# at this point, the new commit's parent should be sub3 +# if it's not, something went wrong (the "newparent" of "master~" commit should have been sub3, +# but it wasn't, because it's cache was not set to itself) +check_equal "$(git log --pretty=format:%P -1 mainsub4)" "$(git rev-parse sub3)" + +mkdir subdir2 +create subdir2/main-sub5 +git commit -m "main-sub5" +git subtree split --prefix subdir2 --branch mainsub5 + +# also test that we still can split out an entirely new subtree +# if the parent of the first commit in the tree isn't empty, +# then the new subtree has accidently been attached to something +check_equal "$(git log --pretty=format:%P -1 mainsub5)" "" + + +# 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" +} +x= +git log --pretty=format:'commit: %H' | joincommits | +( while read commit a b; do + echo "Verifying commit $commit" + check_equal "$b" "" + x=1 + done + check_equal "$x" 1 +) || exit 1 + +# Return to mainline +cd ../.. + + +# from-submodule + +make_submodule() +{ + cd .. + local prefix=$1 + + local submodule_path=$(mktemp -d submodule.XXX) + cd $submodule_path + local submodule_abs_path=$(pwd) + + git init > /dev/null + create "sub-file" + git commit -m "sub-commit" > /dev/null + local submodule_sha=$(git rev-parse HEAD) + cd ../mainline + + git submodule add $submodule_abs_path $prefix > /dev/null + git submodule update --init > /dev/null + git commit -m "Added $prefix." > /dev/null + + echo $submodule_sha +} + +# Unfortunately the following submodule tests were broken when I found them: +# +# subA_sha=$(make_submodule submodules/subA) +# +# git subtree from-submodule --prefix submodules/subA +# +# check_equal "$(last_commit_message)" "Add 'submodules/subA/' from commit '${subA_sha}'" +# # Submodule should be gone. +# check_equal "$(git submodule status)" "" +# +# rm -rf ../submodule.* + + +# Simple subtree add-and-immediately-push test: +echo +echo '*** TESTING add subtree and immediately push' +echo + +cd .. # return to test dir top level +rm -rf mainline2 +git init mainline2 +cd mainline2 +date > mainline2.txt && git add . && git commit -m "initial mainline2 commit" +git subtree add --prefix=subproj ../subproj subproj +git subtree push --prefix=subproj ../subproj subproj +result=$? +cd .. +check_equal 0 "$result" + +echo +echo '*** PASSED add subtree and immediately push' +echo + +echo '*** ALL TESTS PASSED ***'