diff options
Diffstat (limited to 'contrib/subtree')
-rw-r--r-- | contrib/subtree/.gitignore | 7 | ||||
-rw-r--r-- | contrib/subtree/COPYING | 339 | ||||
-rw-r--r-- | contrib/subtree/INSTALL | 28 | ||||
-rw-r--r-- | contrib/subtree/Makefile | 101 | ||||
-rw-r--r-- | contrib/subtree/README | 8 | ||||
-rwxr-xr-x | contrib/subtree/git-subtree.sh | 901 | ||||
-rw-r--r-- | contrib/subtree/git-subtree.txt | 351 | ||||
-rw-r--r-- | contrib/subtree/t/Makefile | 86 | ||||
-rwxr-xr-x | contrib/subtree/t/t7900-subtree.sh | 1034 | ||||
-rw-r--r-- | contrib/subtree/todo | 48 |
10 files changed, 2903 insertions, 0 deletions
diff --git a/contrib/subtree/.gitignore b/contrib/subtree/.gitignore new file mode 100644 index 000000000000..0b9381abcad3 --- /dev/null +++ b/contrib/subtree/.gitignore @@ -0,0 +1,7 @@ +*~ +git-subtree +git-subtree.1 +git-subtree.html +git-subtree.xml +mainline +subproj diff --git a/contrib/subtree/COPYING b/contrib/subtree/COPYING new file mode 100644 index 000000000000..d511905c1647 --- /dev/null +++ b/contrib/subtree/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/contrib/subtree/INSTALL b/contrib/subtree/INSTALL new file mode 100644 index 000000000000..7ab0cf450967 --- /dev/null +++ b/contrib/subtree/INSTALL @@ -0,0 +1,28 @@ +HOW TO INSTALL git-subtree +========================== + +First, build from the top source directory. + +Then, in contrib/subtree, run: + + make + make install + make install-doc + +If you used configure to do the main build the git-subtree build will +pick up those settings. If not, you will likely have to provide a +value for prefix: + + make prefix=<some dir> + make prefix=<some dir> install + make prefix=<some dir> install-doc + +To run tests first copy git-subtree to the main build area so the +newly-built git can find it: + + cp git-subtree ../.. + +Then: + + make test + diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile new file mode 100644 index 000000000000..6906aae44147 --- /dev/null +++ b/contrib/subtree/Makefile @@ -0,0 +1,101 @@ +# The default target of this Makefile is... +all:: + +-include ../../config.mak.autogen +-include ../../config.mak + +prefix ?= /usr/local +gitexecdir ?= $(prefix)/libexec/git-core +mandir ?= $(prefix)/share/man +man1dir ?= $(mandir)/man1 +htmldir ?= $(prefix)/share/doc/git-doc + +../../GIT-VERSION-FILE: FORCE + $(MAKE) -C ../../ GIT-VERSION-FILE + +-include ../../GIT-VERSION-FILE + +# this should be set to a 'standard' bsd-type install program +INSTALL ?= install +RM ?= rm -f + +ASCIIDOC = asciidoc +ASCIIDOC_CONF = -f ../../Documentation/asciidoc.conf +ASCIIDOC_HTML = xhtml11 +ASCIIDOC_DOCBOOK = docbook +ASCIIDOC_EXTRA = +XMLTO = xmlto + +ifdef USE_ASCIIDOCTOR +ASCIIDOC = asciidoctor +ASCIIDOC_CONF = +ASCIIDOC_HTML = xhtml5 +ASCIIDOC_DOCBOOK = docbook45 +ASCIIDOC_EXTRA += -I../../Documentation -rasciidoctor-extensions +ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;' +endif + +ifndef SHELL_PATH + SHELL_PATH = /bin/sh +endif +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) + +MANPAGE_XSL = ../../Documentation/manpage-normal.xsl + +GIT_SUBTREE_SH := git-subtree.sh +GIT_SUBTREE := git-subtree + +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) + +$(GIT_SUBTREE): $(GIT_SUBTREE_SH) + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' $< >$@ + chmod +x $@ + +doc: $(GIT_SUBTREE_DOC) $(GIT_SUBTREE_HTML) + +man: $(GIT_SUBTREE_DOC) + +html: $(GIT_SUBTREE_HTML) + +install: $(GIT_SUBTREE) + $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir) + $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(gitexecdir) + +install-doc: install-man install-html + +install-man: $(GIT_SUBTREE_DOC) + $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) + $(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir) + +install-html: $(GIT_SUBTREE_HTML) + $(INSTALL) -d -m 755 $(DESTDIR)$(htmldir) + $(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir) + +$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML) + $(XMLTO) -m $(MANPAGE_XSL) man $^ + +$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT) + $(ASCIIDOC) -b $(ASCIIDOC_DOCBOOK) -d manpage $(ASCIIDOC_CONF) \ + -agit_version=$(GIT_VERSION) $(ASCIIDOC_EXTRA) $^ + +$(GIT_SUBTREE_HTML): $(GIT_SUBTREE_TXT) + $(ASCIIDOC) -b $(ASCIIDOC_HTML) -d manpage $(ASCIIDOC_CONF) \ + -agit_version=$(GIT_VERSION) $(ASCIIDOC_EXTRA) $^ + +$(GIT_SUBTREE_TEST): $(GIT_SUBTREE) + cp $< $@ + +test: $(GIT_SUBTREE_TEST) + $(MAKE) -C t/ test + +clean: + $(RM) $(GIT_SUBTREE) + $(RM) *.xml *.html *.1 + +.PHONY: FORCE diff --git a/contrib/subtree/README b/contrib/subtree/README new file mode 100644 index 000000000000..c686b4a69b12 --- /dev/null +++ b/contrib/subtree/README @@ -0,0 +1,8 @@ + +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/git-subtree.sh b/contrib/subtree/git-subtree.sh new file mode 100755 index 000000000000..868e18b9a1ab --- /dev/null +++ b/contrib/subtree/git-subtree.sh @@ -0,0 +1,901 @@ +#!/bin/sh +# +# git-subtree.sh: split/join git repositories in subdirectories of this one +# +# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com> +# +if test $# -eq 0 +then + set -- -h +fi +OPTS_SPEC="\ +git subtree add --prefix=<prefix> <commit> +git subtree add --prefix=<prefix> <repository> <ref> +git subtree merge --prefix=<prefix> <commit> +git subtree pull --prefix=<prefix> <repository> <ref> +git subtree push --prefix=<prefix> <repository> <ref> +git subtree split --prefix=<prefix> <commit> +-- +h,help show the help +q quiet +d show debug messages +P,prefix= the name of the subdir to split out +m,message= use the given message as the commit message for the merge commit + options for 'split' +annotate= add a prefix to commit message of new commits +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', and 'pull' +squash merge subtree changes as a single commit +" +eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)" + +PATH=$PATH:$(git --exec-path) +. git-sh-setup + +require_work_tree + +quiet= +branch= +debug= +command= +onto= +rejoin= +ignore_joins= +annotate= +squash= +message= +prefix= + +debug () { + if test -n "$debug" + then + printf "%s\n" "$*" >&2 + fi +} + +say () { + if test -z "$quiet" + then + printf "%s\n" "$*" >&2 + fi +} + +progress () { + if test -z "$quiet" + then + printf "%s\r" "$*" >&2 + fi +} + +assert () { + if ! "$@" + then + die "assertion failed: " "$@" + fi +} + +ensure_single_rev () { + if test $# -ne 1 + then + die "You must provide exactly one revision. Got: '$@'" + fi +} + +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" + ;; + esac +done + +command="$1" +shift + +case "$command" in +add|merge|pull) + default= + ;; +split|push) + default="--default HEAD" + ;; +*) + die "Unknown command '$command'" + ;; +esac + +if test -z "$prefix" +then + die "You must provide the --prefix option." +fi + +case "$command" in +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 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 $? + ensure_single_rev $revs + if test -n "$dirs" + then + die "Error: Use --prefix instead of bare filenames." + fi +fi + +debug "command: {$command}" +debug "quiet: {$quiet}" +debug "revs: {$revs}" +debug "dir: {$dir}" +debug "opts: {$*}" +debug + +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" + debug "Using cachedir: $cachedir" >&2 +} + +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 ! test -r "$cachedir/$oldrev" + then + echo $oldrev + fi + done +} + +check_parents () { + missed=$(cache_miss "$1") + local indent=$(($2 + 1)) + for miss in $missed + do + if ! test -r "$cachedir/notree/$miss" + then + debug " incorrect order: $miss" + process_split_commit "$miss" "" "$indent" + fi + done +} + +set_notree () { + echo "1" > "$cachedir/notree/$1" +} + +cache_set () { + oldrev="$1" + newrev="$2" + 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 + return 0 + else + return 1 + fi +} + +rev_is_descendant_of_branch () { + newrev="$1" + branch="$2" + branch_hash=$(git rev-parse "$branch") + match=$(git rev-list -1 "$branch_hash" "^$newrev") + + if test -z "$match" + then + return 0 + else + return 1 + fi +} + +# 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 + echo "^$1^" + fi +} + +find_latest_squash () { + debug "Looking for latest squash ($dir)..." + dir="$1" + sq= + main= + sub= + git log --grep="^git-subtree-dir: $dir/*\$" \ + --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD | + 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="$(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 + debug "Squash found: $sq $sub" + echo "$sq" "$sub" + break + fi + sq= + main= + sub= + ;; + esac + done +} + +find_existing_splits () { + debug "Looking for prior splits..." + dir="$1" + revs="$2" + main= + sub= + local grep_format="^git-subtree-dir: $dir/*\$" + if test -n "$ignore_joins" + then + grep_format="^Add '$dir/' from commit '" + fi + git log --grep="$grep_format" \ + --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs | + while read a b junk + do + case "$a" in + 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 () { + # 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 --no-show-signature --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" | + ( + read GIT_AUTHOR_NAME + read GIT_AUTHOR_EMAIL + read GIT_AUTHOR_DATE + read GIT_COMMITTER_NAME + read GIT_COMMITTER_EMAIL + read GIT_COMMITTER_DATE + export GIT_AUTHOR_NAME \ + GIT_AUTHOR_EMAIL \ + GIT_AUTHOR_DATE \ + GIT_COMMITTER_NAME \ + GIT_COMMITTER_EMAIL \ + GIT_COMMITTER_DATE + ( + printf "%s" "$annotate" + cat + ) | + git commit-tree "$2" $3 # reads the rest of stdin + ) || die "Can't copy commit $1" +} + +add_msg () { + dir="$1" + latest_old="$2" + latest_new="$3" + 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 test -n "$message" + then + echo "$message" + else + echo "Merge commit '$1' as '$2'" + fi +} + +rejoin_msg () { + dir="$1" + latest_old="$2" + latest_new="$3" + 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 () { + dir="$1" + oldsub="$2" + newsub="$3" + newsub_short=$(git rev-parse --short "$newsub") + + if test -n "$oldsub" + then + oldsub_short=$(git rev-parse --short "$oldsub") + echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short" + echo + git log --no-show-signature --pretty=tformat:'%h %s' "$oldsub..$newsub" + git log --no-show-signature --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub" + else + echo "Squashed '$dir/' content from commit $newsub_short" + fi + + echo + echo "git-subtree-dir: $dir" + echo "git-subtree-split: $newsub" +} + +toptree_for_commit () { + commit="$1" + git rev-parse --verify "$commit^{tree}" || exit $? +} + +subtree_for_commit () { + commit="$1" + dir="$2" + git ls-tree "$commit" -- "$dir" | + 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=$1 + shift + if test $# -ne 1 + then + return 0 # weird parents, consider it changed + else + ptree=$(toptree_for_commit $1) + if test "$ptree" != "$tree" + then + return 0 # changed + else + return 1 # not changed + fi + fi +} + +new_squash_commit () { + old="$1" + oldsub="$2" + newsub="$3" + tree=$(toptree_for_commit $newsub) || 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 $? + fi +} + +copy_or_skip () { + rev="$1" + tree="$2" + newparents="$3" + assert test -n "$tree" + + identical= + nonidentical= + p= + gotparents= + copycommit= + for parent in $newparents + do + ptree=$(toptree_for_commit $parent) || exit $? + test -z "$ptree" && continue + if test "$ptree" = "$tree" + then + # an identical parent could be used in place of this rev. + if test -n "$identical" + then + # if a previous identical parent was found, check whether + # one is already an ancestor of the other + mergebase=$(git merge-base $identical $parent) + if test "$identical" = "$mergebase" + then + # current identical commit is an ancestor of parent + identical="$parent" + elif test "$parent" != "$mergebase" + then + # no common history; commit must be copied + copycommit=1 + fi + else + # first identical parent detected + identical="$parent" + fi + else + nonidentical="$parent" + fi + + # sometimes both old parents map to the same newparent; + # eliminate duplicates + is_new=1 + for gp in $gotparents + do + if test "$gp" = "$parent" + then + is_new= + break + fi + done + if test -n "$is_new" + then + gotparents="$gotparents $parent" + p="$p -p $parent" + fi + done + + 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 $? + fi +} + +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 + die "Index has modifications. Cannot add." + fi +} + +ensure_valid_ref_format () { + git check-ref-format "refs/heads/$1" || + die "'$1' does not look like a ref" +} + +process_split_commit () { + local rev="$1" + local parents="$2" + local indent=$3 + + if test $indent -eq 0 + then + revcount=$(($revcount + 1)) + else + # processing commit without normal parent information; + # fetch from repo + parents=$(git rev-parse "$rev^@") + extracount=$(($extracount + 1)) + fi + + progress "$revcount/$revmax ($createcount) [$extracount]" + + debug "Processing commit: $rev" + exists=$(cache_get "$rev") + if test -n "$exists" + then + debug " prior: $exists" + return + fi + createcount=$(($createcount + 1)) + debug " parents: $parents" + check_parents "$parents" "$indent" + newparents=$(cache_get $parents) + debug " newparents: $newparents" + + tree=$(subtree_for_commit "$rev" "$dir") + debug " tree is: $tree" + + # ugly. is there no better way to tell if this is a subtree + # vs. a mainline commit? Does it matter? + if test -z "$tree" + then + set_notree "$rev" + if test -n "$newparents" + then + cache_set "$rev" "$rev" + fi + return + 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" +} + +cmd_add () { + if test -e "$dir" + then + die "'$dir' already exists. Cannot add." + fi + + ensure_clean + + 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." + fi +} + +cmd_add_repository () { + echo "git fetch" "$@" + repository=$1 + refspec=$2 + git fetch "$@" || exit $? + revs=FETCH_HEAD + set -- $revs + cmd_add_commit "$@" +} + +cmd_add_commit () { + rev=$(git rev-parse $default --revs-only "$@") || exit $? + ensure_single_rev $rev + + 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 test -n "$headrev" && test "$headrev" != "$rev" + then + headp="-p $headrev" + else + headp= + fi + + 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 $? + else + revp=$(peel_committish "$rev") && + 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 () { + debug "Splitting $dir..." + cache_setup || exit $? + + if test -n "$onto" + then + debug "Reading history for --onto=$onto..." + git rev-list $onto | + 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" + done + fi + + unrevs="$(find_existing_splits "$dir" "$revs")" + + # 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) + grl='git rev-list --topo-order --reverse --parents $revs $unrevs' + revmax=$(eval "$grl" | wc -l) + revcount=0 + createcount=0 + extracount=0 + eval "$grl" | + while read rev parents + do + process_split_commit "$rev" "$parents" 0 + done || exit $? + + latest_new=$(cache_get latest_new) + if test -z "$latest_new" + then + die "No new revisions were found" + fi + + if test -n "$rejoin" + then + debug "Merging split branch into HEAD..." + latest_old=$(cache_get latest_old) + git merge -s ours \ + --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 $? + say "$action branch '$branch'" + fi + echo "$latest_new" + exit 0 +} + +cmd_merge () { + rev=$(git rev-parse $default --revs-only "$@") || exit $? + ensure_single_rev $rev + ensure_clean + + if test -n "$squash" + then + first_split="$(find_latest_squash "$dir")" + if test -z "$first_split" + then + die "Can't squash-merge: '$dir' was never added." + fi + set $first_split + old=$1 + sub=$2 + if test "$sub" = "$rev" + then + say "Subtree is already at commit $rev." + exit 0 + fi + new=$(new_squash_commit "$old" "$sub" "$rev") || exit $? + debug "New squash commit: $new" + rev="$new" + fi + + version=$(git version) + 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" + fi + else + if test -n "$message" + then + git merge -Xsubtree="$prefix" \ + --message="$message" "$rev" + else + git merge -Xsubtree="$prefix" $rev + fi + fi +} + +cmd_pull () { + if test $# -ne 2 + then + die "You must provide <repository> <ref>" + fi + ensure_clean + ensure_valid_ref_format "$2" + git fetch "$@" || exit $? + revs=FETCH_HEAD + set -- $revs + cmd_merge "$@" +} + +cmd_push () { + if test $# -ne 2 + then + die "You must provide <repository> <ref>" + fi + ensure_valid_ref_format "$2" + 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'." + fi +} + +"cmd_$command" "$@" diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt new file mode 100644 index 000000000000..352deda69dcf --- /dev/null +++ b/contrib/subtree/git-subtree.txt @@ -0,0 +1,351 @@ +git-subtree(1) +============== + +NAME +---- +git-subtree - Merge subtrees together and split repository into subtrees + + +SYNOPSIS +-------- +[verse] +'git subtree' add -P <prefix> <commit> +'git subtree' add -P <prefix> <repository> <ref> +'git subtree' pull -P <prefix> <repository> <ref> +'git subtree' push -P <prefix> <repository> <ref> +'git subtree' merge -P <prefix> <commit> +'git subtree' split -P <prefix> [OPTIONS] [<commit>] + + +DESCRIPTION +----------- +Subtrees allow subprojects to be included within a subdirectory +of the main project, optionally including the subproject's +entire history. + +For example, you could include the source code for a library +as a subdirectory of your application. + +Subtrees are not to be confused with submodules, which are meant for +the same task. Unlike submodules, subtrees do not need any special +constructions (like .gitmodules files or gitlinks) be present in +your repository, and do not force end-users of your +repository to do anything special or to understand how subtrees +work. A subtree is just a subdirectory that can be +committed to, branched, and merged along with your project in +any way you want. + +They are also not to be confused with using the subtree merge +strategy. The main difference is that, besides merging +the other project as a subdirectory, you can also extract the +entire history of a subdirectory from your project and make it +into a standalone project. Unlike the subtree merge strategy +you can alternate back and forth between these +two operations. If the standalone library gets updated, you can +automatically merge the changes into your project; if you +update the library inside your project, you can "split" the +changes back out again and merge them back into the library +project. + +For example, if a library you made for one application ends up being +useful elsewhere, you can extract its entire history and publish +that as its own git repository, without accidentally +intermingling the history of your application project. + +[TIP] +In order to keep your commit messages clean, we recommend that +people split their commits between the subtrees and the main +project as much as possible. That is, if you make a change that +affects both the library and the main application, commit it in +two pieces. That way, when you split the library commits out +later, their descriptions will still make sense. But if this +isn't important to you, it's not *necessary*. git subtree will +simply leave out the non-library-related parts of the commit +when it splits it out into the subproject later. + + +COMMANDS +-------- +add:: + Create the <prefix> subtree by importing its contents + from the given <commit> or <repository> and remote <ref>. + A new commit is created automatically, joining the imported + project's history with your own. With '--squash', imports + only a single commit from the subproject, rather than its + entire history. + +merge:: + Merge recent changes up to <commit> into the <prefix> + subtree. As with normal 'git merge', this doesn't + remove your own local changes; it just merges those + changes into the latest <commit>. With '--squash', + creates only one commit that contains all the changes, + rather than merging in the entire history. ++ +If you use '--squash', the merge direction doesn't always have to be +forward; you can use this command to go back in time from v2.5 to v2.4, +for example. If your merge introduces a conflict, you can resolve it in +the usual ways. + +pull:: + Exactly like 'merge', but parallels 'git pull' in that + it fetches the given ref from the specified remote + repository. + +push:: + Does a 'split' (see below) using the <prefix> supplied + and then does a 'git push' to push the result to the + repository and ref. This can be used to push your + subtree to different branches of the remote repository. + +split:: + Extract a new, synthetic project history from the + history of the <prefix> subtree. The new history + includes only the commits (including merges) that + affected <prefix>, and each of those commits now has the + contents of <prefix> at the root of the project instead + of in a subdirectory. Thus, the newly created history + is suitable for export as a separate git repository. ++ +After splitting successfully, a single commit id is printed to stdout. +This corresponds to the HEAD of the newly created tree, which you can +manipulate however you want. ++ +Repeated splits of exactly the same history are guaranteed to be +identical (i.e. to produce the same commit ids). Because of this, if +you add new commits and then re-split, the new commits will be attached +as commits on top of the history you generated last time, so 'git merge' +and friends will work as expected. ++ +Note that if you use '--squash' when you merge, you should usually not +just '--rejoin' when you split. + + +OPTIONS +------- +-q:: +--quiet:: + Suppress unnecessary output messages on stderr. + +-d:: +--debug:: + Produce even more unnecessary output messages on stderr. + +-P <prefix>:: +--prefix=<prefix>:: + Specify the path in the repository to the subtree you + want to manipulate. This option is mandatory + for all commands. + +-m <message>:: +--message=<message>:: + This option is only valid for add, merge and pull (unsure). + Specify <message> as the commit message for the merge commit. + + +OPTIONS FOR add, merge, push, pull +---------------------------------- +--squash:: + This option is only valid for add, merge, and pull + commands. ++ +Instead of merging the entire history from the subtree project, produce +only a single commit that contains all the differences you want to +merge, and then merge that new commit into your project. ++ +Using this option helps to reduce log clutter. People rarely want to see +every change that happened between v1.0 and v1.1 of the library they're +using, since none of the interim versions were ever included in their +application. ++ +Using '--squash' also helps avoid problems when the same subproject is +included multiple times in the same project, or is removed and then +re-added. In such a case, it doesn't make sense to combine the +histories anyway, since it's unclear which part of the history belongs +to which subtree. ++ +Furthermore, with '--squash', you can switch back and forth between +different versions of a subtree, rather than strictly forward. 'git +subtree merge --squash' always adjusts the subtree to match the exactly +specified commit, even if getting to that commit would require undoing +some changes that were added earlier. ++ +Whether or not you use '--squash', changes made in your local repository +remain intact and can be later split and send upstream to the +subproject. + + +OPTIONS FOR split +----------------- +--annotate=<annotation>:: + This option is only valid for the split command. ++ +When generating synthetic history, add <annotation> as a prefix to each +commit message. Since we're creating new commits with the same commit +message, but possibly different content, from the original commits, this +can help to differentiate them and avoid confusion. ++ +Whenever you split, you need to use the same <annotation>, or else you +don't have a guarantee that the new re-created history will be identical +to the old one. That will prevent merging from working correctly. git +subtree tries to make it work anyway, particularly if you use --rejoin, +but it may not always be effective. + +-b <branch>:: +--branch=<branch>:: + This option is only valid for the split command. ++ +After generating the synthetic history, create a new branch called +<branch> that contains the new history. This is suitable for immediate +pushing upstream. <branch> must not already exist. + +--ignore-joins:: + This option is only valid for the split command. ++ +If you use '--rejoin', git subtree attempts to optimize its history +reconstruction to generate only the new commits since the last +'--rejoin'. '--ignore-join' disables this behaviour, forcing it to +regenerate the entire history. In a large project, this can take a long +time. + +--onto=<onto>:: + This option is only valid for the split command. ++ +If your subtree was originally imported using something other than git +subtree, its history may not match what git subtree is expecting. In +that case, you can specify the commit id <onto> that corresponds to the +first revision of the subproject's history that was imported into your +project, and git subtree will attempt to build its history from there. ++ +If you used 'git subtree add', you should never need this option. + +--rejoin:: + This option is only valid for the split command. ++ +After splitting, merge the newly created synthetic history back into +your main project. That way, future splits can search only the part of +history that has been added since the most recent --rejoin. ++ +If your split commits end up merged into the upstream subproject, and +then you want to get the latest upstream version, this will allow git's +merge algorithm to more intelligently avoid conflicts (since it knows +these synthetic commits are already part of the upstream repository). ++ +Unfortunately, using this option results in 'git log' showing an extra +copy of every new commit that was created (the original, and the +synthetic one). ++ +If you do all your merges with '--squash', don't use '--rejoin' when you +split, because you don't want the subproject's history to be part of +your project anyway. + + +EXAMPLE 1. Add command +---------------------- +Let's assume that you have a local repository that you would like +to add an external vendor library to. In this case we will add the +git-subtree repository as a subdirectory of your already existing +git-extensions repository in ~/git-extensions/: + + $ git subtree add --prefix=git-subtree --squash \ + git://github.com/apenwarr/git-subtree.git master + +'master' needs to be a valid remote ref and can be a different branch +name + +You can omit the --squash flag, but doing so will increase the number +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 +in our git-extensions repository. + +EXAMPLE 2. Extract a subtree using commit, merge and pull +--------------------------------------------------------- +Let's use the repository for the git source code as an example. +First, get your own copy of the git.git repository: + + $ git clone git://git.kernel.org/pub/scm/git/git.git test-git + $ cd test-git + +gitweb (commit 1130ef3) was merged into git as of commit +0a8f4f0, after which it was no longer maintained separately. +But imagine it had been maintained separately, and we wanted to +extract git's changes to gitweb since that time, to share with +the upstream. You could do this: + + $ git subtree split --prefix=gitweb --annotate='(split) ' \ + 0a8f4f0^.. --onto=1130ef3 --rejoin \ + --branch gitweb-latest + $ gitk gitweb-latest + $ git push git@github.com:whatever/gitweb.git gitweb-latest:master + +(We use '0a8f4f0^..' because that means "all the changes from +0a8f4f0 to the current version, including 0a8f4f0 itself.") + +If gitweb had originally been merged using 'git subtree add' (or +a previous split had already been done with --rejoin specified) +then you can do all your splits without having to remember any +weird commit ids: + + $ git subtree split --prefix=gitweb --annotate='(split) ' --rejoin \ + --branch gitweb-latest2 + +And you can merge changes back in from the upstream project just +as easily: + + $ git subtree pull --prefix=gitweb \ + git@github.com:whatever/gitweb.git master + +Or, using '--squash', you can actually rewind to an earlier +version of gitweb: + + $ git subtree merge --prefix=gitweb --squash gitweb-latest~10 + +Then make some changes: + + $ date >gitweb/myfile + $ git add gitweb/myfile + $ git commit -m 'created myfile' + +And fast forward again: + + $ git subtree merge --prefix=gitweb --squash gitweb-latest + +And notice that your change is still intact: + + $ ls -l gitweb/myfile + +And you can split it out and look at your changes versus +the standard gitweb: + + git log gitweb-latest..$(git subtree split --prefix=gitweb) + +EXAMPLE 3. Extract a subtree using branch +----------------------------------------- +Suppose you have a source directory with many files and +subdirectories, and you want to extract the lib directory to its own +git project. Here's a short way to do it: + +First, make the new repository wherever you want: + + $ <go to the new location> + $ git init --bare + +Back in your original directory: + + $ git subtree split --prefix=lib --annotate="(split)" -b split + +Then push the new branch onto the new empty repository: + + $ git push <new-repo> split:master + + +AUTHOR +------ +Written by Avery Pennarun <apenwarr@gmail.com> + + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile new file mode 100644 index 000000000000..276898eb6bd7 --- /dev/null +++ b/contrib/subtree/t/Makefile @@ -0,0 +1,86 @@ +# Run tests +# +# Copyright (c) 2005 Junio C Hamano +# + +-include ../../../config.mak.autogen +-include ../../../config.mak + +#GIT_TEST_OPTS=--verbose --debug +SHELL_PATH ?= $(SHELL) +PERL_PATH ?= /usr/bin/perl +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 = $(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) + +test: pre-clean $(TEST_LINT) + $(MAKE) aggregate-results-and-cleanup + +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-except-prove-cache + +$(T): + @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) + +pre-clean: + $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' + +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-shell-syntax + +test-lint-duplicates: + @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ + test -z "$$dups" || { \ + echo >&2 "duplicate test numbers:" $$dups; exit 1; } + +test-lint-executable: + @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \ + 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 '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ + echo "$$f"; \ + done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh + +valgrind: + $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind" + +test-results: + mkdir -p test-results + +.PHONY: pre-clean $(T) aggregate-results clean valgrind diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh new file mode 100755 index 000000000000..57ff4b25c17e --- /dev/null +++ b/contrib/subtree/t/t7900-subtree.sh @@ -0,0 +1,1034 @@ +#!/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 add, pull, merge +and split subcommands of git subtree. +' + +TEST_DIRECTORY=$(pwd)/../../../t +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" && + git add "$1" +} + +check_equal() +{ + test_debug 'echo' + test_debug "echo \"check a:\" \"{$1}\"" + test_debug "echo \" b:\" \"{$2}\"" + if [ "$1" = "$2" ]; then + return 0 + else + return 1 + fi +} + +undo() +{ + git reset --hard HEAD~ +} + +# 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() +{ + 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" +} + +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 +} + +subtree_test_count=0 +next_test() { + subtree_test_count=$(($subtree_test_count+1)) +} + +# +# Tests for 'git subtree add' +# + +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 + ) +' + +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)'\''" + ) +' + +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" + ) +' + +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" + ) +' + +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" + ) +' + +# +# Tests for 'git subtree merge' +# + +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)'\''" + ) +' + +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" + ) +' + +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" + ) +' + +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." + ) +' + +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)'\''" + ) +' + +# +# Tests for 'git subtree split' +# + +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 + ) +' + +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 + ) +' + +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" + ) +' + +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' ' + 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" + ) + ) +' + +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" + ) +' + +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 + ) +' + +# +# Validity checking +# + +next_test +test_expect_success 'make sure exactly the right set of files ends up in the 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" && + 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 && + + test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 \ + sub1 sub2 sub3 sub4 >expect && + git ls-files >actual && + test_cmp expect actual + ) +' + +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 && + + test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 \ + sub1 sub2 sub3 sub4 >expect && + git log --name-only --pretty=format:"" >log && + sort <log | sed "/^\$/ d" >actual && + test_cmp expect actual + ) +' + +next_test +test_expect_success 'make sure exactly the right set of files ends up in the mainline' ' + 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 && + + test_write_lines main1 main2 >chkm && + test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms && + sed "s,^,sub dir/," chkms >chkms_sub && + test_write_lines sub1 sub2 sub3 sub4 >chks && + sed "s,^,sub dir/," chks >chks_sub && + + cat chkm chkms_sub chks_sub >expect && + git ls-files >actual && + test_cmp expect actual + ) +' + +next_test +test_expect_success 'make sure each filename changed exactly once in the entire history' ' + 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 && + + test_write_lines main1 main2 >chkm && + test_write_lines sub1 sub2 sub3 sub4 >chks && + test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms && + sed "s,^,sub dir/," chkms >chkms_sub && + + # 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. + git log --name-only --pretty=format:"" >log && + sort <log >sorted-log && + sed "/^$/ d" sorted-log >actual && + + cat chkms chkm chks chkms_sub >expect-unsorted && + sort expect-unsorted >expect && + test_cmp expect actual + ) +' + +next_test +test_expect_success 'make sure the --rejoin commits never make it into 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" && + 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' ' + 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.*:")" "" + ) +' + +# +# A new set of tests +# + +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)" + ) +' + +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)" "" + ) +' + +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 + ) + ) +' + +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" + ) +' + +# +# 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. +# + +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 new file mode 100644 index 000000000000..0d0e77765175 --- /dev/null +++ b/contrib/subtree/todo @@ -0,0 +1,48 @@ + + delete tempdir + + 'git subtree rejoin' option to do the same as --rejoin, eg. after a + rebase + + --prefix doesn't force the subtree correctly in merge/pull: + "-s subtree" should be given an explicit subtree option? + There doesn't seem to be a way to do this. We'd have to + patch git-merge-subtree. Ugh. + (but we could avoid this problem by generating squashes with + exactly the right subtree structure, rather than using + subtree merge...) + + add a 'log' subcommand to see what's new in a subtree? + + add to-submodule and from-submodule commands + + automated tests for --squash stuff + + "add" command non-obviously requires a commitid; would be easier if + it had a "pull" sort of mode instead + + "pull" and "merge" commands should fail if you've never merged + that --prefix before + + docs should provide an example of "add" + + note that the initial split doesn't *have* to have a commitid + specified... that's just an optimization + + if you try to add (or maybe merge?) with an invalid commitid, you + get a misleading "prefix must end with /" message from + one of the other git tools that git-subtree calls. Should + detect this situation and print the *real* problem. + + "pull --squash" should do fetch-synthesize-merge, but instead just + does "pull" directly, which doesn't work at all. + + make a 'force-update' that does what 'add' does even if the subtree + already exists. That way we can help people who imported + subtrees "incorrectly" (eg. by just copying in the files) in + the past. + + guess --prefix automatically if possible based on pwd + + make a 'git subtree grafts' that automatically expands --squash'd + commits so you can see the full history if you want it. |