├── .gitignore ├── COPYING ├── Changes ├── Makefile ├── README.md ├── commit-partial ├── commit-partial.1 ├── commit-patch ├── commit-patch-buffer.el ├── commit-patch-buffer.nix ├── commit-patch.nix ├── cpanfile └── test ├── main.test ├── run-tests └── vcs ├── bzr.sh ├── cvs.sh ├── darcs.sh ├── git.sh ├── hg.sh ├── mtn.sh └── svn.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#* 3 | *.patch 4 | *.html 5 | commit-patch.1 6 | *.orig 7 | *.tmp 8 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | commit-patch (2.6.2) 2 | 3 | * Small changes to help debian packaging. 4 | 5 | -- David Caldwell Mon, 06 Sep 2021 12:19:25 -0700 6 | 7 | commit-patch (2.6.1) 8 | 9 | * Fixed a bug in `commit-patch-buffer` that caused `vc-annotate` to be 10 | out of date (https://github.com/caldwell/commit-patch/issues/14). 11 | 12 | -- David Caldwell Mon, 06 Sep 2021 08:48:57 -0700 13 | 14 | commit-patch (2.6) 15 | 16 | * Support --amend from the Emacs interface (Dima Kogan) 17 | 18 | * Fixed a bug with symlinked files in repos (Dima Kogan) 19 | 20 | * Allow Emacs config option `commit-patch-program` to be a function 21 | (Dima Kogan) 22 | 23 | -- David Caldwell Sun, 03 Feb 2019 00:24:11 -0800 24 | 25 | commit-patch (2.5.2) 26 | 27 | * Fixed dumb bug that caused "make install" to fail. 28 | 29 | -- David Caldwell Sun, 21 Sep 2014 18:15:04 -0700 30 | 31 | commit-patch (2.5.1) 32 | 33 | * We now use App::Fatpacker to bundle IPC::Run, removing the dependency 34 | from the release tarball. 35 | 36 | * Work around emacs/tramp bug in commit-patch-buffer.el (thanks Dima 37 | Kogan ). 38 | 39 | -- David Caldwell Sun, 21 Sep 2014 17:22:29 -0700 40 | 41 | commit-patch (2.5) 42 | 43 | * Added Monotone support. 44 | 45 | * Support Subversion version 1.7. 46 | 47 | * Fixed an issue with git where committing inside a repository's 48 | subdirectory would fail. 49 | 50 | * Fixed some cases where adding and removing files would fail. 51 | 52 | * Added a small, poor testing infrastructure. 53 | 54 | -- David Caldwell Wed, 15 May 2013 19:34:42 -0700 55 | 56 | commit-patch (2.4) 57 | 58 | * Added --retry option to commit-partial for retrying failed patches. 59 | 60 | * Added auto-detect support to commit-patch-buffer. Pass a prefix arg 61 | (C--) to make it revert to the old behavior and ask for a patch buffer 62 | and directory. 63 | 64 | -- David Caldwell Sun, 24 Oct 2010 15:23:22 -0700 65 | 66 | commit-patch (2.3) 67 | 68 | * Added Bazaar (bzr) support. 69 | 70 | * Added amend support (via new --amend flag). It's only supported by git 71 | and darcs at the moment. 72 | 73 | * It now gets rid of unnecessary spaces before and after commit message. 74 | 75 | * Allowed VISUAL/EDITOR environment variables to have spaces in them. 76 | (So that 'VISUAL="emacs -nw" commit-partial' works). 77 | 78 | * commit-partial now keeps the last few patch/commit-message files 79 | around for easy reuse when a commit fails. 80 | 81 | * Patches can now come from stdin. 82 | 83 | * commit-patch-buffer.el loads diff-mode on demand for faster emacs 84 | startups (Jim Radford ). 85 | 86 | * commit-patch-buffer.el can commit-patch a file loaded with tramp. 87 | 88 | * Fixed commit-patch-buffer.el so it works with Emacs 23 89 | (Jonathan Kotta ). 90 | 91 | * commit-patch-buffer.el only displays commit-patch output when there 92 | was an error (Jonathan Kotta ). 93 | 94 | -- David Caldwell Thu, 18 Mar 2010 20:02:43 -0700 95 | 96 | commit-patch (2.2) 97 | 98 | * Added SVN support from Peter Mack & Joel Hillacre . 99 | 100 | * Made commit-partial work with git. 101 | 102 | -- David Caldwell Fri, 16 Jan 2009 17:09:56 -0800 103 | 104 | commit-patch (2.1) 105 | 106 | * Added Git support. 107 | 108 | * Added "commit-partial". (inspired by Pistos ) 109 | 110 | * Added --verbose and --dry-run options. 111 | 112 | * Added --message-file option. (inspired by Pistos ) 113 | 114 | * Fix lsdiff on CVS sub directories. (thanks: Pistos ) 115 | 116 | -- David Caldwell Mon, 22 Dec 2008 17:05:14 -0800 117 | 118 | commit-patch (2.0) 119 | 120 | * Release 2.0 (rewritten perl version of cvs-commit-patch) 121 | 122 | -- David Caldwell Wed, 23 Aug 2006 17:04:33 -0700 123 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell perl -ne '/VERSION\s*=\s*'"'(.*)'"'/ and print "$$1"' commit-patch) 2 | 3 | BIN = commit-patch commit-partial 4 | MAN = commit-patch.1 commit-partial.1 5 | ELISP = commit-patch-buffer.el 6 | DOC = commit-patch.html README.md COPYING Changes 7 | ALL = $(BIN) $(MAN) $(ELISP) $(DOC) 8 | 9 | all: $(ALL) 10 | 11 | commit-patch.fat: commit-patch 12 | carton exec fatpack pack $< > $@.new || { rm -f $@.new; false; } 13 | mv -f $@.new $@ 14 | chmod +x $@ 15 | 16 | commit-patch.1: commit-patch 17 | pod2man -c "User Commands" $< > $@ 18 | 19 | commit-patch.html: commit-patch 20 | pod2html --title="commit-patch Documentation" $< > $@ 21 | 22 | release: commit-patch-$(VERSION).tar.gz 23 | 24 | commit-patch-$(VERSION).tar.gz: $(ALL) Makefile commit-patch.fat 25 | if ! git diff --quiet -- $^; then tput setab 1; tput setaf 3; /bin/echo -n 'WARNING: Directory is not clean!'; tput sgr0; echo; fi 26 | mkdir commit-patch-$(VERSION) 27 | rsync -a $^ commit-patch-$(VERSION) 28 | mv -f commit-patch-$(VERSION)/commit-patch.fat commit-patch-$(VERSION)/commit-patch 29 | tar czf commit-patch-$(VERSION).tar.gz commit-patch-$(VERSION) 30 | rm -rf commit-patch-$(VERSION) 31 | 32 | test: 33 | ./test/run-tests 34 | .PHONY: test 35 | 36 | PREFIX=/usr/local 37 | LISPDIR=/share/emacs/site-lisp 38 | install: $(ALL) 39 | mkdir -p "$(PREFIX)/bin" 40 | mkdir -p "$(PREFIX)/share/man/man1" 41 | mkdir -p "$(PREFIX)$(LISPDIR)" 42 | mkdir -p "$(PREFIX)/share/doc/commit-patch" 43 | cp -a $(BIN) "$(PREFIX)/bin" 44 | cp -a $(MAN) "$(PREFIX)/share/man/man1" 45 | cp -a $(ELISP) "$(PREFIX)$(LISPDIR)" 46 | cp -a $(DOC) "$(PREFIX)/share/doc/commit-patch" 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | commit-patch 2 | ============ 3 | 4 | Commit patches to Darcs, Git, Mercurial, Bazaar, Monotone, Subversion, or CVS 5 | 6 | 7 | Installation 8 | ------------ 9 | 10 | On a recent Debian/Ubuntu: 11 | 12 | apt install commit-patch 13 | 14 | Anywhere else: Install the prerequisites below and download the release 15 | tarball from the [homepage](https://porkrind.org/commit-patch/). This 16 | tarball contains a "fatpacked" version of the `commit-patch` perl script 17 | with the external perl dependencies embedded. Then put the commit-patch 18 | and commit-partial binaries into your `PATH`. 19 | 20 | 21 | Prerequisites 22 | ------------- 23 | 24 | `commit-patch` is known to run on Linux and Mac OS X. It is perl, 25 | so ideally it will run anywhere, but we have never tested in 26 | other environments, most notably Windows. Use at your own risk. 27 | 28 | `commit-patch` relies on several programs to get the job done: 29 | 30 | - [perl](https://www.perl.org) 31 | - [patch](https://www.gnu.org/software/patch/) 32 | - [interdiff](http://cyberelk.net/tim/software/patchutils/) 33 | - cp - Ideally installed on your system already. :-) 34 | 35 | and, of course, one of: 36 | 37 | - [git](https://git-scm.com/) 38 | - [mercurial](https://subversion.apache.org/) 39 | - [darcs](http://darcs.net/) 40 | - [subversion](http://subversion.tigris.org/) 41 | - [cvs](https://www.nongnu.org/cvs/) 42 | - [bazaar](https://bazaar.canonical.com/) 43 | - [monotone](https://www.monotone.ca/) 44 | 45 | ## Installing Prerequisites 46 | 47 | On Debian/Ubuntu: 48 | 49 | apt-get install patch patchutils 50 | 51 | On Fedora: 52 | 53 | yum install patch patchutils 54 | 55 | On Mac OS X w/ [Homebrew](https://brew.sh) 56 | 57 | brew install patchutils 58 | 59 | 60 | Instructions 61 | ------------ 62 | 63 | ### commit-patch 64 | 65 | See the man page or perldoc: 66 | 67 | man ./commit-patch.1 68 | perldoc commit-patch 69 | 70 | 71 | ### commit-patch-buffer.el 72 | 73 | commit-patch-buffer.el is an emacs interface to `commit-patch`. It 74 | allows you to just hit `C-c C-c` in any patch buffer to apply and commit 75 | only the changes indicated by the patch, regardless of the changes in 76 | your working directory. 77 | 78 | To use commit-patch-buffer with diff mode automatically, add this to 79 | your emacs init file: 80 | 81 | (eval-after-load 'diff-mode 82 | '(require 'commit-patch-buffer nil 'noerror)) 83 | 84 | The easy way of working with commit-patch-buffer is to `M-x vc-diff` a 85 | file (or `M-x vc-root-diff` your whole project) then kill, split or edit 86 | the resulting hunks using diff mode's built-in commands and to then hit 87 | `C-c C-c` to commit the patch. 88 | 89 | 90 | Development 91 | ----------- 92 | 93 | `commit-patch` uses [Carton](https://github.com/perl-carton/carton) for 94 | local development. Once Carton is installed: 95 | 96 | carton install 97 | 98 | After than `commit-patch` and `commit-partial` should work. There is no 99 | need to `carton exec` them—the code autodetects the local libs. 100 | 101 | 102 | Homepage 103 | -------- 104 | https://porkrind.org/commit-patch/ 105 | 106 | Authors 107 | ------- 108 | - David Caldwell 109 | - Jim Radford 110 | 111 | Copyright and License 112 | --------------------- 113 | Copyright © 2003-2021 by David Caldwell and Jim Radford. 114 | 115 | `commit-patch` is distributed under the GNU General Public 116 | License. See the COPYING file in the distribution for more 117 | details. 118 | -------------------------------------------------------------------------------- /commit-partial: -------------------------------------------------------------------------------- 1 | commit-patch -------------------------------------------------------------------------------- /commit-partial.1: -------------------------------------------------------------------------------- 1 | commit-patch.1 -------------------------------------------------------------------------------- /commit-patch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # Copyright © 2003-2021 David Caldwell 3 | # and Jim Radford 4 | # This code can be distributed under the terms of the GNU Public License (Version 2 or greater). 5 | 6 | my $VERSION = '2.6.2'; 7 | my $FALLBACK_EDITOR = 'vi'; 8 | 9 | use strict; 10 | use Cwd qw(abs_path); 11 | use File::Basename; 12 | use lib dirname(abs_path($0))."/local/lib/perl5"; 13 | use IPC::Run; 14 | use File::Temp qw(tempfile); 15 | use Getopt::Long; 16 | use Pod::Usage; 17 | 18 | my ($dry_run, $verbose); 19 | sub run { 20 | if ($verbose || $dry_run) { 21 | for (@_) { 22 | print join ' ', map { $_ =~ / / ? "\"$_\"" : $_ } @$_ if ref $_ eq 'ARRAY'; 23 | print " $_ " if ref $_ eq ''; 24 | print '[',ref $_,'] ' if ref $_ ne 'ARRAY' && ref $_ ne ''; 25 | } 26 | print "\n"; 27 | } 28 | return 1 if $dry_run; 29 | IPC::Run::run(@_); 30 | } 31 | 32 | sub trim($) { 33 | my $s = shift; 34 | $s =~ s/^\s*//; 35 | $s =~ s/\s*$//; 36 | $s 37 | } 38 | 39 | my %clean; # Rename keys to values on any error (rollback). Unlinks keys on success. 40 | my $repo="."; 41 | my $amend; 42 | my %vc; 43 | while (!$vc{name}) { 44 | if (-d "$repo/CVS" && $repo eq '.') { 45 | %vc = (name => 'cvs', 46 | diff => 'cvs diff -Nu', 47 | commit => 'cvs commit', 48 | message => sub { ('-m', $_[0]) }, 49 | message_file => sub { ('-F', $_[0]) }, 50 | add => 'cvs add', 51 | remove => 'cvs rm', 52 | patcharg => '-p0', 53 | lsdiffarg => []); 54 | } elsif (-d "$repo/.svn") { 55 | %vc = (name => 'svn', 56 | diff => 'svn diff -x -u', 57 | commit => 'svn commit', 58 | message => sub { ('-m', $_[0]) }, 59 | message_file => sub { ('-F', $_[0]) }, 60 | add => 'svn add', 61 | remove => 'svn delete', 62 | patcharg => '-p0', 63 | lsdiffarg => []); 64 | } elsif (-d "$repo/_darcs") { 65 | %vc = (name => 'darcs', 66 | diff => 'darcs diff -u', 67 | add => 'darcs add', 68 | remove => 'true', 69 | commit => 'darcs record --all', 70 | amend => 'darcs amend-record --all', 71 | patcharg => '-p1', 72 | lsdiffarg => [qw(--strip 1)], 73 | message => sub { 74 | return () if $amend; # Darcs amend doesn't have --logfile, so don't support comments on amend. 75 | # Darcs doesn't like multiline -m comments so we have to put the log message into a file and use --logfile. Yuck. 76 | #return ('-m', $_[0]); 77 | my $message = $_[0]; 78 | $message .= "\n" unless $message =~ /\n$/s; # Darcs does screwey stuff when logfile has no trailing \n. 79 | my ($message_file, $message_filename) = tempfile("commit-patch-message-XXXXXXXX", UNLINK=>0); 80 | print $message_file $message; 81 | close $message_file; 82 | $clean{$message_filename} = undef; # make sure we delete this file on exit. 83 | ("--logfile=$message_filename"); 84 | }, 85 | message_file => sub { die "Darcs doesn't support --message-file and --amend" if $amend; ("--logfile=$_[0]" ) }); 86 | } elsif (-d "$repo/.hg") { 87 | %vc = (name => 'hg', 88 | diff => 'hg diff', 89 | commit => 'hg commit', 90 | message => sub { ('-m', $_[0]) }, 91 | message_file => sub { ('--logfile', $_[0]) }, 92 | add => 'hg addremove', 93 | remove => 'true', 94 | patcharg => '-p1', 95 | lsdiffarg => [qw(--strip 1)]); 96 | } elsif (-d "$repo/.bzr") { 97 | %vc = (name => 'bzr', 98 | diff => 'bzr diff', 99 | commit => 'bzr commit', 100 | message => sub { ('-m', $_[0]) }, 101 | message_file => sub { ('--file', $_[0]) }, 102 | add => 'bzr add', 103 | remove => 'true', 104 | patcharg => '-p0', 105 | lsdiffarg => []); 106 | chdir $repo; # otherwise commit-partial from within a project subdir fails. 107 | } elsif (-d "$repo/.git") { 108 | %vc = (name => 'git', 109 | diff => 'git diff --relative', # Use --relative here because "git diff | git apply --cached" fails to apply hunks from files not in your current dir tree 110 | commit => 'git commit', 111 | amend => 'git commit --amend', 112 | message => sub { ('-m', $_[0]) }, 113 | message_file => sub { ('-F', $_[0]) }, 114 | previous_message => sub { my $prev; run([qw(git log -1 --pretty=format:%s%n%b)], '>', \$prev); $prev }); 115 | # Git is special cased below. 116 | } elsif (-d "$repo/_MTN") { 117 | %vc = (name => 'mtn', 118 | diff => 'mtn automate content_diff', 119 | commit => 'mtn commit', 120 | message => sub { ('-m', $_[0]) }, 121 | message_file => sub { ('--message-file', $_[0]) }, 122 | add => 'mtn add', 123 | remove => 'mtn drop', 124 | patcharg => '-p0', 125 | lsdiffarg => []); 126 | } else { 127 | $repo.="/.."; 128 | printf("Trying back a dir: $repo, abs:%s\n", abs_path($repo)); 129 | die "couldn't find repo" if abs_path($repo) eq "/"; 130 | } 131 | } 132 | 133 | my $commit_partial = $0 =~ /commit-partial/; 134 | my $commit_patch = !$commit_partial; 135 | 136 | my ($message, $message_filename, $retry); 137 | GetOptions("h|help" => sub { pod2usage(1) }, 138 | "n|dry-run" => \$dry_run, 139 | "a|amend" => \$amend, 140 | "v|verbose" => \$verbose, 141 | "version" => sub { print "Version $VERSION\n"; exit }, 142 | $commit_patch ? 143 | ("m|message=s" => \$message, 144 | "F|message-file=s" => \$message_filename) 145 | : # commit-partial 146 | ("r|retry" => \$retry), 147 | ) and ($commit_patch && scalar @ARGV <= 1 || 148 | $commit_partial) or pod2usage(2); 149 | 150 | my $patch; 151 | if ($commit_patch) { 152 | if (scalar @ARGV == 1) { 153 | $patch = shift; 154 | } else { 155 | (my $p, $patch) = tempfile("commit-patch-patch-XXXXXXXX", UNLINK=>0); 156 | print $p $_ while (<>); 157 | $clean{$patch} = undef; 158 | } 159 | } 160 | 161 | my $vc_commit = $amend ? ($vc{amend} || die "$vc{name} does not support amending.\n") : $vc{commit}; 162 | 163 | # commit-partial creates a patch file for them and launches their editor. 164 | if ($commit_partial) { 165 | $patch = "commit.$$.patch"; 166 | $clean{$patch} = undef; 167 | 168 | my $previous_message = !$amend ? '' : $vc{previous_message} && trim $vc{previous_message}->(); 169 | $message = $previous_message; 170 | if ($retry) { 171 | use File::Copy; 172 | copy("#last-comit-partial.patch", $patch); 173 | } else { 174 | open PATCH, '>', $patch or die "$patch: $!\n"; 175 | print PATCH "$message\n", 176 | "# Enter your commit comment above and edit your patch below.\n", 177 | "# (Lines starting with # will be stripped and leading and trailing whitespace removed).\n", 178 | "# An empty comment will cancel the operation.\n", 179 | "==END_COMMENT==\n" if defined $message; 180 | close PATCH; 181 | 182 | run([split(/ /,$vc{diff}), @ARGV], '>>', $patch); 183 | } 184 | system(($ENV{VISUAL} || $ENV{EDITOR} || $FALLBACK_EDITOR)." ".$patch); 185 | my $last_patch = "#last-comit-partial.patch"; 186 | unlink "$last_patch.3"; 187 | rename "$last_patch.2", "$last_patch.3"; 188 | rename "$last_patch", "$last_patch.2"; 189 | link $patch, $last_patch; 190 | 191 | if (defined $message) { 192 | open PATCH, '<', $patch or die "couldn't read $patch: $!\n"; 193 | while () { 194 | next if /^\#/; 195 | goto finished if /^==END_COMMENT==$/; 196 | $message .= $_; 197 | } 198 | die "Couldn't find comment terminator (==END_COMMENT==)\n"; 199 | finished: 200 | $message = trim($message); 201 | undef $message if $amend && $message eq $previous_message; 202 | } 203 | 204 | die "No commit message, so I'm not going to do anything.\n" if !$amend && $message eq ''; 205 | } 206 | 207 | die "bad patch file: $!" if -z $patch; 208 | die "Invalid message" if !$amend && defined $message && $message eq ""; 209 | 210 | my @message_opt = $message ? $vc{message}->($message) : 211 | $message_filename ? $vc{message_file}->($message_filename) : (); 212 | 213 | # Git pretty much supports what commit-patch does already, so we special case it here. 214 | if ($vc{name} eq 'git') { 215 | run([qw(git diff --cached --quiet)]) or die "The index is not empty. Cowardly refusing to do anything potentially harmful.\n"; 216 | run([qw(git apply --cached), $patch]) or die "Git failed.\n"; 217 | run([split(/ /,$vc_commit), @message_opt]) or die "Git failed.\n"; 218 | exit; 219 | } 220 | 221 | my ($lsdiff_out, $err); 222 | run ["lsdiff", '-s', @{$vc{lsdiffarg}}, $patch], '>', \$lsdiff_out, '2>', \$err or die "lsdiff -s: $! ($err)"; 223 | my %lsdiff = map { /^([-+!])\s+(.*)$/ or die "bad lsdiff line: $_\nOut:\n$lsdiff_out"; ( $2 => $1 ) } split(/\n/, $lsdiff_out); 224 | my @added = grep { $lsdiff{$_} eq '+' } keys %lsdiff; 225 | my @removed = grep { $lsdiff{$_} eq '-' } keys %lsdiff; 226 | my @changed = grep { $lsdiff{$_} eq '!' } keys %lsdiff; 227 | die "No files in patch" unless scalar %lsdiff; 228 | 229 | #print "Found $vc{name} in $repo\n"; 230 | #printf("files: %s\n", join(",", @files)); 231 | 232 | for my $f (@changed, @added, @removed) { 233 | run ["cp", "-f", $f, "$f.orig.$$"] or die "couldn't make backup of $f: $!" if -f $f; 234 | $clean{"$f.orig.$$"} = $f; 235 | unlink $f if grep { $_ eq $f } @added; # Needs to be gone so patch can create it 236 | } 237 | $SIG{PIPE} = $SIG{INT} = $SIG{QUIT} = sub { print "Cleanly aborting..\n"; }; 238 | 239 | my ($out, $working_patch, $non_committed_patch); 240 | run([split(/ /,$vc{diff}), @changed, @removed], '>', \$working_patch, '2>', \$err);# CVS diff dies. Sigh. or die "$err\n"; 241 | unless ($working_patch =~ /^\s*$/s) { # Work around an apparent bug in darcs () 242 | run(["interdiff", $vc{patcharg}, $patch, '/dev/stdin'], '<', \$working_patch, 243 | '>', \$non_committed_patch, '2>', \$err) or die "$err\n"; 244 | run([qw"patch -R --force", $vc{patcharg}], '<', \$working_patch, '>', \$out, '2>', \$err) or die "$out\n$err\n"; 245 | } 246 | run(["patch", $vc{patcharg}], '<', $patch, '>', \$out, '2>', \$err) or die "patch: $out\n$err\n"; 247 | run([split(/ /,$vc{add}), @added], '>', \$out, '2>', \$err) or die "$vc{add}: $out\n$err\n" if @added; 248 | run([split(/ /,$vc{remove}), @removed], '>', \$out, '2>', \$err) or die "$vc{remove}: $out\n$err\n" if @removed; 249 | # Don't capture stdout or stderr because it can be interactive (cough cough darcs) 250 | run([split(/ /,$vc_commit), @message_opt, @added, @removed, @changed]) or die "commit failed.\n"; 251 | run(["patch", $vc{patcharg}], '<', \$non_committed_patch, '>', \$out, '2>', \$err) or die "patch: $out\n$err\n"; 252 | 253 | END { 254 | return if $dry_run; 255 | if ($?) { # Did we die? 256 | foreach my $k (grep { $clean{$_} } keys %clean) { rename $k,$clean{$k}; delete $clean{$k} } 257 | } 258 | foreach my $k (keys %clean) { unlink $k } 259 | } 260 | 261 | 262 | =encoding utf-8 263 | 264 | =head1 NAME 265 | 266 | commit-patch - commit patches to I, I, I, I, I, I, or I repositories 267 | 268 | =head1 SYNOPSIS 269 | 270 | commit-patch [B<--amend>] [B<-m> I] [B<-F> I] [B<-v>] [B<--dry-run>] [I] 271 | 272 | commit-partial [B<--amend>] [B<-v>] [B<--dry-run>] [B<--retry>] [I ...] 273 | 274 | =head1 DESCRIPTION 275 | 276 | Normally version control systems don't allow fine grained 277 | commits. B allows the user to control I what 278 | gets committed (or "recorded", in I parlance) by letting the user 279 | supply a patch to be committed rather than using the files in the 280 | current working directory. If I is not supplied on the 281 | command line then the patch will be read from standard input. 282 | 283 | B is like commit-patch except that it will create a 284 | patch from the current changes in the current working directory and 285 | launch your editor so that you can edit the patch and the commit 286 | message (using the B environment variable, or if that isn't 287 | set the B environment variable, or, if I isn't set, 288 | B). Any files you specify will be passed to your version control's 289 | diff command. 290 | 291 | B currently supports the following version control systems: 292 | B>, B>, B>, B>, B>, B>, and B>. 293 | 294 | =head1 OPTIONS 295 | 296 | B<-a>, B<--amend> - Amend a previous commit. Currently only B> and 297 | B> support this option. When used with B> it will amend the 298 | previous commit. When used with B>, B> will ask you which 299 | patch you want to amend. 300 | 301 | B<-m>, B<--message>=I - An optional I to use as the commit 302 | text. If the message is multiple lines then I, I, and I 303 | will use the first line as the patch name and the rest as commit 304 | details. If the C<-m> option is not specified then the result will be 305 | the same as whatever the underlying version control system would do if 306 | you didn't specify a message name on the command line. That is, 307 | B does not interfere with the patch naming process of 308 | the underlying version control system; I will still ask you 309 | interactively; I and I will still launch your editor. 310 | 311 | B<-F>, B<--message-file>=I - You can optionally get the 312 | commit message from a file. This is generally only useful for 313 | scripting B. 314 | 315 | B<-v>, B<--verbose> - Turn on debugging. This will print the commands 316 | that B is running to get the patch committed. 317 | 318 | B<-n>, B<--dry-run> - Turn on more paranoid debugging. This will print 319 | the commands that B will run to get the patch committed 320 | but it won't actually run those commands. 321 | 322 | B<-r>, B<--retry> - Only available in I. This will reload the 323 | last patch that was attempted to be committed into your editor instead of the 324 | current changes in the directory. This is for cases where the patch fails to 325 | commit for some reason and you want to try to fix it instead of starting over. 326 | 327 | =head1 DIAGNOSTICS 328 | 329 | B works by manipulating the working directory using 330 | C, C, and the underlying version control system's 331 | C. If any part of the process fails, B will 332 | attempt to restore the working directory to the state it was before 333 | the command was run. Any errors from the underlying version control 334 | system or from patch will be printed. 335 | 336 | =head1 CAVEATS 337 | 338 | The patch specified on the command line must originate from the same 339 | place as the current directory. That is, the following will not work: 340 | 341 | cvs diff -u > ../a.patch 342 | cd .. 343 | commit-patch a.patch 344 | 345 | You B run B from the same directory that the 346 | original patch was based from. 347 | 348 | I, I and I put C and C in front of all the paths 349 | in the diff output. Don't worry about this; B takes it into 350 | account. 351 | 352 | =head1 EXAMPLES 353 | 354 | Typical I usage: 355 | 356 | cvs diff -u > a.patch 357 | emacs a.patch 358 | commit-patch a.patch 359 | 360 | I usage with a message specified: 361 | 362 | hg diff > a.patch 363 | emacs a.patch 364 | commit-patch -m "This is a commit message" a.patch 365 | 366 | I usage with a multi-line message specified: 367 | 368 | darcs diff -u > a.patch 369 | emacs a.patch 370 | commit-patch -m 'This is the patch name 371 | Here are the patch details' a.patch 372 | 373 | =head1 AUTHORS 374 | 375 | =over 376 | 377 | =item * 378 | 379 | David Caldwell 380 | 381 | =item * 382 | 383 | Jim Radford 384 | 385 | =back 386 | 387 | =head1 COPYRIGHT AND LICENSE 388 | 389 | Copyright © 2003-2021 by David Caldwell and Jim Radford. 390 | 391 | B is distributed under the GNU General Public 392 | License. See the COPYING file in the distribution for more details. 393 | 394 | =head1 HISTORY 395 | 396 | B was originally called C and was a 397 | bash script written in 2003 by Jim Radford (with David Caldwell in the 398 | room drawing the procedure on a white board). David later converted it 399 | do C, then integrated them back together into 400 | B. I support was then added. At some point 401 | David translated from bash into perl because funky bash quoting issues 402 | were causing problems with a repository that had a space in one of the 403 | directory names. 404 | 405 | =cut 406 | -------------------------------------------------------------------------------- /commit-patch-buffer.el: -------------------------------------------------------------------------------- 1 | ;;; commit-patch-buffer.el --- commit patches to Darcs, Git, Mercurial, Bazaar, Monotone, Subversion, or CVS repositories 2 | 3 | ;; Copyright 2003-2021 Jim Radford 4 | ;; and David Caldwell 5 | ;; This code can be distributed under the terms of the GNU Public License (Version 2 or greater). 6 | ;; 7 | ;; Version: 2.6.2 8 | ;; 9 | ;; Author: Jim Radford 10 | ;; David Caldwell 11 | 12 | ;;; Commentary: 13 | 14 | ;; commit-patch-buffer provides an Emacs front end to the commit-patch(1) 15 | ;; program. Typically the patch to commit would be obtained with vc-diff 16 | ;; ("C-c v ="), though any Emacs diff-mode buffer can be committed. 17 | ;; 18 | ;; Typing "C-c C-c" in a diff-mode buffer kicks off the process and brings 19 | ;; up a buffer for the commit comment. After entering a suitable comment, 20 | ;; type "C-c C-c" again to finish the commit. If commit-patch-buffer cannot 21 | ;; automatically detect the repository directory, it will ask for it 22 | ;; interactively. 23 | ;; 24 | ;; commit-patch-buffer-in-directory is also available: this function skips 25 | ;; the automagical repository detection logic if the user wants to directly 26 | ;; specify the buffer to commit and directory. 27 | 28 | ;;; Code: 29 | 30 | (require 'vc) 31 | (require 'log-edit) 32 | 33 | 34 | (defcustom commit-patch-program 35 | ;; Prefer a locally installed commit patch over one installed in the PATH. 36 | (let* ((this-path (or load-file-name (buffer-file-name))) 37 | (local-path (expand-file-name "commit-patch" (file-name-directory this-path)))) 38 | (if (file-executable-p local-path) local-path "commit-patch")) 39 | "The pathname of the commit-patch executable to use. This could be a 40 | string, or a function. If a function, then this is called every 41 | time the program path is needed to retrieve the path. The 42 | function takes no arguments, but can use variables such as 43 | `default-directory'. This is useful to find commit-patch in different 44 | places on different machines when using TRAMP." 45 | :type '(choice 46 | (string :tag "Program path") 47 | (function :tag "Function that returns the program path")) 48 | :group 'commit-patch) 49 | 50 | (defun commit-patch--get-program () 51 | "Retrieves the path to the commit-patch program. This is the value of 52 | the `commit-patch-program' variable, if it is a string, or the value 53 | returned by the `commit-patch-program' function, if it is a function." 54 | (let ((program 55 | (cond 56 | ((stringp commit-patch-program) commit-patch-program) 57 | ((functionp commit-patch-program) (funcall commit-patch-program)) 58 | (t (error "commit-patch-program must be a string or function: %s" 59 | commit-patch-program))))) 60 | (or program (error "commit-patch-program is nil!")))) 61 | 62 | ;; Based on vc-git-expanded-log-entry, but don't indent and only grab the full comment using --pretty 63 | (defun commit-patch-git-log-comment (revision) 64 | (with-temp-buffer 65 | (apply 'vc-git-command t nil nil (list "log" "--pretty=format:%B" revision "-1")) 66 | (goto-char (point-min)) 67 | (unless (eobp) 68 | (buffer-string)))) 69 | 70 | ;; Currently commit-patch only supports --amend with Git and Darcs. 71 | ;; But Darcs amend is very interactive, so we don't support it here. 72 | (defun commit-patch-last-log-comment (directory) 73 | (let ((default-directory directory)) 74 | (pcase (vc-responsible-backend directory) 75 | (`Git 76 | (commit-patch-git-log-comment "HEAD"))))) 77 | 78 | 79 | (defun commit-patch-buffer-in-directory (buffer directory &optional amend) 80 | "Commit the patch found in BUFFER by applying it to the 81 | repository in DIRECTORY with commit-patch(1). If AMEND is 82 | non-nil, we amend the previous commit instead of creating a new 83 | one." 84 | (interactive "bBuffer to commit: \nDDirectory: \nP") 85 | (let* ((patch-files (with-temp-buffer 86 | (let ((lsdiff (current-buffer))) 87 | (when (eq 0 (with-current-buffer buffer 88 | (call-process-region (point-min) (point-max) 89 | "lsdiff" nil lsdiff nil))) 90 | (split-string (buffer-string)))))) 91 | (log-buffer-name (if amend "*amend*" "*commit*")) 92 | (f patch-files) visiting-buffers) 93 | (while (car f) 94 | (let* ((default-directory directory) 95 | (buf (or 96 | (find-buffer-visiting (expand-file-name (car f))) 97 | (find-buffer-visiting (expand-file-name (diff-filename-drop-dir (car f))))))) 98 | (when buf 99 | (with-current-buffer buf (vc-buffer-sync)) 100 | (add-to-list 'visiting-buffers buf))) 101 | (setq f (cdr f))) 102 | (if amend 103 | (with-current-buffer (get-buffer-create log-buffer-name) 104 | (erase-buffer) 105 | (insert (or (commit-patch-last-log-comment directory) "")) 106 | (goto-char 0))) 107 | (log-edit 108 | `(lambda () (interactive) 109 | (let ((patch (make-temp-file "commit-buffer" nil)) 110 | (comment (buffer-string)) 111 | (output-buffer (get-buffer-create "*commit-patch*"))) 112 | (unwind-protect 113 | (progn 114 | (with-current-buffer ,buffer 115 | (write-region (point-min) (point-max) patch)) 116 | (with-current-buffer output-buffer 117 | (erase-buffer) 118 | (let* ((default-directory ,directory) 119 | (status (apply 'process-file (commit-patch--get-program) patch 120 | output-buffer nil 121 | (append `("-m" ,comment) 122 | (if ,amend '("--amend")))))) 123 | (if (not (eq status 0)) 124 | (progn 125 | (window-buffer (display-buffer output-buffer)) 126 | (message "Commit patch failed with a status of '%S' (%S)." status patch)) 127 | (mapc (lambda (buf) (with-current-buffer buf 128 | (vc-resynch-buffer (buffer-file-name buf) 'revert 'noquery) 129 | ;; stupid vc-revert-buffer1 doesn't call revert-buffer 130 | ;; with preserve-modes which means the CVS version doesn't 131 | ;; get updated, so we do it by hand. 132 | (run-hooks 'find-file-hooks))) 133 | ',visiting-buffers) 134 | (message "Patched and %s %S file(s) and reverted %S." 135 | (if ,amend "amended" "committed") 136 | ,(length patch-files) ,(length visiting-buffers)))))) 137 | (delete-file patch)))) 138 | nil 139 | `((log-edit-listfun . (lambda () ',patch-files))) 140 | log-buffer-name))) 141 | 142 | (defun commit-patch-buffer (&optional arg amend) 143 | "Commit the patch in the current buffer, applying it to the 144 | repository in the appropriate directory with commit-patch(1). If 145 | the current buffer is not in diff-mode or ARG is non-nil then it 146 | will ask interactively which buffer to commit and to which 147 | directory to commit it. If AMEND is non-nil, we amend the 148 | previous commit instead of creating a new one." 149 | (interactive "P") 150 | (if (and (not arg) (eq major-mode 'diff-mode)) 151 | (commit-patch-buffer-in-directory (buffer-name) (autodetect-patch-directory-root) amend) 152 | 153 | (let ((current-prefix-arg amend)) 154 | (call-interactively 'commit-patch-buffer-in-directory)))) 155 | 156 | (defun commit-patch-buffer-amend (&optional arg) 157 | "Commit the patch in the current buffer, applying it to the 158 | repository in the appropriate directory with commit-patch(1). If 159 | the current buffer is not in diff-mode or ARG is non-nil then it 160 | will ask interactively which buffer to commit and to which 161 | directory to commit it. This is identical to 162 | `commit-patch-buffer' except it amends the last commit by default 163 | instead of creating a new one." 164 | (interactive "P") 165 | (commit-patch-buffer arg t)) 166 | 167 | (defun autodetect-patch-directory-root () 168 | "Tries to autodect where a patch should be committed from using the 169 | following algorithm: 170 | 171 | 1. Grab the path mentioned in the first diff hunk of the 172 | current buffer and its buffer's full path. 173 | 174 | 2. Strip common files/directories from end of paths. 175 | 176 | 3. Return whatever is left over of buffer's path." 177 | (save-excursion 178 | (beginning-of-buffer) 179 | (diff-hunk-next) ;; Have to be in a hunk or diff-hunk-file-names won't work. 180 | (let ((diff-path (reverse (split-string (car (diff-hunk-file-names)) "/"))) 181 | (file-path (reverse (split-string (file-chase-links (buffer-file-name (car (diff-find-source-location)))) "/")))) 182 | (while (string-equal (car file-path) (car diff-path)) 183 | (setq file-path (cdr file-path)) 184 | (setq diff-path (cdr diff-path))) 185 | ;; The extra "" here adds a / onto the end of our directory. Otherwise call-process (via process-file) 186 | ;; calls unhandled-file-name-directory which strips the last part of the path off if it doesn't end with 187 | ;; a /. Yes, this took multiple hours to figure out. 188 | (combine-and-quote-strings (reverse (cons "" file-path)) "/")))) 189 | 190 | (eval-after-load 'diff-mode '(progn 191 | (setq diff-default-read-only nil) 192 | (define-key diff-mode-map "\C-c\C-c" 'commit-patch-buffer) 193 | (define-key diff-mode-map "\C-xvv" 'commit-patch-buffer) 194 | (define-key diff-mode-map (kbd "C-c C-S-c") 'commit-patch-buffer-amend))) 195 | 196 | (provide 'commit-patch-buffer) 197 | 198 | ;;; commit-patch-buffer.el ends here 199 | -------------------------------------------------------------------------------- /commit-patch-buffer.nix: -------------------------------------------------------------------------------- 1 | with import {}; 2 | with { commit-patch' = import ./commit-patch.nix; }; 3 | 4 | { commit-patch ? commit-patch' 5 | , emacs ? pkgs.emacs-nox 6 | }: 7 | 8 | let 9 | trivialBuild = pkgs.callPackage "${}/pkgs/build-support/emacs/trivial.nix" { emacs = emacs; }; 10 | in 11 | 12 | trivialBuild { 13 | pname = "commit-patch-buffer"; 14 | inherit (commit-patch) src version; 15 | 16 | propagatedUserEnvPkgs = [ commit-patch patchutils ]; 17 | 18 | meta = { 19 | inherit (commit-patch.meta) homepage license; 20 | description = "Commit diff buffers using commit-patch"; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /commit-patch.nix: -------------------------------------------------------------------------------- 1 | with import {}; 2 | 3 | stdenv.mkDerivation rec { 4 | name = "commit-patch"; 5 | version = "2.6.2"; 6 | src = fetchurl { 7 | url = "https://porkrind.org/commit-patch/commit-patch-${version}.tar.gz"; 8 | sha256 = "0v11vjyisk243zi0ym90bnqb229j7iaqx1lwqdkszxzn1yxwq4ck"; 9 | }; 10 | 11 | buildInputs = [ perl patchutils makeWrapper ]; 12 | 13 | phases = "unpackPhase installPhase postFixup"; 14 | 15 | installFlags = [ "PREFIX=$(out)" ]; 16 | 17 | # If it weren't fatpacked we could do this for IPC::Run: 18 | # postFixup = '' 19 | # wrapProgram $out/bin/commit-patch --set PERL5LIB \ 20 | # ${with perlPackages; makePerlPath ([ 21 | # IPCRun 22 | # ])} \ 23 | # --prefix PATH ":" \ 24 | # "${lib.makeBinPath [ perl patchutils ]}" 25 | # ''; 26 | 27 | postFixup = '' 28 | perl -pi -e 's,#!/usr/bin/perl,#!${perl}/bin/perl,' $out/bin/commit-patch 29 | wrapProgram $out/bin/commit-patch \ 30 | --prefix PATH ":" \ 31 | "${lib.makeBinPath [ perl patchutils ]}" 32 | ''; 33 | 34 | meta = with lib; { 35 | license = licenses.gpl2; 36 | homepage = "https://porkrind.org/commit-patch"; 37 | description = "Commit patches to Darcs, Git, Mercurial, Bazaar, Monotone, Subversion, or CVS"; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | # -*- perl -*- 2 | requires 'IPC::Run'; 3 | requires 'App::FatPacker'; 4 | -------------------------------------------------------------------------------- /test/main.test: -------------------------------------------------------------------------------- 1 | # -*- sh -*- 2 | 3 | echo "initial" > a 4 | 5 | diff -uN --label /dev/null --label ${DIFF_PREFIX}a /dev/null a > initial.patch || true 6 | 7 | $COMMIT_PATCH -m "initial" initial.patch 8 | 9 | echo "1234" >> a 10 | 11 | $VC_DIFF > changed.patch || true 12 | 13 | $COMMIT_PATCH -m "changed" changed.patch 14 | 15 | rm a 16 | 17 | # Some VCSes won't show diffs for removed files unless you tell it they are removed: 18 | $VC_RM a 19 | 20 | $VC_DIFF > removed.patch || true 21 | 22 | $COMMIT_PATCH -m "removed" removed.patch 23 | -------------------------------------------------------------------------------- /test/run-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ "$1" == "-v" -o "$1" == "--verbose" ]; then 6 | shift 7 | set -x 8 | export V=--verbose 9 | export V_SH=-x 10 | fi 11 | 12 | TESTDIR=$(dirname $0) 13 | TESTDIR_ABS=$(cd $TESTDIR && pwd) 14 | export COMMIT_PATCH="$TESTDIR_ABS/../commit-patch $V" 15 | 16 | VCSes=$((cd "$TESTDIR/vcs" && ls -1 *.sh) | sed -e 's/\(.*\)\.sh/\1/') 17 | Tests=$(cd "$TESTDIR" && ls -1 *.test) 18 | 19 | usage () { 20 | set +x 21 | echo "Usage:" 22 | echo " $0 [-v|--verbose] " 23 | echo "" 24 | echo " Where is one of: $(echo $VCSes)" 25 | exit 1; 26 | } 27 | 28 | VCs=${1:-$VCSes} 29 | 30 | [ "$VCs" == "--help" -o "$VCs" == "-h" ] && usage 31 | 32 | for vc in "$TESTDIR/vcs/"*.sh; do 33 | source "$vc" 34 | done 35 | 36 | run_test () { 37 | export VC=$1 38 | TEST=$2 39 | 40 | [ -d "$TESTDIR/tmp" ] || mkdir "$TESTDIR/tmp" 41 | WD=$(mktemp -d "$TESTDIR_ABS/tmp/wd.XXXXXXX") 42 | REPO=$(mktemp -d "$TESTDIR_ABS/tmp/repo.XXXXXXX") 43 | 44 | ${VC}_init "$WD" "$REPO" 45 | 46 | if ! (cd "$WD" && sh $V_SH -e "$TESTDIR_ABS/$TEST"); then 47 | echo "$VC / $TEST: Test failed." 48 | exit 1; 49 | fi 50 | 51 | ${VC}_cleanup "$WD" "$REPO" 52 | rm -rf "$WD" 53 | rm -rf "$REPO" 54 | } 55 | 56 | for vc in $VCs; do 57 | for t in $Tests; do 58 | run_test $vc $t 59 | done 60 | done 61 | 62 | echo "All tests appear to pass." 63 | -------------------------------------------------------------------------------- /test/vcs/bzr.sh: -------------------------------------------------------------------------------- 1 | bzr_init () { 2 | WD=$1 3 | mkdir -p "$WD" 4 | (cd "$WD" && bzr init && bzr whoami --branch "Chester McTester ") 5 | 6 | export VC_DIFF="bzr diff" 7 | export VC_RM="true" 8 | export DIFF_PREFIX="" 9 | } 10 | 11 | bzr_cleanup () { 12 | true 13 | } 14 | -------------------------------------------------------------------------------- /test/vcs/cvs.sh: -------------------------------------------------------------------------------- 1 | cvs_init () { 2 | WD=$1 3 | REPO=$2 4 | 5 | export CVSROOT="$REPO" 6 | cvs init 7 | mkdir "$REPO/test" 8 | cvs checkout -d "$WD" test 9 | 10 | export VC_DIFF="cvs diff -N" 11 | export VC_RM="cvs rm" 12 | export DIFF_PREFIX="" 13 | } 14 | 15 | cvs_cleanup () { 16 | true 17 | } 18 | -------------------------------------------------------------------------------- /test/vcs/darcs.sh: -------------------------------------------------------------------------------- 1 | darcs_init () { 2 | WD=$1 3 | mkdir -p "$WD" 4 | (cd "$WD" && darcs init && echo "Chester McTester" >_darcs/prefs/author) 5 | 6 | export VC_DIFF="darcs diff" 7 | export VC_RM="true" 8 | export DIFF_PREFIX="a/" 9 | } 10 | 11 | darcs_cleanup () { 12 | true 13 | } 14 | -------------------------------------------------------------------------------- /test/vcs/git.sh: -------------------------------------------------------------------------------- 1 | git_init () { 2 | WD=$1 3 | mkdir -p "$WD" 4 | (cd "$WD" && git init) 5 | 6 | export VC_DIFF="git diff" 7 | export VC_RM="true" 8 | export DIFF_PREFIX="b/" 9 | } 10 | 11 | git_cleanup () { 12 | true 13 | } 14 | -------------------------------------------------------------------------------- /test/vcs/hg.sh: -------------------------------------------------------------------------------- 1 | hg_init () { 2 | WD=$1 3 | mkdir -p "$WD" 4 | (cd "$WD" && hg init && (echo "[ui]"; echo "username=Chester McTester") > .hg/hgrc) 5 | 6 | export VC_DIFF="hg diff" 7 | export VC_RM="hg rm" 8 | export DIFF_PREFIX="a/" 9 | } 10 | 11 | hg_cleanup () { 12 | true 13 | } 14 | -------------------------------------------------------------------------------- /test/vcs/mtn.sh: -------------------------------------------------------------------------------- 1 | mtn_init () { 2 | WD=$1 3 | REPO=$2 4 | 5 | export MTN_KEYDIR=$(mktemp -d "$TESTDIR_ABS/tmp/keydir.XXXXXXX") 6 | 7 | # Monotone doesns't like the db dir to exist when you init it. 8 | [ -d "$REPO" ] && rmdir "$REPO" 9 | 10 | mtn --keydir "$MTN_KEYDIR" db init --db="$REPO" 11 | mtn --keydir "$MTN_KEYDIR" --db="$REPO" --branch=test setup "$WD" 12 | (cd "$WD" && mtn automate generate_key commit-patch-automated-tester@example.com '') 13 | 14 | export VC_DIFF="mtn diff --without-header" 15 | export VC_RM="mtn drop" 16 | export DIFF_PREFIX="" 17 | } 18 | 19 | mtn_cleanup () { 20 | rm -rf "$MTN_KEYDIR" 21 | } 22 | -------------------------------------------------------------------------------- /test/vcs/svn.sh: -------------------------------------------------------------------------------- 1 | svn_init () { 2 | WD=$1 3 | REPO=$2 4 | set -e 5 | 6 | (cd "$REPO" && svnadmin create test) 7 | svn import "$WD" file://"$REPO"/test/trunk -m "Setting up trunk" 8 | svn checkout file://"$REPO"/test/trunk "$WD" 9 | 10 | export VC_DIFF="svn diff" 11 | export VC_RM="svn rm" 12 | export DIFF_PREFIX="" 13 | } 14 | 15 | svn_cleanup () { 16 | true 17 | } 18 | --------------------------------------------------------------------------------