Hi *, now that there is a lot of traffic about git-subtree and git-submodules I would like to show you why *I* don't use neither of them, but something in between. First my requirements: 1) Everything[1] must be available from the same repository/branch (so I'm not worried about repository size) 2) The history must be as clean as possible 3) The directory content must be equal to the external module, at least when you add/update it[2] 4) The external module should be able to switch back and forth between different versions. [1] Everything means all that you need to checkout all the commits in the superproject not in the submodule. [2] A consequence of 3) is that I lose all change I've made in the subdirectory, if they are important I have to extract them, apply them and add the module back. git-submodule is rule out because of 1) but accomplish 2), 3) and 4). git-subtree is rule out because of 2) (even with --squash) 3) and 4) without --squash but accomplish 1) and 4) with --squash. So I need something in between or a mixture of both. At the end I've done what I called originally git-subtree, but now I've written the prune mode of git-subtree. The idea is that you want to add the content of a module in a subdirectory and that's all! I think that this simplicity is also very powerful as it is very clear how it behaves. You just get a commit each time you add a subtree (normal commit not a merge) without the history of the subtree. You get something like this: $ git log --graph * ee225bd Subtree 'a/': 895916a Commit message 1 * aa345dg Modification to a/Makefile * ddcd676 Subtree 'a/': 9a053f2 Commit message 2 * ea35faf Indent, whitespaces,... Later you can extract/split all the modifications to a/ with "git-subtree split" (not yet implemented). If you merge two branches with different content in the subtree you just merge them the normal way as with any other file. It works quite well in my use case. Below you can find the patch to git-subtree to add this prune mode (also attatched because of whitespace corruption). Only slightly tested as I normally use my git-subtree command (completely different) and I've just patch git-subtree but you can get the idea of what it does. HTH, Santi diff --git i/git-subtree.sh w/git-subtree.sh index 781eef3..7706d72 100755 --- i/git-subtree.sh +++ w/git-subtree.sh @@ -27,6 +27,7 @@ onto= try connecting new tree to an existing one rejoin merge the new branch back into HEAD options for 'add', 'merge', 'pull' and 'push' squash merge subtree changes as a single commit +prune prune history " eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?) @@ -44,6 +45,7 @@ rejoin= ignore_joins= annotate= squash= +prune= message= debug() @@ -92,6 +94,8 @@ while [ $# -gt 0 ]; do --no-ignore-joins) ignore_joins= ;; --squash) squash=1 ;; --no-squash) squash= ;; + --prune) prune=1;; + --no-prune) prune=;; --) break ;; *) die "Unexpected option: $opt" ;; esac @@ -110,7 +114,7 @@ if [ -z "$prefix" ]; then fi case "$command" in - add) [ -e "$prefix" ] && + add) [ -e "$prefix" -a -z "$prune" ] && die "prefix '$prefix' already exists." ;; *) [ -e "$prefix" ] || die "'$prefix' does not exist; use 'git subtree add'" ;; @@ -359,6 +363,17 @@ squash_msg() echo "git-subtree-split: $newsub" } +prune_msg() +{ + dir="$1" + newsub="$2" + + git show -s --pretty="tformat:Subtree '$dir/': %h %s" $newsub + echo + echo "git-subtree-dir: $dir" + echo "git-subtree-split: $newsub" +} + toptree_for_commit() { commit="$1" @@ -464,7 +479,7 @@ ensure_clean() cmd_add() { - if [ -e "$dir" ]; then + if [ -e "$dir" -a -z "$prune" ]; then die "'$dir' already exists. Cannot add." fi @@ -498,6 +513,10 @@ cmd_add_commit() rev="$1" debug "Adding $dir as '$rev'..." + if [ -d "$dir" ]; then + #TODO: write it with plumbing commands + git rm -r -q $dir + fi git read-tree --prefix="$dir" $rev || exit $? git checkout -- "$dir" || exit $? tree=$(git write-tree) || exit $? @@ -513,6 +532,9 @@ cmd_add_commit() rev=$(new_squash_commit "" "" "$rev") || exit $? commit=$(add_squashed_msg "$rev" "$dir" | git commit-tree $tree $headp -p "$rev") || exit $? + elif [ -n "$prune" ]; then + commit=$(prune_msg "$dir" "$rev" | + git commit-tree $tree -p $headrev) || exit $? else commit=$(add_msg "$dir" "$headrev" "$rev" | git commit-tree $tree $headp -p "$rev") || exit $?
diff --git i/git-subtree.sh w/git-subtree.sh index 781eef3..7706d72 100755 --- i/git-subtree.sh +++ w/git-subtree.sh @@ -27,6 +27,7 @@ onto= try connecting new tree to an existing one rejoin merge the new branch back into HEAD options for 'add', 'merge', 'pull' and 'push' squash merge subtree changes as a single commit +prune prune history " eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?) @@ -44,6 +45,7 @@ rejoin= ignore_joins= annotate= squash= +prune= message= debug() @@ -92,6 +94,8 @@ while [ $# -gt 0 ]; do --no-ignore-joins) ignore_joins= ;; --squash) squash=1 ;; --no-squash) squash= ;; + --prune) prune=1;; + --no-prune) prune=;; --) break ;; *) die "Unexpected option: $opt" ;; esac @@ -110,7 +114,7 @@ if [ -z "$prefix" ]; then fi case "$command" in - add) [ -e "$prefix" ] && + add) [ -e "$prefix" -a -z "$prune" ] && die "prefix '$prefix' already exists." ;; *) [ -e "$prefix" ] || die "'$prefix' does not exist; use 'git subtree add'" ;; @@ -359,6 +363,17 @@ squash_msg() echo "git-subtree-split: $newsub" } +prune_msg() +{ + dir="$1" + newsub="$2" + + git show -s --pretty="tformat:Subtree '$dir/': %h %s" $newsub + echo + echo "git-subtree-dir: $dir" + echo "git-subtree-split: $newsub" +} + toptree_for_commit() { commit="$1" @@ -464,7 +479,7 @@ ensure_clean() cmd_add() { - if [ -e "$dir" ]; then + if [ -e "$dir" -a -z "$prune" ]; then die "'$dir' already exists. Cannot add." fi @@ -498,6 +513,10 @@ cmd_add_commit() rev="$1" debug "Adding $dir as '$rev'..." + if [ -d "$dir" ]; then + #TODO: write it with plumbing commands + git rm -r -q $dir + fi git read-tree --prefix="$dir" $rev || exit $? git checkout -- "$dir" || exit $? tree=$(git write-tree) || exit $? @@ -513,6 +532,9 @@ cmd_add_commit() rev=$(new_squash_commit "" "" "$rev") || exit $? commit=$(add_squashed_msg "$rev" "$dir" | git commit-tree $tree $headp -p "$rev") || exit $? + elif [ -n "$prune" ]; then + commit=$(prune_msg "$dir" "$rev" | + git commit-tree $tree -p $headrev) || exit $? else commit=$(add_msg "$dir" "$headrev" "$rev" | git commit-tree $tree $headp -p "$rev") || exit $?