pax_global_header00006660000000000000000000000064112563627610014523gustar00rootroot0000000000000052 comment=f59e4f9e87e5f485fdaee0af002edd2105fa298a topgit-0.8/000077500000000000000000000000001125636276100127005ustar00rootroot00000000000000topgit-0.8/.gitignore000066400000000000000000000006121125636276100146670ustar00rootroot00000000000000/hooks/pre-commit /tg-create /tg-create.txt /tg-delete /tg-delete.txt /tg-depend /tg-depend.txt /tg-info /tg-info.txt /tg-mail /tg-mail.txt /tg-patch /tg-patch.txt /tg-summary /tg-summary.txt /tg-update /tg-update.txt /tg-export /tg-export.txt /tg-import /tg-import.txt /tg-remote /tg-remote.txt /tg-push /tg-push.txt /tg .*.swp # quilt working directory /.pc # .deb build stamp /build-stamp topgit-0.8/COPYING000066400000000000000000000504541125636276100137430ustar00rootroot00000000000000This file has several parts: - this index (just to get this list complete) - GNU GENERAL PUBLIC LICENSE Version 2 - Description for Signed-off-by including Developer's Certificate of Origin 1.1 Note that TopGit (unless specified otherwise in the corresponding file) doesn't allow you to choose a later version of the GPL as suggested by the Free Software Foundation. -------------------------------------------------------------------------------- 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. Copyright (C) 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. , 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. -------------------------------------------------------------------------------- Each patch added to TopGit shall have a sign-off by its author, forwarders (if applicable) and final commiter. This is what core GIT and Linux kernel require, too. The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as a open-source patch. The rules are pretty simple: if you can certify the below: Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. then you just add a line saying Signed-off-by: Random J Developer This line can be automatically added by git if you run the git-commit command with the -s option. Notice that you can place your own Signed-off-by: line when forwarding somebody else's patch with the above rules for D-C-O. Indeed you are encouraged to do so. Do not forget to place an in-body "From: " line at the beginning to properly attribute the change to its true author (see (2) above). Also notice that a real name is used in the Signed-off-by: line. Please don't hide your real name. topgit-0.8/Makefile000066400000000000000000000022731125636276100143440ustar00rootroot00000000000000prefix ?= $(HOME) bindir := $(prefix)/bin cmddir := $(prefix)/libexec/topgit sharedir := $(prefix)/share/topgit hooksdir := $(cmddir)/hooks commands_in := $(wildcard tg-*.sh) hooks_in = hooks/pre-commit.sh commands_out := $(patsubst %.sh,%,$(commands_in)) hooks_out := $(patsubst %.sh,%,$(hooks_in)) help_out := $(patsubst %.sh,%.txt,$(commands_in)) all:: tg $(commands_out) $(hooks_out) $(help_out) tg $(commands_out) $(hooks_out): % : %.sh Makefile @echo "[SED] $@" @sed -e 's#@cmddir@#$(cmddir)#g;' \ -e 's#@hooksdir@#$(hooksdir)#g' \ -e 's#@bindir@#$(bindir)#g' \ -e 's#@sharedir@#$(sharedir)#g' \ $@.sh >$@+ && \ chmod +x $@+ && \ mv $@+ $@ $(help_out): README @CMD=`echo $@ | sed -e 's/tg-//' -e 's/\.txt//'` && \ echo '[HELP]' $$CMD && \ ./create-help.sh $$CMD install:: all install -d -m 755 "$(DESTDIR)$(bindir)" install tg "$(DESTDIR)$(bindir)" install -d -m 755 "$(DESTDIR)$(cmddir)" install $(commands_out) "$(DESTDIR)$(cmddir)" install -d -m 755 "$(DESTDIR)$(hooksdir)" install $(hooks_out) "$(DESTDIR)$(hooksdir)" install -d -m 755 "$(DESTDIR)$(sharedir)" install -m 644 $(help_out) "$(DESTDIR)$(sharedir)" clean:: rm -f tg $(commands_out) $(hooks_out) $(help_out) topgit-0.8/README000066400000000000000000000551771125636276100135770ustar00rootroot00000000000000TopGit - A different patch queue manager DESCRIPTION ----------- TopGit aims to make handling of large amount of interdependent topic branches easier. In fact, it is designed especially for the case when you maintain a queue of third-party patches on top of another (perhaps Git-controlled) project and want to easily organize, maintain and submit them - TopGit achieves that by keeping a separate topic branch for each patch and providing few tools to maintain the branches. RATIONALE --------- Why not use something like StGIT or Guilt or rebase -i for maintaining your patch queue? The advantage of these tools is their simplicity; they work with patch _series_ and defer to the reflog facility for version control of patches (reordering of patches is not version-controlled at all). But there are several disadvantages - for one, these tools (especially StGIT) do not actually fit well with plain Git at all: it is basically impossible to take advantage of the index effectively when using StGIT. But more importantly, these tools horribly fail in the face of distributed environment. TopGit has been designed around three main tenets: (i) TopGit is as thin layer on top of Git as possible. You still maintain your index and commit using Git, TopGit will only automate few indispensable tasks. (ii) TopGit is anxious about _keeping_ your history. It will never rewrite your history and all metadata is also tracked by Git, smoothly and non-obnoxiously. It is good to have a _single_ point when the history is cleaned up, and that is at the point of inclusion in the upstream project; locally, you can see how your patch has evolved and easily return to older versions. (iii) TopGit is specifically designed to work in distributed environment. You can have several instances of TopGit-aware repositories and smoothly keep them all up-to-date and transfer your changes between them. As mentioned above, the main intended use-case for TopGit is tracking third-party patches, where each patch is effectively a single topic branch. In order to flexibly accommodate even complex scenarios when you track many patches where many are independent but some depend on others, TopGit ignores the ancient Quilt heritage of patch series and instead allows the patches to freely form graphs (DAGs just like Git history itself, only "one level higher"). For now, you have to manually specify which patches does the current one depend on, but TopGit might help you with that in the future in a darcs-like fashion. A glossary plug: The union (i.e. merge) of patch dependencies is called a _base_ of the patch (topic branch). Of course, TopGit is perhaps not the right tool for you: (i) TopGit is not complicated, but StGIT et al. are somewhat simpler, conceptually. If you just want to make a linear purely-local patch queue, deferring to StGIT instead might make more sense. (ii) When using TopGit, your history can get a little hairy over time, especially with all the merges rippling through. ;-) SYNOPSIS -------- ## Create and evolve a topic branch $ tg create t/gitweb/pathinfo-action tg: Automatically marking dependency on master tg: Creating t/gitweb/pathinfo-action base from master... $ ..hack.. $ git commit $ ..fix a mistake.. $ git commit ## Create another topic branch on top of the former one $ tg create t/gitweb/nifty-links tg: Automatically marking dependency on t/gitweb/pathinfo-action tg: Creating t/gitweb/nifty-links base from t/gitweb/pathinfo-action... $ ..hack.. $ git commit ## Create another topic branch on top of master and submit ## the resulting patch upstream $ tg create t/revlist/author-fixed master tg: Creating t/revlist/author-fixed base from master... $ ..hack.. $ git commit $ tg patch -m tg: Sent t/revlist/author-fixed From: pasky@suse.cz To: git@vger.kernel.org Cc: gitster@pobox.com Subject: [PATCH] Fix broken revlist --author when --fixed-string ## Create another topic branch depending on two others non-trivially $ tg create t/whatever t/revlist/author-fixed t/gitweb/nifty-links tg: Creating t/whatever base from t/revlist/author-fixed... tg: Merging t/whatever base with t/gitweb/nifty-links... Merge failed! tg: Please commit merge resolution and call: tg create tg: It is also safe to abort this operation using `git reset --hard` tg: but please remember you are on the base branch now; tg: you will want to switch to a different branch. $ ..resolve.. $ git commit $ tg create tg: Resuming t/whatever setup... $ ..hack.. $ git commit ## Update a single topic branch and propagate the changes to ## a different one $ git checkout t/gitweb/nifty-links $ ..hack.. $ git commit $ git checkout t/whatever $ tg info Topic Branch: t/whatever (1 commit) Subject: [PATCH] Whatever patch Base: 3f47ebc1 Depends: t/revlist/author-fixed t/gitweb/nifty-links Needs update from: t/gitweb/nifty-links (1 commit) $ tg update tg: Updating base with t/gitweb/nifty-links changes... Merge failed! tg: Please commit merge resolution and call `tg update` again. tg: It is also safe to abort this operation using `git reset --hard`, tg: but please remember you are on the base branch now; tg: you will want to switch to a different branch. $ ..resolve.. $ git commit $ tg update tg: Updating t/whatever against new base... Merge failed! tg: Please resolve the merge and commit. No need to do anything else. tg: You can abort this operation using `git reset --hard` now tg: and retry this merge later using `tg update`. $ ..resolve.. $ git commit ## Update a single topic branch and propagate the changes ## further through the dependency chain $ git checkout t/gitweb/pathinfo-action $ ..hack.. $ git commit $ git checkout t/whatever $ tg info Topic Branch: t/whatever (1/2 commits) Subject: [PATCH] Whatever patch Base: 0ab2c9b3 Depends: t/revlist/author-fixed t/gitweb/nifty-links Needs update from: t/gitweb/pathinfo-action (<= t/gitweb/nifty-links) (1 commit) $ tg update tg: Recursing to t/gitweb/nifty-links... [t/gitweb/nifty-links] tg: Updating base with t/gitweb/pathinfo-action changes... Merge failed! [t/gitweb/nifty-links] tg: Please commit merge resolution and call `tg update` again. [t/gitweb/nifty-links] tg: It is also safe to abort this operation using `git reset --hard`, [t/gitweb/nifty-links] tg: but please remember you are on the base branch now; [t/gitweb/nifty-links] tg: you will want to switch to a different branch. [t/gitweb/nifty-links] tg: You are in a subshell. If you abort the merge, [t/gitweb/nifty-links] tg: use `exit` to abort the recursive update altogether. [t/gitweb/nifty-links] $ ..resolve.. [t/gitweb/nifty-links] $ git commit [t/gitweb/nifty-links] $ tg update [t/gitweb/nifty-links] tg: Updating t/gitweb/nifty-links against new base... Merge failed! [t/gitweb/nifty-links] tg: Please resolve the merge and commit. [t/gitweb/nifty-links] tg: You can abort this operation using `git reset --hard`. [t/gitweb/nifty-links] tg: You are in a subshell. After you either commit or abort [t/gitweb/nifty-links] tg: your merge, use `exit` to proceed with the recursive update. [t/gitweb/nifty-links] $ ..resolve.. [t/gitweb/nifty-links] $ git commit [t/gitweb/nifty-links] $ exit tg: Updating base with t/gitweb/nifty-links changes... tg: Updating t/whatever against new base... ## Clone a TopGit-controlled repository $ git clone URL repo $ cd repo $ tg remote --populate origin ... $ git fetch $ tg update ## Add a TopGit remote to a repository and push to it $ git remote add foo URL $ tg remote foo $ git push foo ## Update from a non-default TopGit remote $ git fetch foo $ tg -r foo summary $ tg -r foo update USAGE ----- The 'tg' tool of TopGit has several subcommands: tg help ~~~~~~~ Our sophisticated integrated help facility. Doesn't do a whole lot for now. tg create ~~~~~~~~~ Create a new TopGit-controlled topic branch of a given name (required argument) and switch to it. If no dependencies are specified (by extra arguments passed after the first one), the current branch is assumed to be the only dependency. After `tg create`, you should insert the patch description to the '.topmsg' file, which will already contain some prefilled bits. You can set topgit.to, topgit.cc and topgit.bcc configuration variables in order to have `tg create` add these headers with given default values to '.topmsg'. The main task of `tg create` is to set up the topic branch base from the dependencies. This may fail due to merge conflicts. In that case, after you commit the conflicts resolution, you should call `tg create` again (without any arguments); it will detect that you are on a topic branch base ref and resume the topic branch creation operation. In an alternative use case, if '-r BRANCH' is given instead of dependency list, the topic branch is created based on the given remote branch. tg delete ~~~~~~~~~ Remove a TopGit-controlled topic branch of given name (required argument). Normally, this command will remove only empty branch (base == head); use '-f' to remove non-empty branch. Currently, this command will _NOT_ remove the branch from the dependency list in other branches. You need to take care of this _manually_. This is even more complicated in combination with '-f', in that case you need to manually unmerge the removed branch's changes from the branches depending on it. TODO: '-a' to delete all empty branches, depfix, revert tg depend ~~~~~~~~~ Change dependencies of a TopGit-controlled topic branch. This should have several subcommands, but only 'add' is supported right now. The 'add' subcommand takes an argument of a topic branch to be added, adds it to '.topdeps', performs a commit and then updates your topic branch accordingly. If you want to do other things related to the dependency addition, like adjusting '.topmsg', prepare them in the index before calling 'tg depend add'. TODO: Subcommand for removing dependencies, obviously tg info ~~~~~~~ Show a summary information about the current or specified topic branch. tg patch ~~~~~~~~ Generate a patch from the current or specified topic branch. This means that the diff between the topic branch base and head (latest commit) is shown, appended to the description found in the .topmsg file. The patch is by default simply dumped to stdout. In the future, tg patch will be able to automatically send the patches by mail or save them to files. (TODO) Options: -i base patch generation on index instead of branch -w base patch generation on working tree instead of branch tg mail ~~~~~~~ Send a patch from the current or specified topic branch as email. Takes the patch given on the command line and emails it out. Destination addresses such as To, Cc and Bcc are taken from the patch header. Since it actually boils down to `git send-email` please refer to its documentation for details on how to setup email for git. You can pass arbitrary options to this command through the '-s' parameter, but you must double-quote everything. The '-r' parameter with msgid can be used to generate in-reply-to and reference headers to an earlier mail. TODO: 'tg mail patchfile' to mail an already exported patch TODO: mailing patch series TODO: specifying additional options and addresses on command line tg remote ~~~~~~~~~ Register given remote as TopGit-controlled. This will create the namespace for the remote branch bases and teach 'git fetch' and 'git push' to operate on them. (Do NOT use 'git push --all' for your pushes - plain 'git push' will do the right thing.) It takes a mandatory remote name argument, and optional '--populate' switch - use that for your origin-style remote, it will seed the local topic branch system based on the remote topic branches. '--populate' will also make 'tg remote' automatically fetch the remote and 'tg update' to look at branches of this remote for updates by default. tg summary ~~~~~~~~~~ Show overview of all TopGit-tracked topic branches and their up-to-date status ('>' marks the current topic branch, '0' marks that it introduces no own changes, 'l'/'r' marks that it is local-only or has remote mate, 'L'/'R' marks that it is ahead/out-of-date wrt. its remote mate, 'D' marks that it is out-of-date wrt. its dependencies, '!' marks that it has missing dependencies (even recursively), 'B' marks that it is out-of-date wrt. its base). This can take long time to accurately determine all the relevant information about each branch; you can pass '-t' to get just terse list of topic branch names quickly. Alternately, you can pass '--graphviz' to get a dot-suitable output to draw a dependency graph between the topic branches. TODO: Speed up by an order of magnitude TODO: Text graph view tg export ~~~~~~~~~ Export a tidied-up history of the current topic branch and its dependencies, suitable for feeding upstream. Each topic branch corresponds to a single commit or patch in the cleaned up history (corresponding basically exactly to `tg patch` output for the topic branch). The command has three possible outputs now - either a Git branch with the collapsed history, a Git branch with a linearized history, or a quilt series in new directory. In case of producing collapsed history in new branch, you can use this collapsed structure either for providing a pull source for upstream, or further linearization e.g. for creation of a quilt series using git log: git log --pretty=email -p --topo-order origin..exported To better understand the function of `tg export`, consider this dependency structure of topic branches: origin/master - t/foo/blue - t/foo/red - master `- t/bar/good <,----------' `- t/baz ------------' (Where each of the branches may have hefty history.) Then master$ tg export for-linus will create this commit structure on branch for-linus: origin/master - t/foo/blue -. merge - t/foo/red -.. merge - master `- t/bar/good <,-------------------'/ `- t/baz ---------------------' In case of using the linearize mode: master$ tg export --linearize for-linus you get a linear history respecting the dependencies of your patches in a new branch for-linus. The result should be more or less the same as using quilt mode and reimporting it into a Git branch. (More or less because the topologic order can usually be extended in more than one way into a complete ordering and the two methods may choose different one's.) The result might be more appropriate for merging upstream as it contains fewer merges. Note that you might get conflicts during linearization because the patches are reordered to get a linear history. In case of the quilt mode, master$ tg export --quilt for-linus would create this directory for-linus: for-linus/t/foo/blue.diff for-linus/t/foo/red.diff for-linus/t/bar/good.diff for-linus/t/baz.diff for-linus/series: t/foo/blue.diff -p1 t/bar/good.diff -p1 t/foo/red.diff -p1 t/baz.diff -p1 The command works on the current topic branch and can be called either without a parameter (in that case, '--collapse' is assumed) and with one mandatory argument: the name of the branch where the exported result shall be stored. The branch will be silently overwritten if it exists already! Use git reflog to recover in case of mistake. Alternatively, call it with the '--quilt' parameter and an argument specifying the directory where the quilt series should be saved. With '--quilt', you can also pass '-b' parameter followed by a comma-separated explicit list of branches to export. This mode of operation is currently not supported with collapse. In '--quilt' mode the patches are named like the originating topgit branch. So usually they end up in subdirectories of the output directory. With option '--flatten' the names are mangled such that they end up directly in the output dir (i.e. slashed are substituted by underscores). With '--numbered' (which implies '--flatten') the patch names get a number as prefix to allow getting the order without consulting the series file, which eases sending out the patches. Usage: tg export ([(--collapse | --linearize)] BRANCH | --quilt DIR) TODO: Make stripping of non-essential headers configurable TODO: Make stripping of [PATCH] and other prefixes configurable TODO: --mbox option for other mode of operation TODO: -a option to export all branches TODO: For quilt exporting, export the linearized history created in a temporary branch---this would allow producing conflict-less series tg import ~~~~~~~~~ Import commits within the given revision range into TopGit, creating one topic branch per commit, the dependencies forming a linear sequence starting on your current branch (or a branch specified by the '-d' parameter). The branch names are auto-guessed from the commit messages and prefixed by t/ by default; use '-p PREFIX' to specify an alternative prefix (even an empty one). Alternatively, you can use the '-s NAME' parameter to specify the name of target branch; the command will then take one more argument describing a single commit to import. tg update ~~~~~~~~~ Update the current topic branch wrt. changes in the branches it depends on and remote branches. This is performed in two phases - first, changes within the dependencies are merged to the base, then the base is merged into the topic branch. The output will guide you in case of conflicts. In case your dependencies are not up-to-date, tg update will first recurse into them and update these. If a remote branch update brings dependencies on branches not yet instantiated locally, you can either bring in all the new branches from the remote using 'tg remote --populate' or only pick out the missing ones using 'tg create -r' ('tg summary' will point out branches with incomplete dependencies by showing an '!' near to them). TODO: tg update -a for updating all topic branches tg push ~~~~~~~ pushes a TopGit-controlled topic branch to a remote repository. By default the remote gets all dependencies (both tgish and non-tgish) and bases pushed to. TODO: tg rename IMPLEMENTATION -------------- TopGit stores all the topic branches in the regular refs/heads/ namespace, (we recommend to mark them with the 't/' prefix). Except that, TopGit also maintains a set of auxiliary refs in refs/top-*. Currently, only refs/top-bases/ is used, containing the current _base_ of the given topic branch - this is basically a merge of all the branches the topic branch depends on; it is updated during `tg update` and then merged to the topic branch, and it is the base of a patch generated from the topic branch by `tg patch`. All the metadata is tracked within the source tree and history of the topic branch itself, in .top* files; these files are kept isolated within the topic branches during TopGit-controlled merges and are of course omitted during `tg patch`. The state of these files in base commits is undefined; look at them only in the topic branches themselves. Currently, two files are defined: .topmsg: Contains the description of the topic branch in a mail-like format, plus the author information, whatever Cc headers you choose or the post-three-dashes message. When mailing out your patch, basically only few extra headers mail headers are inserted and the patch itself is appended. Thus, as your patches evolve, you can record nuances like whether the particular patch should have To-list/Cc-maintainer or vice versa and similar nuances, if your project is into that. From is prefilled from your current GIT_AUTHOR_IDENT, other headers can be prefilled from various optional topgit.* config options. .topdeps: Contains the one-per-line list of branches your patch depends on, pre-seeded with `tg create`. (Continuously updated) merge of these branches will be the "base" of your topic branch. DO NOT EDIT THIS FILE MANUALLY!!! If you do so, you need to know exactly what are you doing, since this file must stay in sync with the Git history information, otherwise very bad things will happen. TopGit also automagically installs a bunch of custom commit-related hooks that will verify if you are committing the .top* files in sane state. It will add the hooks to separate files within the hooks/ subdirectory and merely insert calls of them to the appropriate hooks and make them executable (but make sure the original hooks code is not called if the hook was not executable beforehand). Another automagically installed piece is .git/info/attributes specifier for an 'ours' merge strategy for the files .topmsg and .topdeps, and the (intuitive) 'ours' merge strategy definition in .git/config. REMOTE HANDLING --------------- There are three issues with accessing topic branches in remote repositories: (i) Fetching/pushing accurate picture of the remote topic branch setup (ii) Referring to remote topic branches from your local repository (iii) Developing some of the remote topic branches locally (ii) and (iii) are fairly interconnected problems, while (i) is largely independent. The issue is to accurately reflect the current state of the quickly changing topic branches set - this can be easily done with the current facilities like 'git remote prune' and 'git push --mirror' - and to properly upload also the bases of the topic branches. For this, we need to modify the fetch/push refspecs to also include the refs/top-bases/ ref namespace; we shall provide a special 'tg remote' command to set up an existing remote for TopGit usage. About (ii) and (iii), there are two somewhat contradicting design considerations: (a) Hacking on multiple independent TopGit remotes in a single repository (b) Having a self-contained topic system in local refs space To us, (a) does not appear to be very convincing, while (b) is quite desirable for 'git-log topic' etc. working, 'git push' automatically creating self-contained topic system in the remote repository, and increased conceptual simplicity. Thus, we choose to instantiate all the topic branches of given remote locally; this is performed by 'tg remote --populate'. 'tg update' will also check if a branch can be updated from its corresponding remote branch. The logic is somewhat involved if we should DTRT. First, we update the base, handling the remote branch as if it was the first dependency; thus, conflict resolutions made in the remote branch will be carried over to our local base automagically. Then, the base is merged into remote branch and the result is merged to local branch - again, to carry over remote conflict resolutions. In the future, this order might be adjustable per-update in case local changes are diverging more than the remote ones. All commands by default refer to the remote that 'tg remote --populate' was called on the last time ('topgit.remote' configuration variable). You can manually run any command with a different base remote by passing '-r REMOTE' _before_ the subcommand name. topgit-0.8/contrib/000077500000000000000000000000001125636276100143405ustar00rootroot00000000000000topgit-0.8/contrib/tg-completion.bash000077500000000000000000000171721125636276100177730ustar00rootroot00000000000000# # bash completion support for TopGit. # # Copyright (C) 2008 Jonas Fonseca # Copyright (C) 2006,2007 Shawn O. Pearce # Based git's git-completion.sh: http://repo.or.cz/w/git/fastimport.git # Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/). # Distributed under the GNU General Public License, version 2.0. # # To use these routines: # # 1) Copy this file to somewhere (e.g. ~/.tg-completion.sh). # 2) Source it from your ~/.bashrc. # # Note: Make sure the tg script is in your PATH before you source this # script, so it can properly setup cached values. case "$COMP_WORDBREAKS" in *:*) : great ;; *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" esac ### {{{ Utilities __tgdir () { if [ -z "$1" ]; then if [ -n "$__tg_dir" ]; then echo "$__tg_dir" elif [ -d .git ]; then echo .git else git rev-parse --git-dir 2>/dev/null fi elif [ -d "$1/.git" ]; then echo "$1/.git" else echo "$1" fi } __tgcomp_1 () { local c IFS=' '$'\t'$'\n' for c in $1; do case "$c$2" in --*=*) printf %s$'\n' "$c$2" ;; *.) printf %s$'\n' "$c$2" ;; *) printf %s$'\n' "$c$2 " ;; esac done } __tgcomp () { local cur="${COMP_WORDS[COMP_CWORD]}" if [ $# -gt 2 ]; then cur="$3" fi case "$cur" in --*=) COMPREPLY=() ;; *) local IFS=$'\n' COMPREPLY=($(compgen -P "$2" \ -W "$(__tgcomp_1 "$1" "$4")" \ -- "$cur")) ;; esac } __tg_heads () { local cmd i is_hash=y dir="$(__tgdir "$1")" if [ -d "$dir" ]; then git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ refs/heads return fi for i in $(git ls-remote "$1" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; n,*) is_hash=y; echo "$i" ;; esac done } __tg_refs () { local cmd i is_hash=y dir="$(__tgdir "$1")" if [ -d "$dir" ]; then if [ -e "$dir/HEAD" ]; then echo HEAD; fi git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ refs/tags refs/heads refs/remotes return fi for i in $(git ls-remote "$dir" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;; n,*) is_hash=y; echo "$i" ;; esac done } __tg_refs2 () { local i for i in $(__tg_refs "$1"); do echo "$i:$i" done } __tg_refs_remotes () { local cmd i is_hash=y for i in $(git ls-remote "$1" 2>/dev/null); do case "$is_hash,$i" in n,refs/heads/*) is_hash=y echo "$i:refs/remotes/$1/${i#refs/heads/}" ;; y,*) is_hash=n ;; n,*^{}) is_hash=y ;; n,refs/tags/*) is_hash=y;; n,*) is_hash=y; ;; esac done } __tg_remotes () { local i ngoff IFS=$'\n' d="$(__tgdir)" shopt -q nullglob || ngoff=1 shopt -s nullglob for i in "$d/remotes"/*; do echo ${i#$d/remotes/} done [ "$ngoff" ] && shopt -u nullglob for i in $(git --git-dir="$d" config --list); do case "$i" in remote.*.url=*) i="${i#remote.}" echo "${i/.url=*/}" ;; esac done } __tg_find_subcommand () { local word subcommand c=1 while [ $c -lt $COMP_CWORD ]; do word="${COMP_WORDS[c]}" for subcommand in $1; do if [ "$subcommand" = "$word" ]; then echo "$subcommand" return fi done c=$((++c)) done } __tg_complete_revlist () { local pfx cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in *...*) pfx="${cur%...*}..." cur="${cur#*...}" __tgcomp "$(__tg_refs)" "$pfx" "$cur" ;; *..*) pfx="${cur%..*}.." cur="${cur#*..}" __tgcomp "$(__tg_refs)" "$pfx" "$cur" ;; *) __tgcomp "$(__tg_refs)" ;; esac } __tg_topics () { tg summary -t } __tg_commands () { if [ -n "$__tg_all_commandlist" ]; then echo "$__tg_all_commandlist" return fi local i IFS=" "$'\n' for i in $(tg help | sed -n 's/^Usage:.*(\(.*\)).*/\1/p' | tr '|' ' ') do case $i in *--*) : helper pattern;; *) echo $i;; esac done } __tg_all_commandlist= __tg_all_commandlist="$(__tg_commands 2>/dev/null)" __tg_complete_arg () { if [ $COMP_CWORD -gt 2 ] && [ "${COMP_WORDS[$COMP_CWORD - 1]}" = "$1" ]; then return 0 fi return 1 } ### }}} ### {{{ Commands _tg_create () { local cmd="$1" local cur="${COMP_WORDS[COMP_CWORD]}" # Name must be the first arg after the create command if [ $((cmd + 1)) = $COMP_CWORD ]; then __tgcomp "$(__tg_topics)" return fi __tg_complete_arg "-r" && { __tgcomp "$(__tg_remotes)" return } case "$cur" in -*) __tgcomp " -r " ;; *) __tgcomp "$(__tg_refs)" esac } _tg_delete () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in -*) __tgcomp " -f " ;; *) __tgcomp "$(__tg_topics)" esac } _tg_depend () { local subcommands="add" local subcommand="$(__git_find_subcommand "$subcommands")" if [ -z "$subcommand" ]; then __tgcomp "$subcommands" return fi case "$subcommand" in add) __tgcomp "$(__tg_refs)" esac } _tg_export () { local cur="${COMP_WORDS[COMP_CWORD]}" __tg_complete_arg "--collapse" && { __tgcomp "$(__tg_heads)" return } __tg_complete_arg "--quilt" && { return } case "$cur" in *) __tgcomp " --collapse --quilt " esac } _tg_help () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in -*) COMPREPLY=() return ;; esac __tgcomp "$(__tg_commands)" } _tg_import () { local cur="${COMP_WORDS[COMP_CWORD]}" __tg_complete_arg "-p" && { COMPREPLY=() return } case "$cur" in -*) __tgcomp " -p " ;; *) __tg_complete_revlist esac } _tg_info () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in *) __tgcomp "$(__tg_topics)" esac } _tg_mail () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in *) __tgcomp "$(__tg_topics)" esac } _tg_patch () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in -*) __tgcomp " -i -w " ;; *) __tgcomp "$(__tg_topics)" esac } _tg_push () { local cur="${COMP_WORDS[COMP_CWORD]}" __tg_complete_arg "-r" && { __tgcomp "$(__tg_remotes)" return } case "$cur" in -*) __tgcomp " --no-deps --dry-run --tgish-only -r " ;; *) __tgcomp "$(__tg_topics)" esac } _tg_remote () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in -*) __tgcomp " --populate " ;; *) __tgcomp "$(__tg_remotes)" esac } _tg_summary () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in *) __tgcomp " --graphviz -t " esac } _tg_update () { COMPREPLY=() } ### }}} ### {{{ tg completion _tg () { local i c=1 command __tg_dir while [ $c -lt $COMP_CWORD ]; do i="${COMP_WORDS[c]}" case "$i" in -r) c=$((++c)) if [ $c -lt $COMP_CWORD ]; then __tgcomp "$(__tg_remotes)" return fi ;; -h|--help) command="help"; break ;; *) command="$i"; break ;; esac c=$((++c)) done if [ -z "$command" ]; then case "${COMP_WORDS[COMP_CWORD]}" in -*) __tgcomp " -r -h --help " ;; *) __tgcomp "$(__tg_commands)" ;; esac return fi case "$command" in create) _tg_create "$c" ;; delete) _tg_delete ;; depend) _tg_depend ;; export) _tg_export ;; help) _tg_help ;; import) _tg_import ;; info) _tg_info ;; mail) _tg_mail ;; patch) _tg_patch ;; push) _tg_push ;; remote) _tg_remote ;; summary) _tg_summary ;; update) _tg_update ;; *) COMPREPLY=() ;; esac } ### }}} complete -o default -o nospace -F _tg tg # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then complete -o default -o nospace -F _tg tg.exe fi topgit-0.8/create-help.sh000077500000000000000000000005731125636276100154350ustar00rootroot00000000000000#!/bin/sh # Create the tg-foo.txt files which contain help for the tg-foo command. if [ $# -ne 1 ] ; then echo "Usage: $0 tgcommand" 1>&2 exit 1 fi < README awk ' BEGIN { incommand = 0; } /^tg '"$1"'$/ { incommand = 1; next; } /^~/ { next; } # Ignore the title underlines. /^[^\t]/ { incommand = 0; next; } { if (incommand) { print $0; } } ' > tg-"$1".txt # vim:noet topgit-0.8/hooks/000077500000000000000000000000001125636276100140235ustar00rootroot00000000000000topgit-0.8/hooks/pre-commit.sh000066400000000000000000000012671125636276100164410ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 ## Set up all the tg machinery set -e tg__include=1 tg_util() { . "@bindir@"/tg } tg_util ## Generally have fun # Don't do anything on non-topgit branch if head_=$(git symbolic-ref -q HEAD); then case "$head_" in refs/heads/*) git rev-parse -q --verify "refs/top-bases${head_#refs/heads}" >/dev/null || exit 0;; *) exit 0;; esac else exit 0; fi # TODO: check the index, not the working copy [ -s "$root_dir/.topdeps" ] || die ".topdeps is missing" [ -s "$root_dir/.topmsg" ] || die ".topmsg is missing" # TODO: Verify .topdeps for valid branch names and against cycles topgit-0.8/tg-create.sh000066400000000000000000000074021125636276100151120ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 deps= # List of dependent branches restarted= # Set to 1 if we are picking up in the middle of base setup merge= # List of branches to be merged; subset of $deps name= rname= # Remote branch to base this one on ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -r) rname="$1"; shift;; -*) echo "Usage: tg [...] create NAME [DEPS...|-r RNAME]" >&2 exit 1;; *) if [ -z "$name" ]; then name="$arg" else deps="$deps $arg" fi;; esac done ## Fast-track creating branches based on remote ones if [ -n "$rname" ]; then [ -n "$name" ] || die "no branch name given" ! ref_exists "$name" || die "branch '$name' already exists" has_remote "$rname" || die "no branch $rname in remote $base_remote" git update-ref "refs/top-bases/$name" "refs/remotes/$base_remote/top-bases/$rname" git update-ref "refs/heads/$name" "refs/remotes/$base_remote/$rname" info "Topic branch $name based on $base_remote : $rname set up." exit 0 fi ## Auto-guess dependencies deps="${deps# }" if [ -z "$deps" ]; then if [ -z "$name" -a -s "$git_dir/top-name" -a -s "$git_dir/top-deps" -a -s "$git_dir/top-merge" ]; then # We are setting up the base branch now; resume merge! name="$(cat "$git_dir/top-name")" deps="$(cat "$git_dir/top-deps")" merge="$(cat "$git_dir/top-merge")" restarted=1 info "Resuming $name setup..." else # The common case [ -z "$name" ] && die "no branch name given" head="$(git symbolic-ref HEAD)" deps="${head#refs/heads/}" [ "$deps" != "$head" ] || die "refusing to auto-depend on non-head ref ($head)" info "Automatically marking dependency on $deps" fi fi [ -n "$merge" -o -n "$restarted" ] || merge="$deps " for d in $deps; do ref_exists "$d" || die "unknown branch dependency '$d'" done ! ref_exists "$name" || die "branch '$name' already exists" # Clean up any stale stuff rm -f "$git_dir/top-name" "$git_dir/top-deps" "$git_dir/top-merge" ## Find starting commit to create the base if [ -n "$merge" -a -z "$restarted" ]; then # Unshift the first item from the to-merge list branch="${merge%% *}" merge="${merge#* }" info "Creating $name base from $branch..." # We create a detached head so that we can abort this operation git checkout -q "$(git rev-parse "$branch")" fi ## Merge other dependencies into the base while [ -n "$merge" ]; do # Unshift the first item from the to-merge list branch="${merge%% *}" merge="${merge#* }" info "Merging $name base with $branch..." if ! git merge "$branch"; then info "Please commit merge resolution and call: $tg create" info "It is also safe to abort this operation using:" info "git reset --hard some_branch" info "(You are on a detached HEAD now.)" echo "$name" >"$git_dir/top-name" echo "$deps" >"$git_dir/top-deps" echo "$merge" >"$git_dir/top-merge" exit 2 fi done ## Set up the topic branch git update-ref "refs/top-bases/$name" "HEAD" "" git checkout -b "$name" echo "$deps" | sed 's/ /\n/g' >"$root_dir/.topdeps" git add -f "$root_dir/.topdeps" author="$(git var GIT_AUTHOR_IDENT)" author_addr="${author%> *}>" { echo "From: $author_addr" ! header="$(git config topgit.to)" || echo "To: $header" ! header="$(git config topgit.cc)" || echo "Cc: $header" ! header="$(git config topgit.bcc)" || echo "Bcc: $header" ! subject_prefix="$(git config topgit.subjectprefix)" || subject_prefix="$subject_prefix " echo "Subject: [${subject_prefix}PATCH] $name" echo cat < Signed-off-by: $author_addr EOT } >"$root_dir/.topmsg" git add -f "$root_dir/.topmsg" info "Topic branch $name set up. Please fill .topmsg now and make initial commit." info "To abort: git rm -f .top* && git checkout ${deps%% *} && $tg delete $name" # vim:noet topgit-0.8/tg-delete.sh000066400000000000000000000020411125636276100151030ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 force= # Whether to delete non-empty branch name= ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -f) force=1;; -*) echo "Usage: tg [...] delete [-f] NAME" >&2 exit 1;; *) [ -z "$name" ] || die "name already specified ($name)" name="$arg";; esac done ## Sanity checks [ -n "$name" ] || die "no branch name specified" branchrev="$(git rev-parse --verify "$name" 2>/dev/null)" || die "invalid branch name: $name" baserev="$(git rev-parse --verify "refs/top-bases/$name" 2>/dev/null)" || die "not a TopGit topic branch: $name" ! git symbolic-ref HEAD >/dev/null || [ "$(git symbolic-ref HEAD)" != "refs/heads/$name" ] || die "cannot delete your current branch" nonempty= branch_empty "$name" || nonempty=1 [ -z "$nonempty" ] || [ -n "$force" ] || die "branch is non-empty: $name" ## Wipe out git update-ref -d "refs/top-bases/$name" "$baserev" git update-ref -d "refs/heads/$name" "$branchrev" # vim:noet topgit-0.8/tg-depend.sh000066400000000000000000000014701125636276100151050ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 name= ## Parse options subcmd="$1"; shift || : case "$subcmd" in -h|"") echo "Usage: tg [...] depend add NAME" >&2 exit 1;; add) ;; *) die "unknown subcommand ($subcmd)";; esac while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -*) echo "Usage: tg [...] depend add NAME" >&2 exit 1;; *) [ -z "$name" ] || die "name already specified ($name)" name="$arg";; esac done ## Sanity checks [ -n "$name" ] || die "no branch name specified" branchrev="$(git rev-parse --verify "$name" 2>/dev/null)" || die "invalid branch name: $name" ## Record new dependency echo "$name" >>"$root_dir/.topdeps" git add -f "$root_dir/.topdeps" git commit -m"New TopGit dependency: $name" $tg update # vim:noet topgit-0.8/tg-export.sh000066400000000000000000000172531125636276100151750ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 name= branches= output= driver=collapse flatten=false numbered=false ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -b) branches="$1"; shift;; --flatten) flatten=true;; --numbered) flatten=true; numbered=true;; --quilt) driver=quilt;; --collapse) driver=collapse;; --linearize) driver=linearize;; -*) echo "Usage: tg [...] export ([--collapse] NEWBRANCH | [-b BRANCH1,BRANCH2...] --quilt DIRECTORY | --linearize NEWBRANCH)" >&2 exit 1;; *) [ -z "$output" ] || die "output already specified ($output)" output="$arg";; esac done [ -z "$branches" -o "$driver" = "quilt" ] || die "-b works only with the quilt driver" [ "$driver" = "quilt" ] || ! "$numbered" || die "--numbered works only with the quilt driver"; [ "$driver" = "quilt" ] || ! "$flatten" || die "--flatten works only with the quilt driver" if [ -z "$branches" ]; then # this check is only needed when no branches have been passed name="$(git symbolic-ref HEAD | sed 's#^refs/heads/##')" base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" || die "not on a TopGit-controlled branch" fi playground="$(mktemp -d -t tg-export.XXXXXX)" trap 'rm -rf "$playground"' EXIT ## Collapse driver # pretty_tree NAME # Output tree ID of a cleaned-up tree without tg's artifacts. pretty_tree() { (export GIT_INDEX_FILE="$playground/^index" git read-tree "$1" git update-index --force-remove ".topmsg" ".topdeps" git write-tree) } create_tg_commit() { name="$1" tree="$2" parent="$3" # Get commit message and authorship information git cat-file blob "$name:.topmsg" | git mailinfo "$playground/^msg" /dev/null > "$playground/^info" GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$playground/^info")" GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$playground/^info")" GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$playground/^info")" SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$playground/^info")" test -n "$GIT_AUTHOR_NAME" && export GIT_AUTHOR_NAME test -n "$GIT_AUTHOR_EMAIL" && export GIT_AUTHOR_EMAIL test -n "$GIT_AUTHOR_DATE" && export GIT_AUTHOR_DATE (printf '%s\n\n' "$SUBJECT"; cat "$playground/^msg") | git stripspace | git commit-tree "$tree" -p "$parent" } # collapsed_commit NAME # Produce a collapsed commit of branch NAME. collapsed_commit() { name="$1" rm -f "$playground/^pre" "$playground/^post" >"$playground/^body" # Determine parent parent="$(cut -f 1 "$playground/$name^parents")" if [ "$(cat "$playground/$name^parents" | wc -l)" -gt 1 ]; then # Produce a merge commit first parent="$({ echo "TopGit-driven merge of branches:" echo cut -f 2 "$playground/$name^parents" } | git commit-tree "$(pretty_tree "refs/top-bases/$name")" \ $(for p in $parent; do echo -p $p; done))" fi if branch_empty "$name"; then echo "$parent"; else create_tg_commit "$name" "$(pretty_tree $name)" "$parent" fi; echo "$name" >>"$playground/^ticker" } # collapse # This will collapse a single branch, using information about # previously collapsed branches stored in $playground. collapse() { if [ -s "$playground/$_dep" ]; then # We've already seen this dep commit="$(cat "$playground/$_dep")" elif [ -z "$_dep_is_tgish" ]; then # This dep is not for rewrite commit="$(git rev-parse --verify "$_dep")" else # First time hitting this dep; the common case echo "Collapsing $_dep" commit="$(collapsed_commit "$_dep")" mkdir -p "$playground/$(dirname "$_dep")" echo "$commit" >"$playground/$_dep" fi # Propagate our work through the dependency chain mkdir -p "$playground/$(dirname "$_name")" echo "$commit $_dep" >>"$playground/$_name^parents" } ## Quilt driver quilt() { if [ -z "$_dep_is_tgish" ]; then # This dep is not for rewrite return fi if "$flatten"; then bn="$(echo "$_dep.diff" | sed -e 's#_#__#g' -e 's#/#_#g')"; dn=""; else bn="$(basename "$_dep.diff")"; dn="$(dirname "$_dep.diff")/"; if [ "x$dn" = "x./" ]; then dn=""; fi; fi; if [ -e "$playground/$_dep" ]; then # We've already seen this dep return fi mkdir -p "$playground/$(dirname "$_dep")"; touch "$playground/$_dep"; if branch_empty "$_dep"; then echo "Skip empty patch $_dep"; else if "$numbered"; then number="$(echo $(($(cat "$playground/^number" 2>/dev/null) + 1)))"; bn="$(printf "%04u-$bn" $number)"; echo "$number" >"$playground/^number"; fi; echo "Exporting $_dep" mkdir -p "$output/$dn"; $tg patch "$_dep" >"$output/$dn$bn" echo "$dn$bn -p1" >>"$output/series" fi } linearize() { if test ! -f "$playground/^BASE"; then head="$(git rev-parse --verify "$_dep")" echo "$head" > "$playground/^BASE" git checkout -q "$head" return; fi; head=$(git rev-parse --verify HEAD) if [ -z "$_dep_is_tgish" ]; then # merge in $_dep unless already included rev="$(git rev-parse --verify "$_dep")"; common="$(git merge-base --all HEAD "$_dep")"; if test "$rev" = "$common"; then # already included, just skip :; else retmerge=0; git merge -s recursive "$_dep" || retmerge="$?"; if test "x$retmerge" != "x0"; then echo fix up the merge, commit and then exit; #todo error handling sh -i /dev/null [ "$_ret" -eq 0 ] || die "cancelling export of $_dep (-> $_name): branch not up-to-date" $driver } # Call driver on all the branches - this will happen # in topological order. if [ -z "$branches" ]; then recurse_deps driver "$name" (_ret=0; _dep="$name"; _name=""; _dep_is_tgish=1; driver) else echo "$branches" | tr ',' '\n' | while read _dep; do _dep_is_tgish=1 $driver done name="$(echo "$branches" | sed 's/.*,//')" fi if [ "$driver" = "collapse" ]; then git update-ref "refs/heads/$output" "$(cat "$playground/$name")" "" depcount="$(cat "$playground/^ticker" | wc -l)" echo "Exported topic branch $name (total $depcount topics) to branch $output" elif [ "$driver" = "quilt" ]; then depcount="$(cat "$output/series" | wc -l)" echo "Exported topic branch $name (total $depcount topics) to directory $output" elif [ "$driver" = "linearize" ]; then git checkout -q -b $output echo $name if test $(git rev-parse "$(pretty_tree $name)^{tree}") != $(git rev-parse "HEAD^{tree}"); then echo "Warning: Exported result doesn't match"; echo "tg-head=$(git rev-parse "$name"), exported=$(git rev-parse "HEAD")"; #git diff $head HEAD; fi; fi # vim:noet topgit-0.8/tg-import.sh000066400000000000000000000046741125636276100151710ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # (c) Aneesh Kumar K.V 2008 # GPLv2 branch_prefix=t/ single= ranges= basedep= ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -d) basedep="$1"; shift;; -p) branch_prefix="$1"; shift;; -s) single="$1"; shift;; -*) echo "Usage: tg [...] import [-d BASE_BRANCH] {[-p PREFIX] RANGE...|-s NAME COMMIT}" >&2 exit 1;; *) ranges="$ranges $arg";; esac done ## Make sure our tree is clean git update-index --ignore-submodules --refresh || exit [ -z "$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)" ] || die "the index is not clean" ## Perform import get_commit_msg() { commit="$1" headers="" ! header="$(git config topgit.to)" || headers="$headers%nTo: $header" ! header="$(git config topgit.cc)" || headers="$headers%nCc: $header" ! header="$(git config topgit.bcc)" || headers="$headers%nBcc: $header" git log -1 --pretty=format:"From: %an <%ae>$headers%nSubject: %s%n%n%b" "$commit" } get_branch_name() { # nice sed script from git-format-patch.sh commit="$1" titleScript=' s/[^-a-z.A-Z_0-9]/-/g s/\.\.\.*/\./g s/\.*$// s/--*/-/g s/^-// s/-$// q ' git log -1 --pretty=format:"%s" "$commit" | sed -e "$titleScript" } process_commit() { commit="$1" branch_name="$2" info "---- Importing $commit to $branch_name" tg create "$branch_name" $basedep basedep= get_commit_msg "$commit" > .topmsg git add -f .topmsg .topdeps if ! git cherry-pick --no-commit "$commit"; then info "The commit will also finish the import of this patch." exit 2 fi git commit -C "$commit" info "++++ Importing $commit finished" } if [ -n "$single" ]; then process_commit $ranges "$single" exit fi # nice arg verification stolen from git-format-patch.sh for revpair in $ranges do case "$revpair" in ?*..?*) rev1=`expr "z$revpair" : 'z\(.*\)\.\.'` rev2=`expr "z$revpair" : 'z.*\.\.\(.*\)'` ;; *) die "Unknow range spec $revpair" ;; esac git rev-parse --verify "$rev1^0" >/dev/null 2>&1 || die "Not a valid rev $rev1 ($revpair)" git rev-parse --verify "$rev2^0" >/dev/null 2>&1 || die "Not a valid rev $rev2 ($revpair)" git cherry -v "$rev1" "$rev2" | while read sign rev comment do case "$sign" in '-') info "Merged already: $comment" ;; *) process_commit "$rev" "$branch_prefix$(get_branch_name "$rev")" ;; esac done done # vim:noet topgit-0.8/tg-info.sh000066400000000000000000000040031125636276100145740ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 name= ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -*) echo "Usage: tg [...] info [NAME]" >&2 exit 1;; *) [ -z "$name" ] || die "name already specified ($name)" name="$arg";; esac done [ -n "$name" ] || name="$(git symbolic-ref HEAD | sed 's#^refs/\(heads\|top-bases\)/##')" base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" || die "not a TopGit-controlled branch" measure="$(measure_branch "$name" "$base_rev")" echo "Topic Branch: $name ($measure)" if [ "$(git rev-parse --short "$name")" = "$base_rev" ]; then echo "* No commits." exit 0 fi git cat-file blob "$name:.topmsg" | grep ^Subject: || : echo "Base: $base_rev" branch_contains "$name" "$base_rev" || echo "* Base is newer than head! Please run \`$tg update\`." if has_remote "$name"; then echo "Remote Mate: $base_remote/$name" branch_contains "$base_rev" "refs/remotes/$base_remote/top-bases/$name" || echo "* Local base is out of date wrt. the remote base." branch_contains "$name" "refs/remotes/$base_remote/$name" || echo "* Local head is out of date wrt. the remote head." branch_contains "refs/remotes/$base_remote/$name" "$name" || echo "* Local head is ahead of the remote head." fi git cat-file blob "$name:.topdeps" | sed '1{ s/^/Depends: /; n; }; s/^/ /;' depcheck="$(mktemp -t tg-depcheck.XXXXXX)" missing_deps= needs_update "$name" >"$depcheck" || : if [ -n "$missing_deps" ]; then echo "MISSING: $missing_deps" fi if [ -s "$depcheck" ]; then echo "Needs update from:" cat "$depcheck" | sed 's/ [^ ]* *$//' | # last is $name sed 's/^: //' | # don't distinguish base updates while read dep chain; do echo -n "$dep " [ -n "$chain" ] && echo -n "(<= $(echo "$chain" | sed 's/ / <= /')) " dep_parent="${chain%% *}" echo -n "($(measure_branch "$dep" "${dep2:-$name}"))" echo done | sed 's/^/\t/' else echo "Up-to-date." fi rm "$depcheck" # vim:noet topgit-0.8/tg-mail.sh000066400000000000000000000023241125636276100145670ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # GPLv2 name= send_email_args= in_reply_to= ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -s) send_email_args="$1"; shift;; -r) in_reply_to="$1"; shift;; -*) echo "Usage: tg [...] mail [-s SEND_EMAIL_ARGS] [-r REFERENCE_MSGID] [NAME]" >&2 exit 1;; *) [ -z "$name" ] || die "name already specified ($name)" name="$arg";; esac done [ -n "$name" ] || name="$(git symbolic-ref HEAD | sed 's#^refs/heads/##')" base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" || die "not a TopGit-controlled branch" if [ -n "$in_reply_to" ]; then send_email_args="$send_email_args --in-reply-to=$in_reply_to" fi patchfile="$(mktemp -t tg-mail.XXXXXX)" $tg patch "$name" >"$patchfile" header="$(sed -e '/^$/,$d' "$patchfile")" from="$(echo "$header" | grep '^From:' | sed 's/From:\s*//')" to="$(echo "$header" | grep '^To:' | sed 's/To:\s*//')" people= [ -n "$from" ] && people="$people --from '$from'" # FIXME: there could be multimple To [ -n "$to" ] && people="$people --to '$to'" # NOTE: git-send-email handles cc itself eval git send-email $send_email_args "$people" "$patchfile" rm "$patchfile" # vim:noet topgit-0.8/tg-patch.sh000066400000000000000000000034161125636276100147470ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 name= topic= diff_opts= diff_committed_only=yes # will be unset for index/worktree ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -i) topic='(i)' diff_opts="$diff_opts --cached"; diff_committed_only=;; -w) topic='(w)' diff_committed_only=;; -*) echo "Usage: tg [...] patch [-i | -w] [NAME]" >&2 exit 1;; *) [ -z "$name" ] || die "name already specified ($name)" name="$arg";; esac done [ -n "$name" -a -z "$diff_committed_only" ] && die "-i/-w are mutually exclusive with NAME" [ -n "$name" ] || name="$(git symbolic-ref HEAD | sed 's#^refs/\(heads\|top-bases\)/##')" base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" || die "not a TopGit-controlled branch" # if not index/worktree, topic is current branch [ -z "$topic" ] && topic="$name" setup_pager cat_file "$topic:.topmsg" echo [ -n "$(git grep $diff_opts '^[-]--' ${diff_committed_only:+"$name"} -- ".topmsg")" ] || echo '---' # Evil obnoxious hack to work around the lack of git diff --exclude git_is_stupid="$(mktemp -t tg-patch-changes.XXXXXX)" git diff --name-only $diff_opts "$base_rev" ${diff_committed_only:+"$name"} -- | fgrep -vx ".topdeps" | fgrep -vx ".topmsg" >"$git_is_stupid" || : # fgrep likes to fail randomly? if [ -s "$git_is_stupid" ]; then cat "$git_is_stupid" | xargs git diff --patch-with-stat $diff_opts "$base_rev" ${diff_committed_only:+"$name"} -- else echo "No changes." fi rm "$git_is_stupid" echo '-- ' echo "tg: ($base_rev..) $name (depends on: $(cat_file "$topic:.topdeps" | paste -s -d' '))" branch_contains "$name" "$base_rev" || echo "tg: The patch is out-of-date wrt. the base! Run \`$tg update\`." # vim:noet topgit-0.8/tg-push.sh000066400000000000000000000030531125636276100146240ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # GPLv2 ## Parse options recurse_deps=true tgish_deps_only=false dry_run= while [ -n "$1" ]; do arg="$1"; shift case "$arg" in --no-deps) recurse_deps=false;; --dry-run) dry_run=--dry-run;; --tgish-only) tgish_deps_only=true;; -h|--help) echo "Usage: tg push [--dry-run] [--no-deps] [--tgish-only] [-r remote] branch*" exit 0;; -r) remote="$1" shift ;; *) branches="$branches $arg";; esac done if [ -z "$remote" ]; then remote="$base_remote" fi if [ -z "$remote" ]; then die "no remote location given. Either use -r remote argument or set topgit.remote" fi if [ -z "$branches" ]; then branches="$(git symbolic-ref HEAD | sed 's#^refs/heads/##')" fi for name in $branches; do ref_exists "$name" || die "detached HEAD? Can't push $name" done _listfile="$(mktemp -t tg-push-listfile.XXXXXX)" trap "rm -f \"$_listfile\"" 0 push_branch() { # if so desired omit non tgish deps $tgish_deps_only && [ -z "$_dep_is_tgish" ] && return 0 echo "$_dep" >> "$_listfile" [ -z "$_dep_is_tgish" ] || echo "top-bases/$_dep" >> "$_listfile" } for name in $branches; do # current branch # re-use push_branch, which expects some pre-defined variables _dep="$name" _dep_is_tgish=1 ref_exists "top-bases/$_dep" || _dep_is_tgish= push_branch "$name" # deps but only if branch is tgish $recurse_deps && [ -n "$_dep_is_tgish" ] && no_remotes=1 recurse_deps push_branch "$name" # remove multiple occurrences of the same branch sort -u "$_listfile" | xargs git push $dry_run "$remote" done topgit-0.8/tg-remote.sh000066400000000000000000000040521125636276100151400ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 populate= # Set to 1 if we shall seed local branches with this name= ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in --populate) populate=1;; -*) echo "Usage: tg [...] remote [--populate] REMOTE" >&2 exit 1;; *) name="$arg";; esac done git config "remote.$name.url" >/dev/null || die "unknown remote '$name'" ## Configure the remote git config --replace-all "remote.$name.fetch" "+refs/top-bases/*:refs/remotes/$name/top-bases/*" "\\+refs/top-bases/\\*:refs/remotes/$name/top-bases/\\*" if git config --get-all "remote.$name.push" "\\+refs/top-bases/\\*:refs/top-bases/\\*" >/dev/null && test "xtrue" != "x$(git config --bool --get topgit.dontwarnonoldpushspecs)"; then info "Probably you want to remove the push specs introduced by an old version of topgit:" info ' git config --unset-all "remote.'$name'.push" "\\+refs/top-bases/\\*:refs/top-bases/\\*"' info ' git config --unset-all "remote.'$name'.push" "\\+refs/heads/\\*:refs/heads/\\*"' info '(or use git config --bool --add topgit.dontwarnonoldpushspecs true to get rid of this warning)' fi info "Remote $name can now follow TopGit topic branches." if [ -z "$populate" ]; then info "Next, do: git fetch $name" exit fi ## Populate local branches info "Populating local topic branches from remote '$name'..." git fetch "$name" git for-each-ref "refs/remotes/$name/top-bases" | while read rev type ref; do branch="${ref#refs/remotes/$name/top-bases/}" if git rev-parse "$branch" >/dev/null 2>&1; then git rev-parse "refs/top-bases/$branch" >/dev/null 2>&1 || git update-ref "refs/top-bases/$branch" "$rev" info "Skipping branch $branch: Already exists" continue fi info "Adding branch $branch..." git update-ref "refs/top-bases/$branch" "$rev" git update-ref "refs/heads/$branch" "$(git rev-parse "$name/$branch")" done git config "topgit.remote" "$name" info "The remote '$name' is now the default source of topic branches." # vim:noet topgit-0.8/tg-summary.sh000066400000000000000000000046641125636276100153530ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 terse= graphviz= ## Parse options while [ -n "$1" ]; do arg="$1"; shift case "$arg" in -t) terse=1;; --graphviz) graphviz=1;; *) echo "Usage: tg [...] summary [-t | --graphviz]" >&2 exit 1;; esac done curname="$(git symbolic-ref HEAD | sed 's#^refs/\(heads\|top-bases\)/##')" ! [ -n "$terse" -a -n "$graphviz" ] || die "-t and --graphviz options are mutual exclusive" if [ -n "$graphviz" ]; then cat < # or # | dot -Txlib digraph G { graph [ rankdir = "TB" label="TopGit Layout\n\n\n" fontsize = 14 labelloc=top pad = "0.5,0.5" ]; EOT fi ## List branches git for-each-ref refs/top-bases | while read rev type ref; do name="${ref#refs/top-bases/}" if branch_annihilated "$name"; then continue; fi; if [ -n "$terse" ]; then echo "$name" continue fi if [ -n "$graphviz" ]; then git cat-file blob "$name:.topdeps" | while read dep; do dep_is_tgish=true ref_exists "refs/top-bases/$dep" || dep_is_tgish=false if ! "$dep_is_tgish" || ! branch_annihilated $dep; then echo "\"$name\" -> \"$dep\";" fi done continue fi missing_deps= current=' ' [ "$name" != "$curname" ] || current='>' nonempty=' ' ! branch_empty "$name" || nonempty='0' remote=' ' [ -z "$base_remote" ] || remote='l' ! has_remote "$name" || remote='r' rem_update=' ' [ "$remote" != 'r' ] || ! ref_exists "refs/remotes/$base_remote/top-bases/$name" || { branch_contains "refs/top-bases/$name" "refs/remotes/$base_remote/top-bases/$name" && branch_contains "$name" "refs/remotes/$base_remote/$name" } || rem_update='R' [ "$rem_update" = 'R' ] || branch_contains "refs/remotes/$base_remote/$name" "$name" 2>/dev/null || rem_update='L' deps_update=' ' needs_update "$name" >/dev/null || deps_update='D' deps_missing=' ' [ -z "$missing_deps" ] || deps_missing='!' base_update=' ' branch_contains "$name" "refs/top-bases/$name" || base_update='B' if [ "$(git rev-parse "$name")" != "$rev" ]; then subject="$(git cat-file blob "$name:.topmsg" | sed -n 's/^Subject: //p')" else # No commits yet subject="(No commits)" fi printf '%s\t%-31s\t%s\n' "$current$nonempty$remote$rem_update$deps_update$deps_missing$base_update" \ "$name" "$subject" done if [ -n "$graphviz" ]; then echo '}' fi # vim:noet topgit-0.8/tg-update.sh000066400000000000000000000105031125636276100151250ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 name= ## Parse options if [ -n "$1" ]; then echo "Usage: tg [...] update" >&2 exit 1 fi name="$(git symbolic-ref HEAD | sed 's#^refs/\(heads\|top-bases\)/##')" base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" || die "not a TopGit-controlled branch" ## First, take care of our base depcheck="$(mktemp -t tg-depcheck.XXXXXX)" missing_deps= needs_update "$name" >"$depcheck" || : [ -z "$missing_deps" ] || die "some dependencies are missing: $missing_deps" if [ -s "$depcheck" ]; then # We need to switch to the base branch # ...but only if we aren't there yet (from failed previous merge) HEAD="$(git symbolic-ref HEAD)" if [ "$HEAD" = "${HEAD#refs/top-bases/}" ]; then switch_to_base "$name" fi cat "$depcheck" | sed 's/ [^ ]* *$//' | # last is $name sed 's/.* \([^ ]*\)$/+\1/' | # only immediate dependencies sed 's/^\([^+]\)/-\1/' | # now each line is +branch or -branch (+ == recurse) uniq -s 1 | # fold branch lines; + always comes before - and thus wins within uniq while read depline; do action="$(echo "$depline" | cut -c 1)" dep="$(echo "$depline" | cut -c 2-)" # We do not distinguish between dependencies out-of-date # and base/remote out-of-date cases for $dep here, # but thanks to needs_update returning : or % # for the latter, we do correctly recurse here # in both cases. if [ x"$action" = x+ ]; then info "Recursing to $dep..." git checkout -q "$dep" ( export TG_RECURSIVE="[$dep] $TG_RECURSIVE" export PS1="[$dep] $PS1" while ! $tg update; do # The merge got stuck! Let the user fix it up. info "You are in a subshell. If you abort the merge," info "use \`exit 1\` to abort the recursive update altogether." if ! sh -i " info "It is also safe to abort this operation using: git reset --hard $name" exit 3 fi # Go back but remember we want to merge with this, not base merge_with="$(git rev-parse HEAD)" git checkout -q "$name" fi fi ## Third, update our head with the base if branch_contains "$name" "$merge_with"; then info "The $name head is up-to-date wrt. the base." exit 0 fi info "Updating $name against new base..." if ! git merge "$merge_with"; then if [ -z "$TG_RECURSIVE" ]; then info "Please commit merge resolution. No need to do anything else" info "You can abort this operation using \`git reset --hard\` now" info "and retry this merge later using \`$tg update\`." else # subshell info "Please commit merge resolution and call exit." info "You can abort this operation using \`git reset --hard\`." fi exit 3 fi # vim:noet topgit-0.8/tg.sh000066400000000000000000000221531125636276100136510ustar00rootroot00000000000000#!/bin/sh # TopGit - A different patch queue manager # (c) Petr Baudis 2008 # GPLv2 TG_VERSION=0.8 ## Auxiliary functions info() { echo "${TG_RECURSIVE}tg: $*" } die() { info "fatal: $*" exit 1 } # cat_file "topic:file" # Like `git cat-file blob $1`, but topics '(i)' and '(w)' means index and worktree cat_file() { arg="$1" case "$arg" in '(w):'*) arg=$(echo "$arg" | tail --bytes=+5) cat "$arg" return ;; '(i):'*) # ':file' means cat from index arg=$(echo "$arg" | tail --bytes=+5) git cat-file blob ":$arg" ;; *) git cat-file blob "$arg" esac } # setup_hook NAME setup_hook() { hook_call="\"\$($tg --hooks-path)\"/$1 \"\$@\"" if [ -f "$git_dir/hooks/$1" ] && fgrep -q "$hook_call" "$git_dir/hooks/$1"; then # Another job well done! return fi # Prepare incantation if [ -x "$git_dir/hooks/$1" ]; then hook_call="$hook_call"' || exit $?' else hook_call="exec $hook_call" fi # Insert call into the hook { echo "#!/bin/sh" echo "$hook_call" [ ! -s "$git_dir/hooks/$1" ] || cat "$git_dir/hooks/$1" } >"$git_dir/hooks/$1+" chmod a+x "$git_dir/hooks/$1+" mv "$git_dir/hooks/$1+" "$git_dir/hooks/$1" } # setup_ours (no arguments) setup_ours() { if [ ! -s "$git_dir/info/attributes" ] || ! grep -q topmsg "$git_dir/info/attributes"; then { echo ".topmsg merge=ours" echo ".topdeps merge=ours" } >>"$git_dir/info/attributes" fi if ! git config merge.ours.driver >/dev/null; then git config merge.ours.name '"always keep ours" merge driver' git config merge.ours.driver 'touch %A' fi } # measure_branch NAME [BASE] measure_branch() { _bname="$1"; _base="$2" [ -n "$_base" ] || _base="refs/top-bases/$_bname" # The caller should've verified $name is valid _commits="$(git rev-list "$_bname" ^"$_base" -- | wc -l)" _nmcommits="$(git rev-list --no-merges "$_bname" ^"$_base" -- | wc -l)" if [ $_commits -gt 1 ]; then _suffix="commits" else _suffix="commit" fi echo "$_commits/$_nmcommits $_suffix" } # branch_contains B1 B2 # Whether B1 is a superset of B2. branch_contains() { [ -z "$(git rev-list --max-count=1 ^"$1" "$2" --)" ] } # ref_exists REF # Whether REF is a valid ref name ref_exists() { git rev-parse --verify "$@" >/dev/null 2>&1 } # has_remote BRANCH # Whether BRANCH has a remote equivalent (accepts top-bases/ too) has_remote() { [ -n "$base_remote" ] && ref_exists "remotes/$base_remote/$1" } branch_annihilated() { _name="$1"; # use the merge base in case the base is ahead. mb="$(git merge-base "refs/top-bases/$_name" "$_name")"; test "$(git rev-parse "$mb^{tree}")" = "$(git rev-parse "$_name^{tree}")"; } # recurse_deps CMD NAME [BRANCHPATH...] # Recursively eval CMD on all dependencies of NAME. # CMD can refer to $_name for queried branch name, # $_dep for dependency name, # $_depchain for space-seperated branch backtrace, # and the $_dep_is_tgish boolean. # It can modify $_ret to affect the return value # of the whole function. # If recurse_deps() hits missing dependencies, it will append # them to space-separated $missing_deps list and skip them. # remote dependencies are processed if no_remotes is unset. recurse_deps() { _cmd="$1"; shift _name="$1"; # no shift _depchain="$*" _depsfile="$(mktemp -t tg-depsfile.XXXXXX)" # If no_remotes is unset check also our base against remote base. # Checking our head against remote head has to be done in the helper. if test -z "$no_remotes" && has_remote "top-bases/$_name"; then echo "refs/remotes/$base_remote/top-bases/$_name" >>"$_depsfile" fi # if the branch was annihilated, there exists no .topdeps file if ! branch_annihilated "$_name"; then #TODO: handle nonexisting .topdeps? git cat-file blob "$_name:.topdeps" >>"$_depsfile"; fi; _ret=0 while read _dep; do if ! ref_exists "$_dep" ; then # All hope is lost missing_deps="$missing_deps $_dep" continue fi _dep_is_tgish=1 ref_exists "refs/top-bases/$_dep" || _dep_is_tgish= # Shoo shoo, keep our environment alone! [ -z "$_dep_is_tgish" ] || (recurse_deps "$_cmd" "$_dep" "$@") || _ret=$? eval "$_cmd" done <"$_depsfile" missing_deps="${missing_deps# }" rm "$_depsfile" return $_ret } # branch_needs_update # This is a helper function for determining whether given branch # is up-to-date wrt. its dependencies. It expects input as if it # is called as a recurse_deps() helper. # In case the branch does need update, it will echo it together # with the branch backtrace on the output (see needs_update() # description for details) and set $_ret to non-zero. branch_needs_update() { _dep_base_update= if [ -n "$_dep_is_tgish" ]; then if has_remote "$_dep"; then branch_contains "$_dep" "refs/remotes/$base_remote/$_dep" || _dep_base_update=% fi # This can possibly override the remote check result; # we want to sync with our base first branch_contains "$_dep" "refs/top-bases/$_dep" || _dep_base_update=: fi if [ -n "$_dep_base_update" ]; then # _dep needs to be synced with its base/remote echo "$_dep_base_update $_dep $_depchain" _ret=1 elif [ -n "$_name" ] && ! branch_contains "refs/top-bases/$_name" "$_dep"; then # Some new commits in _dep echo "$_dep $_depchain" _ret=1 fi } # needs_update NAME # This function is recursive; it outputs reverse path from NAME # to the branch (e.g. B_DIRTY B1 B2 NAME), one path per line, # inner paths first. Innermost name can be ':' if the head is # not in sync with the base or '%' if the head is not in sync # with the remote (in this order of priority). # It will also return non-zero status if NAME needs update. # If needs_update() hits missing dependencies, it will append # them to space-separated $missing_deps list and skip them. needs_update() { recurse_deps branch_needs_update "$@" } # branch_empty NAME branch_empty() { [ -z "$(git diff-tree "refs/top-bases/$1" "$1" -- | fgrep -v " .top")" ] } # switch_to_base NAME [SEED] switch_to_base() { _base="refs/top-bases/$1"; _seed="$2" # We have to do all the hard work ourselves :/ # This is like git checkout -b "$_base" "$_seed" # (or just git checkout "$_base"), # but does not create a detached HEAD. git read-tree -u -m HEAD "${_seed:-$_base}" [ -z "$_seed" ] || git update-ref "$_base" "$_seed" git symbolic-ref HEAD "$_base" } # Show the help messages. do_help() { if [ -z "$1" ] ; then # This is currently invoked in all kinds of circumstances, # including when the user made a usage error. Should we end up # providing more than a short help message, then we should # differentiate. # Petr's comment: http://marc.info/?l=git&m=122718711327376&w=2 ## Build available commands list for help output cmds= sep= for cmd in "@cmddir@"/tg-*; do ! [ -r "$cmd" ] && continue # strip directory part and "tg-" prefix cmd="$(basename "$cmd")" cmd="${cmd#tg-}" cmds="$cmds$sep$cmd" sep="|" done echo "TopGit v$TG_VERSION - A different patch queue manager" echo "Usage: tg [-r REMOTE] ($cmds|help) ..." elif [ -r "@cmddir@"/tg-$1 ] ; then setup_pager @cmddir@/tg-$1 -h 2>&1 || : echo if [ -r "@sharedir@/tg-$1.txt" ] ; then cat "@sharedir@/tg-$1.txt" fi else echo "`basename $0`: no help for $1" 1>&2 do_help exit 1 fi } ## Pager stuff # isatty FD isatty() { test -t $1 } # setup_pager # Spawn pager process and redirect the rest of our output to it setup_pager() { isatty 1 || return 0 # TG_PAGER = GIT_PAGER | PAGER | less # NOTE: GIT_PAGER='' is significant TG_PAGER=${GIT_PAGER-${PAGER-less}} [ -z "$TG_PAGER" -o "$TG_PAGER" = "cat" ] && return 0 # now spawn pager export LESS=${LESS:-FRSX} # as in pager.c:pager_preexec() _pager_fifo_dir="$(mktemp -t -d tg-pager-fifo.XXXXXX)" _pager_fifo="$_pager_fifo_dir/0" mkfifo -m 600 "$_pager_fifo" "$TG_PAGER" < "$_pager_fifo" & exec > "$_pager_fifo" # dup2(pager_fifo.in, 1) # this is needed so e.g. `git diff` will still colorize it's output if # requested in ~/.gitconfig with color.diff=auto export GIT_PAGER_IN_USE=1 # atexit(close(1); wait pager) trap "exec >&-; rm \"$_pager_fifo\"; rmdir \"$_pager_fifo_dir\"; wait" EXIT } ## Startup [ -d "@cmddir@" ] || die "No command directory: '@cmddir@'" ## Initial setup set -e git_dir="$(git rev-parse --git-dir)" root_dir="$(git rev-parse --show-cdup)"; root_dir="${root_dir:-.}" # Make sure root_dir doesn't end with a trailing slash. root_dir="${root_dir%/}" base_remote="$(git config topgit.remote 2>/dev/null)" || : tg="tg" # make sure merging the .top* files will always behave sanely setup_ours setup_hook "pre-commit" ## Dispatch # We were sourced from another script for our utility functions; # this is set by hooks. Skip the rest of the file. A simple return doesn't # work as expected in every shell. See http://bugs.debian.org/516188 if [ -z "$tg__include" ]; then if [ "$1" = "-r" ]; then shift if [ -z "$1" ]; then echo "Option -r requires an argument." >&2 do_help exit 1 fi base_remote="$1"; shift tg="$tg -r $base_remote" fi cmd="$1" [ -n "$cmd" ] || { do_help; exit 1; } shift case "$cmd" in help|--help|-h) do_help "$1" exit 0;; --hooks-path) # Internal command echo "@hooksdir@";; *) [ -r "@cmddir@"/tg-$cmd ] || { echo "Unknown subcommand: $cmd" >&2 do_help exit 1 } . "@cmddir@"/tg-$cmd;; esac fi # vim:noet