{"thread":{"id":"19251","subject":"[PATCH/RFC RESEND 0/2] git subtree: an alternative to git submodule","startedAt":"2009-05-08T22:39:07Z","lastAt":"2009-05-19T16:13:41Z","messageCount":11,"participants":["Avery Pennarun","Junio C Hamano","Ping Yin","Jakub Narebski"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"113376","messageId":"1241822349-27470-1-git-send-email-apenwarr@gmail.com","threadId":"19251","inReplyTo":null,"subject":"[PATCH/RFC RESEND 0/2] git subtree: an alternative to git submodule","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2009-05-08T22:39:07Z","receivedAt":"2009-05-08T22:39:07Z","isPatch":true,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"Hi all,\n\nI first sent out this patch set a couple of weeks ago\n(http://article.gmane.org/gmane.comp.version-control.git/117612) and got a\ncouple of positive comments, but no negative ones, so I'm guessing people\nhaven't reviewed it as closely as I would have hoped :)\n\ngit subtree has these subcommands:\n\n - add: connect a given commit to a given subtree, basically using the\n   occasionally-documented 'git read-tree --prefix; git commit' trick.\n\n - merge: a user-friendlier form of 'git merge -s subtree'\n\n - pull: likewise, but for git pull\n\n - split: (the magical part!) generate a new commit series from the given\n   prefix, so you can submit subtree changes *back* to the upstream project\n   you merged in the first place. \n\nDoes anyone have any comments on what it would take to get the git subtree\nstuff accepted into git?\n\nThanks,\n\nAvery\n\n\nAvery Pennarun (2):\n  Add 'git subtree' command for tracking history of subtrees\n    separately.\n  Automated test script for 'git subtree'.\n\n .gitignore       |    1 +\n Makefile         |    1 +\n command-list.txt |    1 +\n git-subtree.sh   |  435 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n subtree-test.sh  |  206 ++++++++++++++++++++++++++\n 5 files changed, 644 insertions(+), 0 deletions(-)\n create mode 100755 git-subtree.sh\n create mode 100755 subtree-test.sh\n"},{"id":"113377","messageId":"1241822349-27470-2-git-send-email-apenwarr@gmail.com","threadId":"19251","inReplyTo":"1241822349-27470-1-git-send-email-apenwarr@gmail.com","subject":"[PATCH/RFC RESEND 1/2] Add 'git subtree' command for tracking history of subtrees separately.","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2009-05-08T22:39:08Z","receivedAt":"2009-05-08T22:39:08Z","isPatch":true,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"Many projects are made of a combination of several subprojects/libraries and\nsome application-specific code.  In some cases, particularly when the\nsubprojects are all maintained independently, 'git submodule' is the best\nway to deal with this situation.  But if you frequently change the\nsubprojects as part of developing your application, use multiple branches,\nand sometimes want to push your subproject changes upstream, the overhead of\nmanually managing submodules can be excessive.\n\n'git subtree' provides an alternative mechanism, based around the\n'git merge -s subtree' merge strategy.  Instead of tracking a submodule\nseparately, you merge its history into your main project, and occasionally\nextract a new \"virtual history\" from your mainline that can be easily merged\nback into the upstream project.  The virtual history can be incrementally\nexpanded as you make more changes to the superproject.\n\nYou would normally then merge the virtual history back into your mainline\n(the --rejoin option). This results in extra commits in your application\nthat appear to change the same files, but these extra commits will tend to\nbe ignored by git's merge simplification algorithm anyway.\n\nFor example, gitweb (commit 1130ef3) was merged into git as of commit\n0a8f4f0, after which it was no longer maintained separately.  But imagine it\nhad been maintained separately, and we wanted to extract git's changes to\ngitweb since that time, to share with the upstream.  You could do this:\n\ngit subtree split --prefix=gitweb --annotate='(split) ' \\\n\t0a8f4f0^.. --onto=1130ef3 --rejoin\n\nIf gitweb had originally been merged using 'git subtree add' (or a previous\nsplit had been done with --rejoin specified), then you could incrementally\nproduce the list of new changes without needing to remember any commit ids:\n\ngit subtree split --prefix=gitweb --annotate='(split) ' --rejoin\n---\n .gitignore       |    1 +\n Makefile         |    1 +\n command-list.txt |    1 +\n git-subtree.sh   |  435 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 438 insertions(+), 0 deletions(-)\n create mode 100755 git-subtree.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 41c0b20..6f0bc34 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -127,6 +127,7 @@ git-stash\n git-status\n git-stripspace\n git-submodule\n+git-subtree\n git-svn\n git-symbolic-ref\n git-tag\ndiff --git a/Makefile b/Makefile\nindex 6e21643..02d72a5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -305,6 +305,7 @@ SCRIPT_SH += git-request-pull.sh\n SCRIPT_SH += git-sh-setup.sh\n SCRIPT_SH += git-stash.sh\n SCRIPT_SH += git-submodule.sh\n+SCRIPT_SH += git-subtree.sh\n SCRIPT_SH += git-web--browse.sh\n \n SCRIPT_PERL += git-add--interactive.perl\ndiff --git a/command-list.txt b/command-list.txt\nindex fb03a2e..9be4774 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -113,6 +113,7 @@ git-stash                               mainporcelain\n git-status                              mainporcelain common\n git-stripspace                          purehelpers\n git-submodule                           mainporcelain\n+git-subtree                             mainporcelain\n git-svn                                 foreignscminterface\n git-symbolic-ref                        plumbingmanipulators\n git-tag                                 mainporcelain common\ndiff --git a/git-subtree.sh b/git-subtree.sh\nnew file mode 100755\nindex 0000000..39c377c\n--- /dev/null\n+++ b/git-subtree.sh\n@@ -0,0 +1,435 @@\n+#!/bin/bash\n+#\n+# git-subtree.sh: split/join git repositories in subdirectories of this one\n+#\n+# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>\n+#\n+if [ $# -eq 0 ]; then\n+    set -- -h\n+fi\n+OPTS_SPEC=\"\\\n+git subtree add --prefix=<prefix> <commit>\n+git subtree split [options...] --prefix=<prefix> <commit...>\n+git subtree merge --prefix=<prefix> <commit>\n+git subtree pull  --prefix=<prefix> <repository> <refspec...>\n+--\n+h,help        show the help\n+q             quiet\n+d             show debug messages\n+prefix=       the name of the subdir to split out\n+ options for 'split'\n+annotate=     add a prefix to commit message of new commits\n+onto=         try connecting new tree to an existing one\n+rejoin        merge the new branch back into HEAD\n+ignore-joins  ignore prior --rejoin commits\n+\"\n+eval $(echo \"$OPTS_SPEC\" | git rev-parse --parseopt -- \"$@\" || echo exit $?)\n+. git-sh-setup\n+require_work_tree\n+\n+quiet=\n+debug=\n+command=\n+onto=\n+rejoin=\n+ignore_joins=\n+annotate=\n+\n+debug()\n+{\n+\tif [ -n \"$debug\" ]; then\n+\t\techo \"$@\" >&2\n+\tfi\n+}\n+\n+say()\n+{\n+\tif [ -z \"$quiet\" ]; then\n+\t\techo \"$@\" >&2\n+\tfi\n+}\n+\n+assert()\n+{\n+\tif \"$@\"; then\n+\t\t:\n+\telse\n+\t\tdie \"assertion failed: \" \"$@\"\n+\tfi\n+}\n+\n+\n+#echo \"Options: $*\"\n+\n+while [ $# -gt 0 ]; do\n+\topt=\"$1\"\n+\tshift\n+\tcase \"$opt\" in\n+\t\t-q) quiet=1 ;;\n+\t\t-d) debug=1 ;;\n+\t\t--annotate) annotate=\"$1\"; shift ;;\n+\t\t--no-annotate) annotate= ;;\n+\t\t--prefix) prefix=\"$1\"; shift ;;\n+\t\t--no-prefix) prefix= ;;\n+\t\t--onto) onto=\"$1\"; shift ;;\n+\t\t--no-onto) onto= ;;\n+\t\t--rejoin) rejoin=1 ;;\n+\t\t--no-rejoin) rejoin= ;;\n+\t\t--ignore-joins) ignore_joins=1 ;;\n+\t\t--no-ignore-joins) ignore_joins= ;;\n+\t\t--) break ;;\n+\tesac\n+done\n+\n+command=\"$1\"\n+shift\n+case \"$command\" in\n+\tadd|merge|pull) default= ;;\n+\tsplit) default=\"--default HEAD\" ;;\n+\t*) die \"Unknown command '$command'\" ;;\n+esac\n+\n+if [ -z \"$prefix\" ]; then\n+\tdie \"You must provide the --prefix option.\"\n+fi\n+dir=\"$prefix\"\n+\n+if [ \"$command\" != \"pull\" ]; then\n+\trevs=$(git rev-parse $default --revs-only \"$@\") || exit $?\n+\tdirs=\"$(git rev-parse --no-revs --no-flags \"$@\")\" || exit $?\n+\tif [ -n \"$dirs\" ]; then\n+\t\tdie \"Error: Use --prefix instead of bare filenames.\"\n+\tfi\n+fi\n+\n+debug \"command: {$command}\"\n+debug \"quiet: {$quiet}\"\n+debug \"revs: {$revs}\"\n+debug \"dir: {$dir}\"\n+debug \"opts: {$*}\"\n+debug\n+\n+cache_setup()\n+{\n+\tcachedir=\"$GIT_DIR/subtree-cache/$$\"\n+\trm -rf \"$cachedir\" || die \"Can't delete old cachedir: $cachedir\"\n+\tmkdir -p \"$cachedir\" || die \"Can't create new cachedir: $cachedir\"\n+\tdebug \"Using cachedir: $cachedir\" >&2\n+}\n+\n+cache_get()\n+{\n+\tfor oldrev in $*; do\n+\t\tif [ -r \"$cachedir/$oldrev\" ]; then\n+\t\t\tread newrev <\"$cachedir/$oldrev\"\n+\t\t\techo $newrev\n+\t\tfi\n+\tdone\n+}\n+\n+cache_set()\n+{\n+\toldrev=\"$1\"\n+\tnewrev=\"$2\"\n+\tif [ \"$oldrev\" != \"latest_old\" \\\n+\t     -a \"$oldrev\" != \"latest_new\" \\\n+\t     -a -e \"$cachedir/$oldrev\" ]; then\n+\t\tdie \"cache for $oldrev already exists!\"\n+\tfi\n+\techo \"$newrev\" >\"$cachedir/$oldrev\"\n+}\n+\n+# if a commit doesn't have a parent, this might not work.  But we only want\n+# to remove the parent from the rev-list, and since it doesn't exist, it won't\n+# be there anyway, so do nothing in that case.\n+try_remove_previous()\n+{\n+\tif git rev-parse \"$1^\" >/dev/null 2>&1; then\n+\t\techo \"^$1^\"\n+\tfi\n+}\n+\n+find_existing_splits()\n+{\n+\tdebug \"Looking for prior splits...\"\n+\tdir=\"$1\"\n+\trevs=\"$2\"\n+\tgit log --grep=\"^git-subtree-dir: $dir\\$\" \\\n+\t\t--pretty=format:'%s%n%n%b%nEND' $revs |\n+\twhile read a b junk; do\n+\t\tcase \"$a\" in\n+\t\t\tgit-subtree-mainline:) main=\"$b\" ;;\n+\t\t\tgit-subtree-split:) sub=\"$b\" ;;\n+\t\t\t*)\n+\t\t\t\tif [ -n \"$main\" -a -n \"$sub\" ]; then\n+\t\t\t\t\tdebug \"  Prior: $main -> $sub\"\n+\t\t\t\t\tcache_set $main $sub\n+\t\t\t\t\ttry_remove_previous \"$main\"\n+\t\t\t\t\ttry_remove_previous \"$sub\"\n+\t\t\t\t\tmain=\n+\t\t\t\t\tsub=\n+\t\t\t\tfi\n+\t\t\t\t;;\n+\t\tesac\n+\tdone\n+}\n+\n+copy_commit()\n+{\n+\t# We're doing to set some environment vars here, so\n+\t# do it in a subshell to get rid of them safely later\n+\tdebug copy_commit \"{$1}\" \"{$2}\" \"{$3}\"\n+\tgit log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' \"$1\" |\n+\t(\n+\t\tread GIT_AUTHOR_NAME\n+\t\tread GIT_AUTHOR_EMAIL\n+\t\tread GIT_AUTHOR_DATE\n+\t\tread GIT_COMMITTER_NAME\n+\t\tread GIT_COMMITTER_EMAIL\n+\t\tread GIT_COMMITTER_DATE\n+\t\texport  GIT_AUTHOR_NAME \\\n+\t\t\tGIT_AUTHOR_EMAIL \\\n+\t\t\tGIT_AUTHOR_DATE \\\n+\t\t\tGIT_COMMITTER_NAME \\\n+\t\t\tGIT_COMMITTER_EMAIL \\\n+\t\t\tGIT_COMMITTER_DATE\n+\t\t(echo -n \"$annotate\"; cat ) |\n+\t\tgit commit-tree \"$2\" $3  # reads the rest of stdin\n+\t) || die \"Can't copy commit $1\"\n+}\n+\n+add_msg()\n+{\n+\tdir=\"$1\"\n+\tlatest_old=\"$2\"\n+\tlatest_new=\"$3\"\n+\tcat <<-EOF\n+\t\tAdd '$dir/' from commit '$latest_new'\n+\t\t\n+\t\tgit-subtree-dir: $dir\n+\t\tgit-subtree-mainline: $latest_old\n+\t\tgit-subtree-split: $latest_new\n+\tEOF\n+}\n+\n+merge_msg()\n+{\n+\tdir=\"$1\"\n+\tlatest_old=\"$2\"\n+\tlatest_new=\"$3\"\n+\tcat <<-EOF\n+\t\tSplit '$dir/' into commit '$latest_new'\n+\t\t\n+\t\tgit-subtree-dir: $dir\n+\t\tgit-subtree-mainline: $latest_old\n+\t\tgit-subtree-split: $latest_new\n+\tEOF\n+}\n+\n+toptree_for_commit()\n+{\n+\tcommit=\"$1\"\n+\tgit log -1 --pretty=format:'%T' \"$commit\" -- || exit $?\n+}\n+\n+subtree_for_commit()\n+{\n+\tcommit=\"$1\"\n+\tdir=\"$2\"\n+\tgit ls-tree \"$commit\" -- \"$dir\" |\n+\twhile read mode type tree name; do\n+\t\tassert [ \"$name\" = \"$dir\" ]\n+\t\techo $tree\n+\t\tbreak\n+\tdone\n+}\n+\n+tree_changed()\n+{\n+\ttree=$1\n+\tshift\n+\tif [ $# -ne 1 ]; then\n+\t\treturn 0   # weird parents, consider it changed\n+\telse\n+\t\tptree=$(toptree_for_commit $1)\n+\t\tif [ \"$ptree\" != \"$tree\" ]; then\n+\t\t\treturn 0   # changed\n+\t\telse\n+\t\t\treturn 1   # not changed\n+\t\tfi\n+\tfi\n+}\n+\n+copy_or_skip()\n+{\n+\trev=\"$1\"\n+\ttree=\"$2\"\n+\tnewparents=\"$3\"\n+\tassert [ -n \"$tree\" ]\n+\n+\tidentical=\n+\tnonidentical=\n+\tp=\n+\tgotparents=\n+\tfor parent in $newparents; do\n+\t\tptree=$(toptree_for_commit $parent) || exit $?\n+\t\t[ -z \"$ptree\" ] && continue\n+\t\tif [ \"$ptree\" = \"$tree\" ]; then\n+\t\t\t# an identical parent could be used in place of this rev.\n+\t\t\tidentical=\"$parent\"\n+\t\telse\n+\t\t\tnonidentical=\"$parent\"\n+\t\tfi\n+\t\t\n+\t\t# sometimes both old parents map to the same newparent;\n+\t\t# eliminate duplicates\n+\t\tis_new=1\n+\t\tfor gp in $gotparents; do\n+\t\t\tif [ \"$gp\" = \"$parent\" ]; then\n+\t\t\t\tis_new=\n+\t\t\t\tbreak\n+\t\t\tfi\n+\t\tdone\n+\t\tif [ -n \"$is_new\" ]; then\n+\t\t\tgotparents=\"$gotparents $parent\"\n+\t\t\tp=\"$p -p $parent\"\n+\t\tfi\n+\tdone\n+\t\n+\tif [ -n \"$identical\" ]; then\n+\t\techo $identical\n+\telse\n+\t\tcopy_commit $rev $tree \"$p\" || exit $?\n+\tfi\n+}\n+\n+ensure_clean()\n+{\n+\tif ! git diff-index HEAD --exit-code --quiet; then\n+\t\tdie \"Working tree has modifications.  Cannot add.\"\n+\tfi\n+\tif ! git diff-index --cached HEAD --exit-code --quiet; then\n+\t\tdie \"Index has modifications.  Cannot add.\"\n+\tfi\n+}\n+\n+cmd_add()\n+{\n+\tif [ -e \"$dir\" ]; then\n+\t\tdie \"'$dir' already exists.  Cannot add.\"\n+\tfi\n+\tensure_clean\n+\t\n+\tset -- $revs\n+\tif [ $# -ne 1 ]; then\n+\t\tdie \"You must provide exactly one revision.  Got: '$revs'\"\n+\tfi\n+\trev=\"$1\"\n+\t\n+\tdebug \"Adding $dir as '$rev'...\"\n+\tgit read-tree --prefix=\"$dir\" $rev || exit $?\n+\tgit checkout \"$dir\" || exit $?\n+\ttree=$(git write-tree) || exit $?\n+\t\n+\theadrev=$(git rev-parse HEAD) || exit $?\n+\tif [ -n \"$headrev\" -a \"$headrev\" != \"$rev\" ]; then\n+\t\theadp=\"-p $headrev\"\n+\telse\n+\t\theadp=\n+\tfi\n+\tcommit=$(add_msg \"$dir\" \"$headrev\" \"$rev\" |\n+\t\t git commit-tree $tree $headp -p \"$rev\") || exit $?\n+\tgit reset \"$commit\" || exit $?\n+}\n+\n+cmd_split()\n+{\n+\tdebug \"Splitting $dir...\"\n+\tcache_setup || exit $?\n+\t\n+\tif [ -n \"$onto\" ]; then\n+\t\tdebug \"Reading history for --onto=$onto...\"\n+\t\tgit rev-list $onto |\n+\t\twhile read rev; do\n+\t\t\t# the 'onto' history is already just the subdir, so\n+\t\t\t# any parent we find there can be used verbatim\n+\t\t\tdebug \"  cache: $rev\"\n+\t\t\tcache_set $rev $rev\n+\t\tdone\n+\tfi\n+\t\n+\tif [ -n \"$ignore_joins\" ]; then\n+\t\tunrevs=\n+\telse\n+\t\tunrevs=\"$(find_existing_splits \"$dir\" \"$revs\")\"\n+\tfi\n+\t\n+\t# We can't restrict rev-list to only $dir here, because some of our\n+\t# parents have the $dir contents the root, and those won't match.\n+\t# (and rev-list --follow doesn't seem to solve this)\n+\tgrl='git rev-list --reverse --parents $revs $unrevs'\n+\trevmax=$(eval \"$grl\" | wc -l)\n+\trevcount=0\n+\tcreatecount=0\n+\teval \"$grl\" |\n+\twhile read rev parents; do\n+\t\trevcount=$(($revcount + 1))\n+\t\tsay -n \"$revcount/$revmax ($createcount)\n\"\n+\t\tdebug \"Processing commit: $rev\"\n+\t\texists=$(cache_get $rev)\n+\t\tif [ -n \"$exists\" ]; then\n+\t\t\tdebug \"  prior: $exists\"\n+\t\t\tcontinue\n+\t\tfi\n+\t\tcreatecount=$(($createcount + 1))\n+\t\tdebug \"  parents: $parents\"\n+\t\tnewparents=$(cache_get $parents)\n+\t\tdebug \"  newparents: $newparents\"\n+\t\t\n+\t\ttree=$(subtree_for_commit $rev \"$dir\")\n+\t\tdebug \"  tree is: $tree\"\n+\t\t[ -z $tree ] && continue\n+\n+\t\tnewrev=$(copy_or_skip \"$rev\" \"$tree\" \"$newparents\") || exit $?\n+\t\tdebug \"  newrev is: $newrev\"\n+\t\tcache_set $rev $newrev\n+\t\tcache_set latest_new $newrev\n+\t\tcache_set latest_old $rev\n+\tdone || exit $?\n+\tlatest_new=$(cache_get latest_new)\n+\tif [ -z \"$latest_new\" ]; then\n+\t\tdie \"No new revisions were found\"\n+\tfi\n+\t\n+\tif [ -n \"$rejoin\" ]; then\n+\t\tdebug \"Merging split branch into HEAD...\"\n+\t\tlatest_old=$(cache_get latest_old)\n+\t\tgit merge -s ours \\\n+\t\t\t-m \"$(merge_msg $dir $latest_old $latest_new)\" \\\n+\t\t\t$latest_new >&2\n+\tfi\n+\techo $latest_new\n+\texit 0\n+}\n+\n+cmd_merge()\n+{\n+\tensure_clean\n+\t\n+\tset -- $revs\n+\tif [ $# -ne 1 ]; then\n+\t\tdie \"You must provide exactly one revision.  Got: '$revs'\"\n+\tfi\n+\trev=\"$1\"\n+\t\n+\tgit merge -s subtree $rev\n+}\n+\n+cmd_pull()\n+{\n+\tensure_clean\n+\tset -x\n+\tgit pull -s subtree \"$@\"\n+}\n+\n+\"cmd_$command\" \"$@\"\n-- \n1.6.3.2.g3d624\n"},{"id":"113378","messageId":"1241822349-27470-3-git-send-email-apenwarr@gmail.com","threadId":"19251","inReplyTo":"1241822349-27470-2-git-send-email-apenwarr@gmail.com","subject":"[PATCH/RFC RESEND 2/2] Automated test script for 'git subtree'.","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2009-05-08T22:39:09Z","receivedAt":"2009-05-08T22:39:09Z","isPatch":true,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"TEMPORARY: this script hasn't yet been integrated into the main git unit\ntests; it runs standalone for the moment.\n---\n subtree-test.sh |  206 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 206 insertions(+), 0 deletions(-)\n create mode 100755 subtree-test.sh\n\ndiff --git a/subtree-test.sh b/subtree-test.sh\nnew file mode 100755\nindex 0000000..38dff7a\n--- /dev/null\n+++ b/subtree-test.sh\n@@ -0,0 +1,206 @@\n+#!/bin/bash\n+. shellopts.sh\n+set -e\n+\n+create()\n+{\n+\techo \"$1\" >\"$1\"\n+\tgit add \"$1\"\n+}\n+\n+check()\n+{\n+\techo\n+\techo \"check:\" \"$@\"\n+\tif \"$@\"; then\n+\t\techo ok\n+\t\treturn 0\n+\telse\n+\t\techo FAILED\n+\t\texit 1\n+\tfi\n+}\n+\n+check_equal()\n+{\n+\techo\n+\techo \"check a:\" \"{$1}\"\n+\techo \"      b:\" \"{$2}\"\n+\tif [ \"$1\" = \"$2\" ]; then\n+\t\treturn 0\n+\telse\n+\t\techo FAILED\n+\t\texit 1\n+\tfi\n+}\n+\n+fixnl()\n+{\t\n+\tt=\"\"\n+\twhile read x; do\n+\t\tt=\"$t$x \"\n+\tdone\n+\techo $t\n+}\n+\n+multiline()\n+{\n+\twhile read x; do\n+\t\tset -- $x\n+\t\tfor d in \"$@\"; do\n+\t\t\techo \"$d\"\n+\t\tdone\n+\tdone\n+}\n+\n+rm -rf mainline subproj\n+mkdir mainline subproj\n+\n+cd subproj\n+git init\n+\n+create sub1\n+git commit -m 'sub1'\n+git branch sub1\n+git branch -m master subproj\n+check true\n+\n+create sub2\n+git commit -m 'sub2'\n+git branch sub2\n+\n+create sub3\n+git commit -m 'sub3'\n+git branch sub3\n+\n+cd ../mainline\n+git init\n+create main4\n+git commit -m 'main4'\n+git branch -m master mainline\n+\n+git fetch ../subproj sub1\n+git branch sub1 FETCH_HEAD\n+git subtree add --prefix=subdir FETCH_HEAD\n+\n+# this shouldn't actually do anything, since FETCH_HEAD is already a parent\n+git merge -m 'merge -s -ours' -s ours FETCH_HEAD\n+\n+create subdir/main-sub5\n+git commit -m 'main-sub5'\n+\n+create main6\n+git commit -m 'main6 boring'\n+\n+create subdir/main-sub7\n+git commit -m 'main-sub7'\n+\n+git fetch ../subproj sub2\n+git branch sub2 FETCH_HEAD\n+git subtree merge --prefix=subdir FETCH_HEAD\n+git branch pre-split\n+\n+spl1=$(git subtree split --annotate='*' \\\n+\t\t--prefix subdir --onto FETCH_HEAD --rejoin)\n+echo \"spl1={$spl1}\"\n+git branch spl1 \"$spl1\"\n+\n+create subdir/main-sub8\n+git commit -m 'main-sub8'\n+\n+cd ../subproj\n+git fetch ../mainline spl1\n+git branch spl1 FETCH_HEAD\n+git merge FETCH_HEAD\n+\n+create sub9\n+git commit -m 'sub9'\n+\n+cd ../mainline\n+split2=$(git subtree split --annotate='*' --prefix subdir --rejoin)\n+git branch split2 \"$split2\"\n+\n+create subdir/main-sub10\n+git commit -m 'main-sub10'\n+\n+spl3=$(git subtree split --annotate='*' --prefix subdir --rejoin)\n+git branch spl3 \"$spl3\"\n+\n+cd ../subproj\n+git fetch ../mainline spl3\n+git branch spl3 FETCH_HEAD\n+git merge FETCH_HEAD\n+git branch subproj-merge-spl3\n+\n+chkm=\"main4 main6\"\n+chkms=\"main-sub10 main-sub5 main-sub7 main-sub8\"\n+chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)\n+chks=\"sub1 sub2 sub3 sub9\"\n+chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)\n+\n+# make sure exactly the right set of files ends up in the subproj\n+subfiles=$(git ls-files | fixnl)\n+check_equal \"$subfiles\" \"$chkms $chks\"\n+\n+# make sure the subproj history *only* contains commits that affect the subdir.\n+allchanges=$(git log --name-only --pretty=format:'' | sort | fixnl)\n+check_equal \"$allchanges\" \"$chkms $chks\"\n+\n+cd ../mainline\n+git fetch ../subproj subproj-merge-spl3\n+git branch subproj-merge-spl3 FETCH_HEAD\n+git subtree pull --prefix=subdir ../subproj subproj-merge-spl3\n+\n+# make sure exactly the right set of files ends up in the mainline\n+mainfiles=$(git ls-files | fixnl)\n+check_equal \"$mainfiles\" \"$chkm $chkms_sub $chks_sub\"\n+\n+# make sure each filename changed exactly once in the entire history.\n+# 'main-sub??' and '/subdir/main-sub??' both change, because those are the\n+# changes that were split into their own history.  And 'subdir/sub??' never\n+# change, since they were *only* changed in the subtree branch.\n+allchanges=$(git log --name-only --pretty=format:'' | sort | fixnl)\n+check_equal \"$allchanges\" \"$chkm $chkms $chks $chkms_sub\"\n+\n+# make sure the --rejoin commits never make it into subproj\n+check_equal \"$(git log --pretty=format:'%s' HEAD^2 | grep -i split)\" \"\"\n+\n+# make sure no 'git subtree' tagged commits make it into subproj. (They're\n+# meaningless to subproj since one side of the merge refers to the mainline)\n+check_equal \"$(git log --pretty=format:'%s%n%b' HEAD^2 | grep 'git-subtree.*:')\" \"\"\n+\n+# make sure no patch changes more than one file.  The original set of commits\n+# changed only one file each.  A multi-file change would imply that we pruned\n+# commits too aggressively.\n+joincommits()\n+{\n+\tcommit=\n+\tall=\n+\twhile read x y; do\n+\t\techo \"{$x}\" >&2\n+\t\tif [ -z \"$x\" ]; then\n+\t\t\tcontinue\n+\t\telif [ \"$x\" = \"commit:\" ]; then\n+\t\t\tif [ -n \"$commit\" ]; then\n+\t\t\t\techo \"$commit $all\"\n+\t\t\t\tall=\n+\t\t\tfi\n+\t\t\tcommit=\"$y\"\n+\t\telse\n+\t\t\tall=\"$all $y\"\n+\t\tfi\n+\tdone\n+\techo \"$commit $all\"\n+}\n+x=\n+git log --pretty=format:'commit: %H' | joincommits |\n+(\twhile read commit a b; do\n+\t\techo \"Verifying commit $commit\"\n+\t\tcheck_equal \"$b\" \"\"\n+\t\tx=1\n+\tdone\n+\tcheck_equal \"$x\" 1\n+) || exit 1\n+\n+echo\n+echo 'ok'\n-- \n1.6.3.2.g3d624\n"},{"id":"113999","messageId":"32541b130905150909h7e596f26w7db6887e7f4267ff@mail.gmail.com","threadId":"19251","inReplyTo":"1241822349-27470-1-git-send-email-apenwarr@gmail.com","subject":"Re: git subtree: an alternative to git submodule","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2009-05-15T16:09:20Z","receivedAt":"2009-05-15T16:09:20Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Fri, May 8, 2009 at 6:39 PM, Avery Pennarun <apenwarr@gmail.com> wrote:\n> I first sent out this patch set a couple of weeks ago\n> (http://article.gmane.org/gmane.comp.version-control.git/117612) and got a\n> couple of positive comments, but no negative ones, so I'm guessing people\n> haven't reviewed it as closely as I would have hoped :)\n>\n> git subtree has these subcommands:\n>\n>  - add: connect a given commit to a given subtree, basically using the\n>   occasionally-documented 'git read-tree --prefix; git commit' trick.\n>\n>  - merge: a user-friendlier form of 'git merge -s subtree'\n>\n>  - pull: likewise, but for git pull\n>\n>  - split: (the magical part!) generate a new commit series from the given\n>   prefix, so you can submit subtree changes *back* to the upstream project\n>   you merged in the first place.\n>\n> Does anyone have any comments on what it would take to get the git subtree\n> stuff accepted into git?\n\nHi all,\n\nJust checking in again.  Since I originally announced git-subtree, it\n(or rather the concept) has received a bit of positive feedback out in\nthe wild.  My original blog post about it:\nhttp://alumnit.ca/~apenwarr/log/?m=200904#30\n\nThe heroku mailing list:\nhttp://groups.google.com/group/heroku/browse_thread/thread/5e6807fcd2572f64\n\nYcombinator news: http://news.ycombinator.com/item?id=604405\nAnd again: http://news.ycombinator.com/item?id=604889\n\nMeanwhile, I've been keeping it in a separate git repo here:\nhttp://github.com/apenwarr/git-subtree/commits/master (that's a web\nlink, not a git link).\n\nThe common thread in all these discussions is along the lines of,\n\"This sounds cool!  I didn't try it though.  Maybe I should!\"\nCommenters then seem to disappear into a black hole.  The black hole\nof my code!  Bwahahaha!  Sigh.\n\nI would love to hear some feedback about this, particularly for the\nquestion of what needs to be done to get it adopted into git's\nmainline, or if it's in fact so evil that this is unlikely to ever\nhappen.  Obviously I would need to write a man page, but I've been\nhesitant to do that in case people have suggestions that need the\nwhole UI to change.  Perhaps that's a chicken-and-egg problem, though,\nand I should just get on with writing it.\n\nFlame away!\n\nThanks,\n\nAvery\n\nP.S. Dscho is usually really good at flaming me for being stupid, but\nhe has been strangely silent on this topic so far.  Don't miss this\nexciting opportunity!\n"},{"id":"114010","messageId":"7vzldes0ce.fsf@alter.siamese.dyndns.org","threadId":"19251","inReplyTo":"32541b130905150909h7e596f26w7db6887e7f4267ff@mail.gmail.com","subject":"Re: git subtree: an alternative to git submodule","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-05-15T18:11:13Z","receivedAt":"2009-05-15T18:11:13Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Avery Pennarun <apenwarr@gmail.com> writes:\n\n> ...  Obviously I would need to write a man page, but I've been\n> hesitant to do that in case people have suggestions that need the\n> whole UI to change.  Perhaps that's a chicken-and-egg problem, though,...\n\nIf you fear that you might get into a situation that the UI _must_ change\nbecause it does not fit people's needs or workflows, that is a sign that\nthe UI and the workflow it was designed to support may not have been well\nthought out yet.  At least, you do not even _know_ if it is well thought\nout or not.  It is understandable that people would say \"sounds cool,\ncould potentially be good, but I'll wait and see if it is real\" and leave.\n\nWith a clear description of \"Here is the workflow _I_ assume you use.  If\nyour usage fits this pattern, this tool may be for you, and here is how it\nworks and how to use it,\" it becomes easier for people to say \"My usage is\nsimilar but slightly different in this way.  How (perhaps slightly\ndifferently from your description) would I use it?\" to help improve the\ntool.\n\nInvesting the time and effort to get the ball rolling, whether it gets\nincluded in an official tree or not in the immediate future, is a way to\nshow that the original author _cares_ about the feature.  That would\nfurther help pursuade potential users to look at it.\n\nIt is an easy mistake to make to consider inclusion to my tree your goal.\nIt can be one of the means to give exposure to wider audience, but it does\nnot have to be your only avenue to do so.\n\nI could throw it in contrib/ but that would help merely by giving easier\naccess to people who decide that they want to invest their own time to see\nif it fits their needs and if they can improve it to match their needs.\nYou need to do your part of convincing them that it is worth looking at it\nfirst; that is not something \"throwing it in contrib/\" would help at all.\n\nWith proliferation of free hosting services, however, I think contrib/\narea for such purposes outlived its usefulness.  People can now fork and\ngather interested and enthused users very easily and can make *me* beg to\nmerge from them to include their new, popular, and already polished\nfeatures.\n"},{"id":"114013","messageId":"32541b130905151131h76048ff2o418764aa41bcd13b@mail.gmail.com","threadId":"19251","inReplyTo":"7vzldes0ce.fsf@alter.siamese.dyndns.org","subject":"Re: git subtree: an alternative to git submodule","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2009-05-15T18:31:54Z","receivedAt":"2009-05-15T18:31:54Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Fri, May 15, 2009 at 2:11 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Avery Pennarun <apenwarr@gmail.com> writes:\n>> ...  Obviously I would need to write a man page, but I've been\n>> hesitant to do that in case people have suggestions that need the\n>> whole UI to change.  Perhaps that's a chicken-and-egg problem, though,...\n>\n> If you fear that you might get into a situation that the UI _must_ change\n> because it does not fit people's needs or workflows, that is a sign that\n> the UI and the workflow it was designed to support may not have been well\n> thought out yet.  At least, you do not even _know_ if it is well thought\n> out or not.  It is understandable that people would say \"sounds cool,\n> could potentially be good, but I'll wait and see if it is real\" and leave.\n\nWell, I'm already using it myself in my own projects and I like it.\nSo I'm pretty confident that it is *a* useful workflow.  Whether it's\nuseful for others is a good question, and the only way to know the\nanswer is to put it out there.\n\nBut I'm at a bit of a loss as to why so many people (er, as compared\nto none) seem to have gotten excited about the tool, but then it\nfizzled.  This implies to me that something is missing.  Perhaps it's\njust the documentation; I'll work on that next, then.\n\n> It is an easy mistake to make to consider inclusion to my tree your goal.\n> It can be one of the means to give exposure to wider audience, but it does\n> not have to be your only avenue to do so.\n\nThanks for pointing that out.  In fact my primary goal wasn't really\nto get it included in the tree (otherwise I *would* have written the\ndocumentation and even included a signed-off-by line :)) but to get\ncomments on the feature.  In fact, it would be detrimental to have it\nincluded in your tree and then find out afterwards that it ought to be\nripped out and replaced.\n\n> With proliferation of free hosting services, however, I think contrib/\n> area for such purposes outlived its usefulness.  People can now fork and\n> gather interested and enthused users very easily and can make *me* beg to\n> merge from them to include their new, popular, and already polished\n> features.\n\nI suppose you could merge it in using git-subtree and then you\nwouldn't even have to beg :)\n\nOkay, documentation next.  At least I have somewhere to go from here.\n\nHave fun,\n\nAvery\n"},{"id":"114187","messageId":"46dff0320905180855m3e1bd74esb564af0fbcf4b1ff@mail.gmail.com","threadId":"19251","inReplyTo":"32541b130905151131h76048ff2o418764aa41bcd13b@mail.gmail.com","subject":"Re: git subtree: an alternative to git submodule","fromName":"Ping Yin","fromEmail":"pkufranky@gmail.com","sentAt":"2009-05-18T15:55:06Z","receivedAt":"2009-05-18T15:55:06Z","isPatch":false,"sender":{"key":"pkufranky@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5346?v=4"},"body":"On Sat, May 16, 2009 at 2:31 AM, Avery Pennarun <apenwarr@gmail.com> wrote:\n> On Fri, May 15, 2009 at 2:11 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Avery Pennarun <apenwarr@gmail.com> writes:\n>>> ...  Obviously I would need to write a man page, but I've been\n>>> hesitant to do that in case people have suggestions that need the\n>>> whole UI to change.  Perhaps that's a chicken-and-egg problem, though,...\n>>\n>> If you fear that you might get into a situation that the UI _must_ change\n>> because it does not fit people's needs or workflows, that is a sign that\n>> the UI and the workflow it was designed to support may not have been well\n>> thought out yet.  At least, you do not even _know_ if it is well thought\n>> out or not.  It is understandable that people would say \"sounds cool,\n>> could potentially be good, but I'll wait and see if it is real\" and leave.\n>\n> Well, I'm already using it myself in my own projects and I like it.\n> So I'm pretty confident that it is *a* useful workflow.  Whether it's\n> useful for others is a good question, and the only way to know the\n> answer is to put it out there.\n>\n> But I'm at a bit of a loss as to why so many people (er, as compared\n> to none) seem to have gotten excited about the tool, but then it\n> fizzled.  This implies to me that something is missing.  Perhaps it's\n> just the documentation; I'll work on that next, then.\n>\n\nIt's really a cool feature, but i havn't tried it. Why?\n\nIt will spends me some time saving and applying the patches and then\ntesting it (i don't have the appropriate environment setuped). But I\nam busy and there is no urgent need to use this feature ( it is only a\nrare case for me).  So i will wait until i need the feature or there\nis an easy to fetch the code ( pu of official reposotory or other\nrepository with these patches applied).\n\nI don't whether this is a common reason, but at least it is the reason for me.\n"},{"id":"114188","messageId":"46dff0320905180855n1d772cb8sea69c7d5f47713e3@mail.gmail.com","threadId":"19251","inReplyTo":"46dff0320905180855m3e1bd74esb564af0fbcf4b1ff@mail.gmail.com","subject":"Re: git subtree: an alternative to git submodule","fromName":"Ping Yin","fromEmail":"pkufranky@gmail.com","sentAt":"2009-05-18T15:55:37Z","receivedAt":"2009-05-18T15:55:37Z","isPatch":false,"sender":{"key":"pkufranky@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5346?v=4"},"body":"Ping Yin\n\n\n\nOn Mon, May 18, 2009 at 11:55 PM, Ping Yin <pkufranky@gmail.com> wrote:\n> On Sat, May 16, 2009 at 2:31 AM, Avery Pennarun <apenwarr@gmail.com> wrote:\n>> On Fri, May 15, 2009 at 2:11 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> Avery Pennarun <apenwarr@gmail.com> writes:\n>>>> ...  Obviously I would need to write a man page, but I've been\n>>>> hesitant to do that in case people have suggestions that need the\n>>>> whole UI to change.  Perhaps that's a chicken-and-egg problem, though,...\n>>>\n>>> If you fear that you might get into a situation that the UI _must_ change\n>>> because it does not fit people's needs or workflows, that is a sign that\n>>> the UI and the workflow it was designed to support may not have been well\n>>> thought out yet.  At least, you do not even _know_ if it is well thought\n>>> out or not.  It is understandable that people would say \"sounds cool,\n>>> could potentially be good, but I'll wait and see if it is real\" and leave.\n>>\n>> Well, I'm already using it myself in my own projects and I like it.\n>> So I'm pretty confident that it is *a* useful workflow.  Whether it's\n>> useful for others is a good question, and the only way to know the\n>> answer is to put it out there.\n>>\n>> But I'm at a bit of a loss as to why so many people (er, as compared\n>> to none) seem to have gotten excited about the tool, but then it\n>> fizzled.  This implies to me that something is missing.  Perhaps it's\n>> just the documentation; I'll work on that next, then.\n>>\n>\n> It's really a cool feature, but i havn't tried it. Why?\n>\n> It will spends me some time saving and applying the patches and then\n> testing it (i don't have the appropriate environment setuped). But I\n> am busy and there is no urgent need to use this feature ( it is only a\n> rare case for me).  So i will wait until i need the feature or there\n> is an easy to fetch the code ( pu of official reposotory or other\n> repository with these patches applied).\n>\n> I don't whether this is a common reason, but at least it is the reason for me.\n>\n\ns/dont't/don't know/\n"},{"id":"114193","messageId":"32541b130905180938v5dd5283g6b75ffb7e76f3280@mail.gmail.com","threadId":"19251","inReplyTo":"46dff0320905180855m3e1bd74esb564af0fbcf4b1ff@mail.gmail.com","subject":"Re: git subtree: an alternative to git submodule","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2009-05-18T16:38:56Z","receivedAt":"2009-05-18T16:38:56Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Mon, May 18, 2009 at 11:55 AM, Ping Yin <pkufranky@gmail.com> wrote:\n> It's really a cool feature, but i havn't tried it. Why?\n>\n> It will spends me some time saving and applying the patches and then\n> testing it (i don't have the appropriate environment setuped). But I\n> am busy and there is no urgent need to use this feature ( it is only a\n> rare case for me).  So i will wait until i need the feature or there\n> is an easy to fetch the code ( pu of official reposotory or other\n> repository with these patches applied).\n\nExcellent, thanks for the feedback.  In fact you can git clone the\ncode from here:\n\n  git clone git://github.com/apenwarr/git-subtree.git\n\n(It's not a copy of the git repo; it's a tiny standalone repo.)\n\nThe important file is 'git-subtree'.  Copy this anywhere on your PATH,\nand magically the 'git subtree' command will work.\n\nI admit that your next roadblock will probably be lack of\ndocumentation, though, as Junio points out.\n\nHave fun,\n\nAvery\n"},{"id":"114270","messageId":"46dff0320905190727m77a582c1j5ddc161230cc4a83@mail.gmail.com","threadId":"19251","inReplyTo":"32541b130905180938v5dd5283g6b75ffb7e76f3280@mail.gmail.com","subject":"Re: git subtree: an alternative to git submodule","fromName":"Ping Yin","fromEmail":"pkufranky@gmail.com","sentAt":"2009-05-19T14:27:03Z","receivedAt":"2009-05-19T14:27:03Z","isPatch":false,"sender":{"key":"pkufranky@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5346?v=4"},"body":"On Tue, May 19, 2009 at 12:38 AM, Avery Pennarun <apenwarr@gmail.com> wrote:\n> On Mon, May 18, 2009 at 11:55 AM, Ping Yin <pkufranky@gmail.com> wrote:\n>> It's really a cool feature, but i havn't tried it. Why?\n>>\n>> It will spends me some time saving and applying the patches and then\n>> testing it (i don't have the appropriate environment setuped). But I\n>> am busy and there is no urgent need to use this feature ( it is only a\n>> rare case for me).  So i will wait until i need the feature or there\n>> is an easy to fetch the code ( pu of official reposotory or other\n>> repository with these patches applied).\n>\n> Excellent, thanks for the feedback.  In fact you can git clone the\n> code from here:\n>\n>  git clone git://github.com/apenwarr/git-subtree.git\n>\n> (It's not a copy of the git repo; it's a tiny standalone repo.)\n>\n> The important file is 'git-subtree'.  Copy this anywhere on your PATH,\n> and magically the 'git subtree' command will work.\n>\n> I admit that your next roadblock will probably be lack of\n> documentation, though, as Junio points out.\n>\n\nSome problems after trying.\n\n* \"split\" generates a commit hash which i take some time to figure out\nthe meaning. May it should accept one more argument (repository name)\nto generate the repository directly?\n\n* \"pull\" creates the merged files to the wrong directory. Following is\nthe output\n\ngit subtree -d pull --prefix=foo git@example.com:foo.git master\ncommand: {pull}\nquiet: {}\nrevs: {}\ndir: {foo}\nopts: {git@example.com:foo.git master}\n\n+ git pull -s subtree git@example.com:foo.git master\nFrom example.com:foo\n * branch            master     -> FETCH_HEAD\nMerge made by subtree.\n scripts/data.example/creatives/1.swf     |  Bin 0 -> 174109 bytes\n scripts/data.example/creatives/2.swf      |  Bin 0 -> 103622 bytes\n scripts/data.example/creatives/3.swf |  Bin 0 -> 35347 bytes\n scripts/data.example/creatives/4.swf  |  Bin 0 -> 16300 bytes\n 5 files changed, 0 insertions(+), 0 deletions(-)\n...\n\n\n* \"merge\" doesn't respect the prefix option. With the following\ncommands, foo.txt is not merged to subdir foo/\n\ntouch foo.txt && git add foo.txt && git commit -m \"add foo.txt\"\ngit branch foo  && git reset --hard HEAD^\ngit subtree merge --prefix=foo foo\n"},{"id":"114278","messageId":"m3my99jclg.fsf@localhost.localdomain","threadId":"19251","inReplyTo":"32541b130905150909h7e596f26w7db6887e7f4267ff@mail.gmail.com","subject":"Re: git subtree: an alternative to git submodule","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-05-19T16:13:41Z","receivedAt":"2009-05-19T16:13:41Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Avery Pennarun <apenwarr@gmail.com> writes:\n> On Fri, May 8, 2009 at 6:39 PM, Avery Pennarun <apenwarr@gmail.com> wrote:\n> >\n> > I first sent out this patch set a couple of weeks ago\n> > (http://article.gmane.org/gmane.comp.version-control.git/117612) and got a\n> > couple of positive comments, but no negative ones, so I'm guessing people\n> > haven't reviewed it as closely as I would have hoped :)\n[...]\n\n> Just checking in again.  Since I originally announced git-subtree, it\n> (or rather the concept) has received a bit of positive feedback out in\n> the wild.  My original blog post about it:\n> http://alumnit.ca/~apenwarr/log/?m=200904#30\n\n[...]\n> Meanwhile, I've been keeping it in a separate git repo here:\n> http://github.com/apenwarr/git-subtree/commits/master (that's a web\n> link, not a git link).\n\nI have added information about git-subtree to git wiki on\nhttp://git.or.cz/gitwiki/InterfacesFrontendsAndTools (which page I\nimagined to be clearinghouse / list of relevant git related tools).\nPlease correct any mistakes, and perhaps expand information there.\n\nYou might want to add SubtreeMerge page to git wiki, and link it\nfrom subproject pages (SubmoduleSupport, GitSubmoduleTutorial)\nand perhaps also from GitDocumentation. But it is not necessary.\n\n-- \nJakub Narebski\nPoland\nShadeHawk on #git\n"}]}