├── .gitignore ├── COPYING ├── README.md ├── __init__.py ├── codediff.py ├── coderev.sh ├── coderevrc.sample ├── demo ├── README-.html ├── README.cdiff.html ├── README.fdiff.html ├── README.html ├── README.sdiff.html ├── README.udiff.html ├── codediff.py-.html ├── codediff.py.cdiff.html ├── codediff.py.fdiff.html ├── codediff.py.html ├── codediff.py.sdiff.html ├── codediff.py.udiff.html ├── coderev.sh-.html ├── coderev.sh.cdiff.html ├── coderev.sh.fdiff.html ├── coderev.sh.html ├── coderev.sh.sdiff.html ├── coderev.sh.udiff.html ├── coderevrc.sample.html ├── index.html ├── libcvs.sh.html └── libsvn.sh.html ├── libcvs.sh └── libsvn.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /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 02111-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 Library 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) 19yy 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 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) 19yy name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *Hey, maybe you are just looking for a side-by-side diff viewer? Try [cdiff](https://github.com/ymattw/cdiff) instead.* 2 | 3 | # Coderev 4 | 5 | A toolkit generates side-by-side html pages for code review 6 | 7 | ## About 8 | 9 | Coderev is a toolkit generates static side-by-side html pages for code review. 10 | Typical use case is to generate diff pages for local modification in svn/cvs 11 | workspace and send page link to teammates for code review. 12 | 13 | See [ymattw.github.io/coderev/demo/](http://ymattw.github.io/coderev/demo/index.html) for a demo. 14 | 15 | This toolkit contains two scripts that can be used separately. 16 | 17 | - coderev.sh - generate diff page in svn/cvs workspace 18 | - codediff.py - generate diff page for any two given files/directories 19 | 20 | This project was originally hosted at [google code](http://code.google.com/p/coderev/) 21 | and recent moved to github. 22 | 23 | ## Usage of coderev.sh 24 | 25 | Just type `./coderev.sh -h` to see the usage. 26 | 27 | ``` 28 | Usage: 29 | coderev.sh [-r revision] [-w width] [-o outdir] [-y] [-d name] \ 30 | [-F comment-file | -m 'comment...'] [file...] 31 | 32 | coderev.sh [-r revision] [-w width] [-o outdir] [-y] [-d name] \ 33 | [-F comment-file | -m 'comment...'] [-p num] < patch-file 34 | 35 | All options are optional. 36 | 37 | -r revision - Specify a revision number, or symbol (PREV, BASE, HEAD) 38 | in svn, see svn books for details. Default revision is 39 | revision of your working copy 40 | 41 | -w width - Let code review pages wrap in specific column 42 | 43 | -o outdir - The output dir to save code review pages 44 | 45 | -y - Force overwrite if outdir alredy exists 46 | 47 | -d name - Use this name instead of a dynamically timestamp string 48 | as coderev directory basename 49 | 50 | -F comment-file - A file to read comments from 51 | 52 | -m 'comment...' - To set inline comments, note '-m' precedes '-F', if 53 | neither `-F' nor `-m' is specified, $EDITOR (default 54 | is vi) will be invoked to write comments 55 | 56 | file... - File/dir list you want to diff, default is `.' 57 | 58 | patch-file - A patch file (usually generated by `diff(1)' or `svn 59 | diff') to use to generate coderev 60 | 61 | -p num - When use a patch file, this option is passed to utility 62 | `patch(1)' to strip the smallest prefix containing num 63 | leading slashes from each file name found in the patch 64 | 65 | Example 1: 66 | 67 | You are working on the most up-to-date revision and made some local 68 | modification, now you want to invite others to review, just run 69 | 70 | cd workspace 71 | coderev.sh -w80 72 | 73 | This generates coderev pages (wrap in column 80) in a temp directory. Then 74 | copy the coderev dir to somewhere on a web host and send out the link for 75 | review. Read coderevrc.sample for how to make this automated. 76 | 77 | Example 2: 78 | 79 | You are making local modification when someone else committed some changes 80 | on foo.c and bar directory, you want to see what's different between your 81 | copy and the up-to-date revision in repository, just run 82 | 83 | cd workspace 84 | coderev.sh -r HEAD -o ~/public_html/coderev foo.c bar/ 85 | 86 | This generate coderev based on diffs between HEAD revision (up-to-date 87 | version in repository) and locally modified revision, this will retrieve 88 | diffs from SVN server, output pages saved under your web home, i.e., if 89 | you correctly configured a web server on your work station you can visit 90 | http://server/~you/coderev to see the coderev. (Replace HEAD with a 91 | revision number this example also works for CVS). 92 | 93 | Example 3: 94 | 95 | Someone invite you to review his code change, unfortunately he sent you raw 96 | diff generated by `cvs diff' named `foo.patch', you can run 97 | 98 | cd workspace 99 | cvs up 100 | coderev.sh -m 'applying patch foo' -o ~/public_html/foo < foo.patch 101 | 102 | Again, you can visit http://server/~you/foo to see his change. Note you 103 | may need to use option `-p num' depends on how he generated the patch. 104 | 105 | Example 4: 106 | 107 | You want to see what's different between previous revision and your 108 | current working copy (modified or not) for foo.c and dir bar/, just run 109 | 110 | cd workspace 111 | svn diff -r PREV foo.c bar/ | coderev.sh -w80 -F comments 112 | 113 | This read comments from file `comments' and generate coderev in a temp 114 | directory. (Replace PREV with a revision number this example also works 115 | for CVS). 116 | ``` 117 | 118 | ## Usage of codediff.py 119 | 120 | Just type `./codediff.py -h` to see the usage. 121 | 122 | ``` 123 | Usage: 124 | codediff.py [options] OLD NEW 125 | codediff.py OLD NEW [options] 126 | 127 | Diff two files/directories and produce HTML pages. 128 | 129 | Options: 130 | -h, --help show this help message and exit 131 | -c, --context generate context diff (default is full diff), only 132 | take effect when diffing two files 133 | -F FILE, --commentfile=FILE 134 | specify a file to read comments 135 | -f FILE, --filelist=FILE 136 | specify a file list to read from, filelist can be 137 | generated by find -type f, specify - to read from 138 | stdin 139 | -m COMMENTS, --comments=COMMENTS 140 | specify inline comments (precedes -F) 141 | -n NUM, --lines=NUM specify context line count when generating context 142 | diff or unified diff, default is 3 143 | -o OUTPUT, --output=OUTPUT 144 | specify output file or directory name 145 | -p NUM, --striplevel=NUM 146 | for all pathnames in the filelist, delete NUM path 147 | name components from the beginning of each path name, 148 | it is similar to patch(1) -p 149 | -P NUM, --pager=NUM specify maximum number of files listed in index 150 | page. If there's more, additionnal indexnn.html will be 151 | generated. 152 | -t TITLE, --title=TITLE 153 | specify title of output index page 154 | -w WIDTH, --wrap=WIDTH 155 | specify column number where lines are broken and 156 | wrapped for sdiff, default is no line wrapping 157 | -y, --yes do not prompt for overwriting 158 | ``` 159 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymattw/coderev/ef61f9470d16df19aa1fbd96938d9519ef9f5e15/__init__.py -------------------------------------------------------------------------------- /coderev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Homepage: http://code.google.com/p/coderev 4 | # License: GPLv2, see "COPYING" 5 | # 6 | # Generate code review page of vs @HEAD, by using 7 | # `codediff.py' - a standalone diff tool 8 | # 9 | # $Id: coderev.sh 40 2011-05-04 21:24:23Z mattwyl $ 10 | 11 | if [[ -L $0 ]]; then 12 | # Note readlink is not compatible 13 | BINDIR=$(dirname $(/bin/ls -l $0 | awk -F ' -> ' '{print $2}')) 14 | else 15 | BINDIR=$(cd $(dirname $0) && pwd -P) || exit 1 16 | fi 17 | 18 | PROG_NAME=$(basename $0) 19 | CODEDIFF=$BINDIR/codediff.py 20 | 21 | function help 22 | { 23 | cat << EOF 24 | 25 | Usage: 26 | $PROG_NAME [-r revision] [-w width] [-o outdir] [-y] [-d name] \\ 27 | [-F comment-file | -m 'comment...'] [file...] 28 | 29 | $PROG_NAME [-r revision] [-w width] [-o outdir] [-y] [-d name] \\ 30 | [-F comment-file | -m 'comment...'] [-p num] < patch-file 31 | 32 | All options are optional. 33 | 34 | -r revision - Specify a revision number, or symbol (PREV, BASE, HEAD) 35 | in svn, see svn books for details. Default revision is 36 | revision of your working copy 37 | 38 | -w width - Let code review pages wrap in specific column 39 | 40 | -o outdir - The output dir to save code review pages 41 | 42 | -y - Force overwrite if outdir alredy exists 43 | 44 | -d name - Use this name instead of a dynamically timestamp string 45 | as coderev directory basename 46 | 47 | -F comment-file - A file to read comments from 48 | 49 | -m 'comment...' - To set inline comments, note '-m' precedes '-F', if 50 | neither \`-F' nor \`-m' is specified, \$EDITOR (default 51 | is vi) will be invoked to write comments 52 | 53 | file... - File/dir list you want to diff, default is \`.' 54 | 55 | patch-file - A patch file (usually generated by \`diff(1)' or \`svn 56 | diff') to use to generate coderev 57 | 58 | -p num - When use a patch file, this option is passed to utility 59 | \`patch(1)' to strip the smallest prefix containing num 60 | leading slashes from each file name found in the patch 61 | 62 | Example 1: 63 | 64 | You are working on the most up-to-date revision and made some local 65 | modification, now you want to invite others to review, just run 66 | 67 | cd workspace 68 | $PROG_NAME -w80 69 | 70 | This generates coderev pages (wrap in column 80) in a temp directory. Then 71 | copy the coderev dir to somewhere on a web host and send out the link for 72 | review. Read coderevrc.sample for how to make this automated. 73 | 74 | Example 2: 75 | 76 | You are making local modification when someone else committed some changes 77 | on foo.c and bar directory, you want to see what's different between your 78 | copy and the up-to-date revision in repository, just run 79 | 80 | cd workspace 81 | $PROG_NAME -r HEAD -o ~/public_html/coderev foo.c bar/ 82 | 83 | This generate coderev based on diffs between HEAD revision (up-to-date 84 | version in repository) and locally modified revision, this will retrieve 85 | diffs from SVN server, output pages saved under your web home, i.e., if 86 | you correctly configured a web server on your work station you can visit 87 | http://server/~you/coderev to see the coderev. (Replace HEAD with a 88 | revision number this example also works for CVS). 89 | 90 | Example 3: 91 | 92 | Someone invite you to review his code change, unfortunately he sent you raw 93 | diff generated by \`cvs diff' named \`foo.patch', you can run 94 | 95 | cd workspace 96 | cvs up 97 | $PROG_NAME -m 'applying patch foo' -o ~/public_html/foo < foo.patch 98 | 99 | Again, you can visit http://server/~you/foo to see his change. Note you 100 | may need to use option \`-p num' depends on how he generated the patch. 101 | 102 | Example 4: 103 | 104 | You want to see what's different between previous revision and your 105 | current working copy (modified or not) for foo.c and dir bar/, just run 106 | 107 | cd workspace 108 | svn diff -r PREV foo.c bar/ | $PROG_NAME -w80 -F comments 109 | 110 | This read comments from file \`comments' and generate coderev in a temp 111 | directory. (Replace PREV with a revision number this example also works 112 | for CVS). 113 | 114 | EOF 115 | 116 | return 0 117 | } 118 | 119 | function filter_svn_diff 120 | { 121 | sed -n -e ' 122 | #no-auto-print 123 | # 124 | # Keep the current set of differences in the hold buffer. 125 | # When the "Index:" line is first seen it is changed to "ndex:" 126 | # and then saved into the hold buffer. 127 | # This will be changed back to "Index:" if some 128 | # differences are found. Differences begin with "@@". 129 | # 130 | # All lines beginning with "Property changes" 131 | # up to the next "Index:" line will be filtered out. 132 | # 133 | /^Property changes on: /,/^Index:/ { 134 | # If on last line, check the hold buffer for diffs and print them. 135 | $ {g; /^Index:/p; q} 136 | # Skip lines that do not begin with "Index:". 137 | /^Index:/!b 138 | # Else, if the line begins with "Index:" fall through. 139 | } 140 | # For "Index:" lines, change to "ndex:" and swap with hold buffer. 141 | # If differences are present in the hold buffer, print them. 142 | /^Index:/ {s/^I//; x; /^Index:/p; b} 143 | # If an actual change is found, change the first line in the hold 144 | # buffer from "ndex:" to "Index:". 145 | /^@@ / {x; s/^n/In/; x} 146 | # If on last line, append this line to the hold buffer, 147 | # then check the hold buffer for diffs and print them. 148 | $ {H; g; /^Index:/p; q} 149 | # Otherwise, append the input line to the hold buffer. 150 | H 151 | ' $* 152 | } 153 | 154 | function get_list_from_patch 155 | { 156 | local patch=${1?"patch file required"} 157 | local patch_lvl=${2?"patch level required"} 158 | 159 | # The trick is use regex "^([^/]+/){,n}" to match the prefixed subdirs 160 | # 161 | if [[ $(head -1 $patch) =~ ^Index:\ .+ ]]; then 162 | grep '^Index: ' $patch | sed 's/.* //' \ 163 | | sed "s|^\([^/]\+/\)\{0,$patch_lvl\}||" 164 | elif [[ $(head -1 $patch) =~ ^diff\ .+\ .+ ]]; then 165 | grep '^diff .+ .+' $patch | sed 's/.* //' \ 166 | | sed "s|^\([^/]\+/\)\{0,$patch_lvl\}||" 167 | else 168 | echo "Unknown patch format." >&2 169 | return 1 170 | fi 171 | 172 | return 0 173 | } 174 | 175 | 176 | #################### VCS Operations Begin #################### 177 | 178 | # Return string: "cvs" for CVS, "svn" for SVN, "unknown" otherwise 179 | # 180 | function detect_vcs 181 | { 182 | local ident="" 183 | 184 | if [[ -f .svn/entries ]]; then 185 | ident="svn" 186 | elif [[ -f CVS/Entries ]]; then 187 | ident="cvs" 188 | else 189 | ident="unknown" 190 | fi 191 | echo "$ident" 192 | } 193 | 194 | function set_vcs_ops 195 | { 196 | local ident=${1?} 197 | 198 | eval vcs_get_banner=${ident}_get_banner 199 | eval vcs_get_repository=${ident}_get_repository 200 | eval vcs_get_project_path=${ident}_get_project_path 201 | eval vcs_get_working_revision=${ident}_get_working_revision 202 | eval vcs_get_active_list=${ident}_get_active_list 203 | eval vcs_get_diff=${ident}_get_diff 204 | eval vcs_get_diff_opt=${ident}_get_diff_opt 205 | } 206 | 207 | # VCS Operations: 208 | # get_banner - print banner, return 1 if not supported 209 | # get_repository - print repository 210 | # get_project_path - print project path without repository 211 | # get_working_revision pathname ... - print working revision 212 | # get_active_list pathname ... - print active file list 213 | # get_diff [diff_opt] pathname ... - get diffs for active files 214 | # get_diff_opt - print diff option and args 215 | 216 | function unknown_get_banner 217 | { 218 | echo "unknown" 219 | return 1 220 | } 221 | 222 | . $BINDIR/libsvn.sh || exit 1 223 | . $BINDIR/libcvs.sh || exit 1 224 | 225 | # Detect VCS (Version Control System) and set handler 226 | # 227 | set_vcs_ops $(detect_vcs) 228 | 229 | #################### VCS Operations End #################### 230 | 231 | # Main Proc 232 | # 233 | CODEREV_NAME= 234 | COMMENT_FILE= 235 | COMMENTS= 236 | OUTPUT_DIR= 237 | PATCH_LVL=0 238 | REV_ARG= 239 | REVERSE_PATCH=false 240 | WRAP_NUM= 241 | OVERWRITE=false 242 | 243 | while getopts "d:F:hm:o:p:r:w:y" op; do 244 | case $op in 245 | d) CODEREV_NAME="$OPTARG" ;; 246 | F) COMMENT_FILE="$OPTARG" ;; 247 | h) help; exit 0 ;; 248 | m) COMMENTS="$OPTARG" ;; 249 | o) OUTPUT_DIR="$OPTARG" ;; 250 | p) PATCH_LVL="$OPTARG" ;; 251 | r) REV_ARG="$OPTARG" ;; 252 | w) WRAP_NUM="$OPTARG" ;; 253 | y) OVERWRITE=true ;; 254 | ?) help; exit 1 ;; 255 | esac 256 | done 257 | shift $((OPTIND - 1)) 258 | PATHNAME="${@:-.}" 259 | 260 | # If terminal opened fd 0 then we aren't receiving patch from stdin 261 | [[ -t 0 ]] && RECV_STDIN=false || RECV_STDIN=true 262 | 263 | BANNER=$($vcs_get_banner) || { 264 | echo "Unsupported version control system ($BANNER)." >&2 265 | exit 1 266 | } 267 | echo -e "\nVersion control system \"$BANNER\" detected." 268 | 269 | # Retrieve information 270 | # 271 | echo -e "\nRetrieving information..." 272 | PROJ_PATH=$($vcs_get_project_path) 273 | WS_NAME=$(basename $PROJ_PATH) 274 | if [[ $PATHNAME == "-" ]]; then 275 | WS_REV=$($vcs_get_working_revision .) 276 | else 277 | WS_REV=$($vcs_get_working_revision $PATHNAME) 278 | fi 279 | echo " * Repository : $($vcs_get_repository)" 280 | echo " * Project path : $PROJ_PATH" 281 | echo " * Working revision : $WS_REV" 282 | 283 | # Prepare file list and base source 284 | # 285 | TMPDIR=$(mktemp -d /tmp/coderev.XXXXXX) || exit 1 286 | ACTIVE_LIST="$TMPDIR/activelist" 287 | DIFF="$TMPDIR/diffs" 288 | BASE_SRC="$TMPDIR/$WS_NAME-base" 289 | PATCH_DRY_RUN_LOG="$TMPDIR/patch-dry-run.log" 290 | PATCH_LOG="$TMPDIR/patch.log" 291 | 292 | if $RECV_STDIN; then 293 | echo -e "\nReceiving diffs..." 294 | # TODO: consider format other than svn diff 295 | filter_svn_diff > $DIFF || exit 1 296 | 297 | # Redirect stdin to tty for comment input and output dir overwritting 298 | # confirmation, otherwise vim will complain and corrupt term after quit 299 | # 300 | if [[ -z $COMMENTS ]] && [[ -z $COMMENT_FILE ]] || ! $OVERWRITE; then 301 | exec < /dev/tty 302 | fi 303 | 304 | get_list_from_patch $DIFF $PATCH_LVL | sort -u > $ACTIVE_LIST || exit 1 305 | else 306 | $vcs_get_active_list $PATHNAME | sort -u > $ACTIVE_LIST || exit 1 307 | fi 308 | 309 | [[ -s "$ACTIVE_LIST" ]] || { 310 | echo "No active file found." 311 | exit 0 312 | } 313 | echo -e "\nActive file list:" 314 | sed 's/^/ * /' $ACTIVE_LIST 315 | 316 | # Generate $BASE_SRC 317 | # 318 | mkdir -p $BASE_SRC || exit 1 319 | 320 | SRC_LIST="" 321 | for f in $(cat $ACTIVE_LIST); do 322 | [[ -f $f ]] && SRC_LIST+=" $f" 323 | done 324 | 325 | if [[ -n $SRC_LIST ]]; then 326 | tar -cf - $SRC_LIST | tar -C $BASE_SRC -xf - || exit 1 327 | fi 328 | 329 | if $RECV_STDIN; then 330 | PATCH_LVL=${PATCH_LVL:-0} 331 | else 332 | echo -e "\nRetrieving diffs..." 333 | VCS_REV_OPT="" 334 | [[ -n $REV_ARG ]] && VCS_REV_OPT="$($vcs_get_diff_opt $REV_ARG)" 335 | $vcs_get_diff $VCS_REV_OPT $(cat $ACTIVE_LIST) > $DIFF || exit 1 336 | # PATCH_LVL default to 0 337 | fi 338 | 339 | # If we are not receiving patch set from stdin and not using "-r" option, it 340 | # then means we are working on patch set from default " diff", in this 341 | # case "patch -R" is required, otherwise we try patch (dry-run) to detect 342 | # errors and reverse patch in advance, then do real patch 343 | # 344 | PATCH_OPT="-E -t -p $PATCH_LVL -d $BASE_SRC" 345 | if ! $RECV_STDIN && [[ -z $REV_ARG ]]; then 346 | REVERSE_PATCH=true 347 | PATCH_OPT+=" -R" 348 | else 349 | patch $PATCH_OPT --dry-run < $DIFF > $PATCH_DRY_RUN_LOG 2>&1 350 | if grep -Eq 'Reversed .* detected.*Assum.* -R|No file to patch' \ 351 | $PATCH_DRY_RUN_LOG 2>&1; then 352 | REVERSE_PATCH=true 353 | PATCH_OPT+=" -R" 354 | fi 355 | fi 356 | 357 | # Do real patch with option "-f" (force) 358 | patch $PATCH_OPT -f < $DIFF > $PATCH_LOG 2>&1 359 | if grep -q 'FAILED -- .* reject' $PATCH_LOG; then 360 | cat $PATCH_LOG 361 | echo "Failed to apply patch, aborting..." >&2 362 | exit 1 363 | fi 364 | 365 | # Form codediff options 366 | # 367 | CODEDIFF_OPT="-f $ACTIVE_LIST" 368 | 369 | if [[ -n $CODEREV_NAME ]]; then 370 | CODEREV=$TMPDIR/$CODEREV_NAME 371 | else 372 | CODEREV=$TMPDIR/${WS_NAME}-r${WS_REV}-$(date '+%F.%H.%M.%S') 373 | fi 374 | [[ -n "$OUTPUT_DIR" ]] && CODEREV=$OUTPUT_DIR 375 | CODEDIFF_OPT+=" -o $CODEREV" 376 | 377 | TITLE="Coderev for $(basename $(pwd)) r$WS_REV" 378 | CODEDIFF_OPT+=" -t '$TITLE'" 379 | 380 | if [[ -z "$COMMENTS" ]]; then 381 | [[ -n "$COMMENT_FILE" ]] || { 382 | COMMENT_FILE="$TMPDIR/comments-$$" 383 | COMMENT_TAG="--Enter comments above. \ 384 | This line and those below will be ignored--" 385 | echo -e "\n$COMMENT_TAG" >> $COMMENT_FILE 386 | echo -e "\n(hint: use '-F' option for comment file)" >> $COMMENT_FILE 387 | echo -e "\nActive file list:" >> $COMMENT_FILE 388 | cat $ACTIVE_LIST | sed 's/^/ /' >> $COMMENT_FILE 389 | echo -e "\n# vim:set ft=svn:" >> $COMMENT_FILE 390 | 391 | [[ -n "$EDITOR" ]] || { 392 | if which vim >/dev/null 2>&1; then 393 | EDITOR=vim 394 | else 395 | EDITOR=vi 396 | fi 397 | } 398 | ${EDITOR} $COMMENT_FILE 399 | sed -i -e '/^--.*--$/, $ d' $COMMENT_FILE 400 | } 401 | CODEDIFF_OPT+=" -F $COMMENT_FILE" 402 | else 403 | CODEDIFF_OPT+=" -m '$COMMENTS'" 404 | fi 405 | 406 | [[ -n "$WRAP_NUM" ]] && CODEDIFF_OPT+=" -w $WRAP_NUM" 407 | $OVERWRITE && CODEDIFF_OPT+=" -y" 408 | 409 | # Generate coderev 410 | # 411 | echo -e "\nGenerating code review..." 412 | if $REVERSE_PATCH ; then 413 | eval $CODEDIFF $CODEDIFF_OPT $BASE_SRC . || exit 1 414 | else 415 | eval $CODEDIFF $CODEDIFF_OPT . $BASE_SRC || exit 1 416 | fi 417 | echo -e "\nCoderev pages generated in $CODEREV" 418 | 419 | # Cleanup 420 | # 421 | rm -rf $ACTIVE_LIST $DIFF $BASE_SRC 422 | 423 | # Copy to web host if output dir is generated automatically 424 | # 425 | if [[ -z "$OUTPUT_DIR" ]]; then 426 | [[ -r /etc/coderevrc ]] || [[ -r ~/.coderevrc ]] || { 427 | echo 428 | echo "[*] Hint: if you want to copy coderev pages to a remote host" 429 | echo " automatically, see coderevrc.sample" 430 | echo 431 | exit 0 432 | } 433 | 434 | [[ -r /etc/coderevrc ]] && { 435 | . /etc/coderevrc || { 436 | echo "Reading /etc/coderevrc failed." >&2 437 | exit 1 438 | } 439 | } 440 | 441 | [[ -r ~/.coderevrc ]] && { 442 | . ~/.coderevrc || { 443 | echo "Reading ~/.coderevrc failed." >&2 444 | exit 1 445 | } 446 | } 447 | 448 | : ${HOST_DIR?"HOST_DIR not defined."} 449 | : ${WEB_URL?"WEB_URL not defined."} 450 | [[ -n $SSH_USER ]] || SSH_USER=$(whoami) 451 | 452 | LOC_PREFIX="" 453 | [[ -n $WEB_HOST ]] && LOC_PREFIX="${SSH_USER}@${WEB_HOST}:" 454 | 455 | echo -e "\nCopying to ${LOC_PREFIX}$HOST_DIR/..." 456 | eval scp -rpq $CODEREV ${LOC_PREFIX}$HOST_DIR/ || exit 1 457 | 458 | echo -e "\nCoderev link:" 459 | echo "$WEB_URL/$(basename $CODEREV)" 460 | echo 461 | rm -rf $TMPDIR 462 | else 463 | rm -rf $TMPDIR 464 | fi 465 | 466 | exit 0 467 | -------------------------------------------------------------------------------- /coderevrc.sample: -------------------------------------------------------------------------------- 1 | # Copy to /etc/coderev or ~/.coderevrc, then customize with your own choices 2 | # 3 | 4 | WEB_HOST="example.org" 5 | SSH_USER="me" 6 | HOST_DIR="~/public_html/coderev" 7 | WEB_URL="http://$WEB_HOST/~$SSH_USER/coderev" 8 | 9 | # Example config to copy to localhost (leave WEB_HOST empty): 10 | # 11 | # WEB_HOST= 12 | # SSH_USER=$(whoami) 13 | # HOST_DIR="~/public_html/coderev" 14 | # WEB_URL="http://example.org/~$SSH_USER/coderev" 15 | -------------------------------------------------------------------------------- /demo/README-.html: -------------------------------------------------------------------------------- 1 | coderev-0.1/README
= Coderev - simple code review tool =
 2 | 
 3 | == Overview ==
 4 | 
 5 | Code review before check in code changes is a must in most projects, but what
 6 | will you do if you want other developers to review your changes?  Send them
 7 | the diff generated by "svn diff" or "cvs diff" or something else?  I beleive
 8 | that would bring a lot of pain to the reviewers - raw diffs are difficult to
 9 | read when you changed many lines.
10 | 
11 | Coderev is just the thing you need for effective code review.  Coderev is a
12 | quite simple and small toolkit that can
13 | 
14 |   * Compares files/dirs and generates side-by-side html pages
15 |   * Generates side-by-side html pages on your SVN working copy
16 |   * More (TODO)
17 | 
18 | == Status ==
19 | 
20 | Basic function is alreay implemented on top of python difflib.
21 | 
22 | == Usage ==
23 | 
24 | Download from http://code.google.com/p/coderev/downloads/list and extract the
25 | tarball to your disk, there are two tools present for you - codediff.py (to
26 | compare files/dirs) and coderev.sh (to generate code review page on your svn
27 | work space).
28 | 
29 | * codediff.py usage
30 | 
31 |     codediff.py [options] OLD NEW
32 |     codediff.py OLD NEW [options]
33 | 
34 |     Diff two files/directories and produce HTML pages.
35 | 
36 |     Options:
37 |     -h, --help            show this help message and exit
38 |     -o OUTPUT, --outupt=OUTPUT
39 |                           specify output file or directory name
40 |     -c, --context         generate context diff (default is full diff), only
41 |                           take effect when diffing two files
42 |     -f FILE, --filelist=FILE
43 |                           specify a file list to read from, filelist can be
44 |                           generated by find -type f, specify - to read from
45 |                           stdin
46 |     -p NUM, --striplevel=NUM
47 |                           for all pathnames in the filelist, delete NUM path
48 |                           name components from the beginning of each path name,
49 |                           it is similar to patch(1) -p
50 |     -n NUM, --lines=NUM   specify context line count when generating context
51 |                           diffs or unified diffs, default is 3
52 |     -w WIDTH, --wrap=WIDTH
53 |                           specify column number where lines are broken and
54 |                           wrapped for sdiff, default is no line wrapping
55 |     -y, --yes             do not prompt for overwriting
56 | 
57 | 
58 | * coderev.sh usage
59 | 
60 |     coderev.sh [-r revsion] [file|subdir ...]
61 | 
62 |     `revision' is a revision number, or symbol (PREV, BASE, HEAD), see svn
63 |     books for details.  Default revision is revision of your working copy
64 |     (aka. BASE)
65 | 
66 |     Default `subdir' is working dir.
67 | 
68 |     Example 1:
69 |     coderev.sh bin lib
70 | 
71 |     Generate coderev based on your working copy.  If you are working on the
72 |     most up-to-date version, this is suggested way (faster).
73 | 
74 |     Example 2:
75 |     coderev.sh -r HEAD bin lib
76 | 
77 |     Generate coderev based on HEAD revision (up-to-date version in repository),
78 |     this will retrive diffs from SVN server so it's slower, but most safely.
79 | 
80 | == Contact ==
81 | 
82 | Comments appreciated, please send to <project owner> at gmail.com.
83 | 
-------------------------------------------------------------------------------- /demo/README.cdiff.html: -------------------------------------------------------------------------------- 1 | 2 | Cdiff of coderev-0.1/README and coderev-0.2/README 3 | 11 | 12 |
*** coderev-0.1/README Sat Aug 23 17:05:39 2008
13 | --- coderev-0.2/README Sat Aug 23 17:07:26 2008
14 | 
*** 17,27 **** 15 | 16 | == Status == 17 | 18 | ! Basic function is alreay implemented on top of python difflib. 19 | 20 | == Usage == 21 | 22 | ! Download from http://code.google.com/p/coderev/downloads/list and extract the 23 | tarball to your disk, there are two tools present for you - codediff.py (to 24 | compare files/dirs) and coderev.sh (to generate code review page on your svn 25 | work space). 26 | --- 17,28 ---- 27 | 28 | == Status == 29 | 30 | ! Basic function is already implemented on top of python difflib. 31 | 32 | == Usage == 33 | 34 | ! Checkout trunk revision http://code.google.com/p/coderev/source/checkout or 35 | ! download from http://code.google.com/p/coderev/downloads/list and extract the 36 | tarball to your disk, there are two tools present for you - codediff.py (to 37 | compare files/dirs) and coderev.sh (to generate code review page on your svn 38 | work space). 39 |
*** 54,77 **** 40 | wrapped for sdiff, default is no line wrapping 41 | -y, --yes do not prompt for overwriting 42 | 43 | - 44 | * coderev.sh usage 45 | 46 | ! coderev.sh [-r revsion] [file|subdir ...] 47 | 48 | ! `revision' is a revision number, or symbol (PREV, BASE, HEAD), see svn 49 | ! books for details. Default revision is revision of your working copy 50 | ! (aka. BASE) 51 | 52 | Default `subdir' is working dir. 53 | 54 | Example 1: 55 | ! coderev.sh bin lib 56 | 57 | ! Generate coderev based on your working copy. If you are working on the 58 | ! most up-to-date version, this is suggested way (faster). 59 | 60 | ! Example 2: 61 | coderev.sh -r HEAD bin lib 62 | 63 | Generate coderev based on HEAD revision (up-to-date version in repository), 64 | --- 55,82 ---- 65 | wrapped for sdiff, default is no line wrapping 66 | -y, --yes do not prompt for overwriting 67 | 68 | * coderev.sh usage 69 | 70 | ! (optional) cp coderevrc.sample ~/.coderevrc, customize your own 71 | ! cd your-workspace 72 | ! coderev.sh [-r revsion] [-w width] [file|subdir ...] 73 | 74 | ! `revision' is a revision number, or symbol (PREV, BASE, HEAD) in svn, 75 | ! see svn books for details. Default revision is revision of your working 76 | ! copy 77 | ! 78 | ! `width' is a number to make code review pages wrap in specific column 79 | 80 | Default `subdir' is working dir. 81 | 82 | Example 1: 83 | ! coderev.sh -w80 bin lib 84 | 85 | ! Generate coderev based on your working copy, web pages wrap in column 80. 86 | ! If you are working on the most up-to-date version, this is suggested way 87 | ! (faster). 88 | 89 | ! Example 2 (for SVN): 90 | coderev.sh -r HEAD bin lib 91 | 92 | Generate coderev based on HEAD revision (up-to-date version in repository), 93 |
94 | 95 | -------------------------------------------------------------------------------- /demo/README.html: -------------------------------------------------------------------------------- 1 | coderev-0.2/README
= Coderev - simple code review tool =
 2 | 
 3 | == Overview ==
 4 | 
 5 | Code review before check in code changes is a must in most projects, but what
 6 | will you do if you want other developers to review your changes?  Send them
 7 | the diff generated by "svn diff" or "cvs diff" or something else?  I beleive
 8 | that would bring a lot of pain to the reviewers - raw diffs are difficult to
 9 | read when you changed many lines.
10 | 
11 | Coderev is just the thing you need for effective code review.  Coderev is a
12 | quite simple and small toolkit that can
13 | 
14 |   * Compares files/dirs and generates side-by-side html pages
15 |   * Generates side-by-side html pages on your SVN working copy
16 |   * More (TODO)
17 | 
18 | == Status ==
19 | 
20 | Basic function is already implemented on top of python difflib.
21 | 
22 | == Usage ==
23 | 
24 | Checkout trunk revision http://code.google.com/p/coderev/source/checkout or
25 | download from http://code.google.com/p/coderev/downloads/list and extract the
26 | tarball to your disk, there are two tools present for you - codediff.py (to
27 | compare files/dirs) and coderev.sh (to generate code review page on your svn
28 | work space).
29 | 
30 | * codediff.py usage
31 | 
32 |     codediff.py [options] OLD NEW
33 |     codediff.py OLD NEW [options]
34 | 
35 |     Diff two files/directories and produce HTML pages.
36 | 
37 |     Options:
38 |     -h, --help            show this help message and exit
39 |     -o OUTPUT, --outupt=OUTPUT
40 |                           specify output file or directory name
41 |     -c, --context         generate context diff (default is full diff), only
42 |                           take effect when diffing two files
43 |     -f FILE, --filelist=FILE
44 |                           specify a file list to read from, filelist can be
45 |                           generated by find -type f, specify - to read from
46 |                           stdin
47 |     -p NUM, --striplevel=NUM
48 |                           for all pathnames in the filelist, delete NUM path
49 |                           name components from the beginning of each path name,
50 |                           it is similar to patch(1) -p
51 |     -n NUM, --lines=NUM   specify context line count when generating context
52 |                           diffs or unified diffs, default is 3
53 |     -w WIDTH, --wrap=WIDTH
54 |                           specify column number where lines are broken and
55 |                           wrapped for sdiff, default is no line wrapping
56 |     -y, --yes             do not prompt for overwriting
57 | 
58 | * coderev.sh usage
59 | 
60 |     (optional) cp coderevrc.sample ~/.coderevrc, customize your own
61 |     cd your-workspace
62 |     coderev.sh [-r revsion] [-w width] [file|subdir ...]
63 | 
64 |     `revision' is a revision number, or symbol (PREV, BASE, HEAD) in svn,
65 |     see svn books for details.  Default revision is revision of your working
66 |     copy
67 | 
68 |     `width' is a number to make code review pages wrap in specific column
69 | 
70 |     Default `subdir' is working dir.
71 | 
72 |     Example 1:
73 |     coderev.sh -w80 bin lib
74 | 
75 |     Generate coderev based on your working copy, web pages wrap in column 80.
76 |     If you are working on the most up-to-date version, this is suggested way
77 |     (faster).
78 | 
79 |     Example 2 (for SVN):
80 |     coderev.sh -r HEAD bin lib
81 | 
82 |     Generate coderev based on HEAD revision (up-to-date version in repository),
83 |     this will retrive diffs from SVN server so it's slower, but most safely.
84 | 
85 | == Contact ==
86 | 
87 | Comments appreciated, please send to <project owner> at gmail.com.
88 | 
-------------------------------------------------------------------------------- /demo/README.sdiff.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |

coderev-0.1/README
coderev-0.2/README
1717
18== Status ==18== Status ==
1919
n20Basic function is alreay implemented on top of python difflib.n20Basic function is already implemented on top of python difflib.
2121
22== Usage ==22== Usage ==
2323
nn24Checkout trunk revision http://code.google.com/p/coderev/source/checkout or
24Download from http://code.google.com/p/coderev/downloads/list and extract the25download from http://code.google.com/p/coderev/downloads/list and extract the
25tarball to your disk, there are two tools present for you - codediff.py (to26tarball to your disk, there are two tools present for you - codediff.py (to
26compare files/dirs) and coderev.sh (to generate code review page on your svn27compare files/dirs) and coderev.sh (to generate code review page on your svn
27work space).28work space).
54                          wrapped for sdiff, default is no line wrapping55                          wrapped for sdiff, default is no line wrapping
55    -y, --yes             do not prompt for overwriting56    -y, --yes             do not prompt for overwriting
5657
n57 n
58* coderev.sh usage58* coderev.sh usage
5959
nn60    (optional) cp coderevrc.sample ~/.coderevrc, customize your own
61    cd your-workspace
60    coderev.sh [-r revsion] [file|subdir ...]62    coderev.sh [-r revsion] [-w width] [file|subdir ...]
6163
n62    `revision' is a revision number, or symbol (PREV, BASE, HEAD), see svnn64    `revision' is a revision number, or symbol (PREV, BASE, HEAD) in svn,
63    books for details.  Default revision is revision of your working copy65    see svn books for details.  Default revision is revision of your working
64    (aka. BASE)66    copy
67 
68    `width' is a number to make code review pages wrap in specific column
6569
66    Default `subdir' is working dir.70    Default `subdir' is working dir.
6771
68    Example 1:72    Example 1:
n69    coderev.sh bin libn73    coderev.sh -w80 bin lib
7074
n71    Generate coderev based on your working copy.  If you are working on then75    Generate coderev based on your working copy, web pages wrap in column 80.
72    most up-to-date version, this is suggested way (faster).76    If you are working on the most up-to-date version, this is suggested way
77    (faster).
7378
t74    Example 2:t79    Example 2 (for SVN):
75    coderev.sh -r HEAD bin lib80    coderev.sh -r HEAD bin lib
7681
77    Generate coderev based on HEAD revision (up-to-date version in repository),82    Generate coderev based on HEAD revision (up-to-date version in repository),
78 | 79 | 80 | 86 | 92 |
Legends
81 | 82 | 83 | 84 | 85 |
Colors
 Added 
Changed
Deleted
87 | 88 | 89 | 90 | 91 |
Links
(f)irst change
(n)ext change
(t)op
93 | 94 | 95 | -------------------------------------------------------------------------------- /demo/README.udiff.html: -------------------------------------------------------------------------------- 1 | 2 | Udiff of coderev-0.1/README and coderev-0.2/README 3 | 11 | 12 |
--- coderev-0.1/README Sat Aug 23 17:05:39 2008
13 | +++ coderev-0.2/README Sat Aug 23 17:07:26 2008
14 | 
@@ -17,11 +17,12 @@ 15 | 16 | == Status == 17 | 18 | -Basic function is alreay implemented on top of python difflib. 19 | +Basic function is already implemented on top of python difflib. 20 | 21 | == Usage == 22 | 23 | -Download from http://code.google.com/p/coderev/downloads/list and extract the 24 | +Checkout trunk revision http://code.google.com/p/coderev/source/checkout or 25 | +download from http://code.google.com/p/coderev/downloads/list and extract the 26 | tarball to your disk, there are two tools present for you - codediff.py (to 27 | compare files/dirs) and coderev.sh (to generate code review page on your svn 28 | work space). 29 |
@@ -54,24 +55,28 @@ 30 | wrapped for sdiff, default is no line wrapping 31 | -y, --yes do not prompt for overwriting 32 | 33 | - 34 | * coderev.sh usage 35 | 36 | - coderev.sh [-r revsion] [file|subdir ...] 37 | + (optional) cp coderevrc.sample ~/.coderevrc, customize your own 38 | + cd your-workspace 39 | + coderev.sh [-r revsion] [-w width] [file|subdir ...] 40 | 41 | - `revision' is a revision number, or symbol (PREV, BASE, HEAD), see svn 42 | - books for details. Default revision is revision of your working copy 43 | - (aka. BASE) 44 | + `revision' is a revision number, or symbol (PREV, BASE, HEAD) in svn, 45 | + see svn books for details. Default revision is revision of your working 46 | + copy 47 | + 48 | + `width' is a number to make code review pages wrap in specific column 49 | 50 | Default `subdir' is working dir. 51 | 52 | Example 1: 53 | - coderev.sh bin lib 54 | + coderev.sh -w80 bin lib 55 | 56 | - Generate coderev based on your working copy. If you are working on the 57 | - most up-to-date version, this is suggested way (faster). 58 | + Generate coderev based on your working copy, web pages wrap in column 80. 59 | + If you are working on the most up-to-date version, this is suggested way 60 | + (faster). 61 | 62 | - Example 2: 63 | + Example 2 (for SVN): 64 | coderev.sh -r HEAD bin lib 65 | 66 | Generate coderev based on HEAD revision (up-to-date version in repository), 67 |
68 | 69 | -------------------------------------------------------------------------------- /demo/codediff.py-.html: -------------------------------------------------------------------------------- 1 | coderev-0.1/codediff.py
#!/usr/bin/env python
  2 | #
  3 | # Homepage: http://code.google.com/p/coderev
  4 | # License: GPLv2, see "COPYING"
  5 | #
  6 | # $Id: codediff.py 3 2008-08-19 04:06:17Z mattwyl $
  7 | 
  8 | '''Diff two files/directories and produce HTML pages.'''
  9 | 
 10 | import sys, os, stat, errno, time, re, difflib, filecmp
 11 | 
 12 | _myname = os.path.basename(sys.argv[0])
 13 | _revision = '$Revision: 3 $'.split()[1]
 14 | 
 15 | 
 16 | ########## globals & templates begin ##########
 17 | # index page layout:
 18 | #
 19 | # h1: dir1 vs dir2
 20 | # summary_info: Files changed/added/deleted: xx/yy/zz
 21 | #
 22 | # Filename C/D/A Summary      Diffs                  Sources
 23 | # Pathname x/y/z         Cdiffs Udiffs Sdiffs Fdiffs Old New
 24 | # Pathname x/y/z         Cdiffs Udiffs Sdiffs Fdiffs Old New
 25 | # Pathname x/y/z         -      -      -      -      -   New
 26 | # Pathname x/y/z         -      -      -      -      Old -
 27 | #
 28 | # Legends:
 29 | # Changed Deleted Added
 30 | # <hr>
 31 | # footer_info
 32 | #
 33 | 
 34 | _file_template = """
 35 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 36 |           "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 37 | <html>
 38 | 
 39 | <head>
 40 |     <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
 41 |     <title>
 42 |         %(title)s
 43 |     </title>
 44 |     <style type="text/css">
 45 |         %(styles)s
 46 |     </style>
 47 | </head>
 48 | 
 49 | <body>
 50 |     %(header_info)s
 51 |     %(summary_info)s
 52 |     %(data_rows)s
 53 |     %(legend)s
 54 |     <hr>
 55 |     %(footer_info)s
 56 | </body>
 57 | 
 58 | </html>"""
 59 | 
 60 | _styles = """
 61 |     body {font-family: monospace; font-size: 9pt;}
 62 |     #summary_info {font-style: italic;}
 63 |     #summary_table {
 64 |         text-align:left;font-family:monospace;
 65 |         border: 1px solid #ccc; border-collapse: collapse
 66 |     }
 67 |     td {padding-left:5px;padding-right:5px;}
 68 |     #legend {border:medium;text-align:center;}
 69 |     #footer_info {color:#333; font-size:8pt;}
 70 |     .diff {background-color:#ffd;}
 71 |     .added {background-color:#afa;}
 72 |     .deleted {background-color:#faa;}
 73 |     .table_header th {
 74 |         text-align:center;
 75 |         background-color:#f0f0f0;
 76 |         border-bottom: 1px solid #aaa;
 77 |         padding: 4px 4px 4px 4px;
 78 |     }
 79 |     """
 80 | 
 81 | _header_info_template = """
 82 |     <h1>%(dir1)s vs %(dir2)s</h1>"""
 83 | 
 84 | _summary_info_template = """
 85 |     <p id="summary_info">
 86 |         Files Changed/Deleted/Added: %(changed)d/%(deleted)d/%(added)d
 87 |     </p>"""
 88 | 
 89 | _data_rows_template = """
 90 |     <table id="summary_table" cellspacing="1" border="1" nowrap="nowrap">
 91 |     <tr class="table_header">
 92 |         <th>Filename</th>
 93 |         <th><abbr title="Changed/Deleted/Added">C/D/A</abbr> Summary</th>
 94 |         <th colspan="4">Diffs</th>
 95 |         <th colspan="2">Sources</th>
 96 |     </tr>
 97 |     %(data_rows)s
 98 |     </table>"""
 99 | 
100 | _diff_data_row_template = """
101 |     <tr class="diff">
102 |         <td>%(pathname)s</td>
103 |         <td><abbr title="Changed/Deleted/Added">%(changed)s/%(deleted)s/%(added)s</abbr></td>
104 |         <td><a href=%(pathname)s.cdiff.html title="context diffs">Cdiffs</a></td>
105 |         <td><a href=%(pathname)s.udiff.html title="unified diffs">Udiffs</a></td>
106 |         <td><a href=%(pathname)s.sdiff.html title="side-by-side context diffs">Sdiffs</a></td>
107 |         <td><a href=%(pathname)s.fdiff.html title="side-by-side full diffs">Fdiffs</a></td>
108 |         <td><a href=%(pathname)s-.html title="old file">Old</a></td>
109 |         <td><a href=%(pathname)s.html title="new file">New</a></td>
110 |     </tr>"""
111 | 
112 | _deleted_data_row_template = """
113 |     <tr class="deleted">
114 |         <td>%(pathname)s</td>
115 |         <td>-/-/-</td>
116 |         <td>-</td>
117 |         <td>-</td>
118 |         <td>-</td>
119 |         <td>-</td>
120 |         <td><a href=%(pathname)s-.html title="old file">Old</a></td>
121 |         <td>-</td>
122 |     </tr>"""
123 | 
124 | _added_data_row_template = """
125 |     <tr class="added">
126 |         <td>%(pathname)s</td>
127 |         <td>-/-/-</td>
128 |         <td>-</td>
129 |         <td>-</td>
130 |         <td>-</td>
131 |         <td>-</td>
132 |         <td>-</td>
133 |         <td><a href=%(pathname)s.html title="new file">New</a></td>
134 |     </tr>"""
135 | 
136 | _legend = """
137 |     <br><em>Legends:</em>
138 |     <table id="legend">
139 |         <tr>
140 |             <td width="20%%" class="diff">Changed</td>
141 |             <td width="20%%" class="deleted">Deleted</td>
142 |             <td width="20%%" class="added">Added</td>
143 |         </tr>
144 |     </table>"""
145 | 
146 | _footer_info_template = """
147 |     <i id="footer_info">
148 |         Generated by %(myname)s r%(revision)s at %(time)s
149 |     </i>"""
150 | 
151 | _global_dir_ignore_list = [
152 |     '\bCVS$',
153 |     '\bSCCS$',
154 |     '\b\\.svn$',
155 | ]
156 | 
157 | _global_file_ignore_list = [
158 |     '.*\\.o$',
159 |     '.*\\.swp$',
160 |     '.*\\.bak$',
161 |     '.*\\.old$',
162 |     '.*\\~$',
163 |     '\\.cvsignore$',
164 | ]
165 | ########## globals & templates end ##########
166 | 
167 | 
168 | def makeTitle(pathname, width):
169 |     'Wrap long pathname to abbreviate name to fit the text width'
170 |     if not pathname:
171 |         return 'None'
172 | 
173 |     if not width or width <= 0:
174 |         title = pathname
175 |     elif len(pathname) > width:
176 |         if width > 3:
177 |             title = '...' + pathname[-(width-3):]
178 |         else:
179 |             title = pathname[-width:]
180 |     else:
181 |         title = pathname
182 |     return title
183 | 
184 | 
185 | def getLines(file):
186 |     '''Return content of file (a list, each is a line)'''
187 |     if not file:
188 |         return []
189 |     try:
190 |         fp = open(file, 'r')
191 |         lines = fp.readlines()
192 |         fp.close()
193 |     except IOError, dig:
194 |         sys.stderr.write(str(dig) + '\n')
195 |         sys.exit(2)
196 |     return lines
197 | 
198 | 
199 | def sdiffLines(fomeLines, toLines, outFile, fromTitle, toTitle,
200 |                context, wrapnum, lines):
201 |     '''diff two texts and return side-by-side html page, write to outFile.
202 |     Return code indicating whether there are differences'''
203 |     d = difflib.HtmlDiff(wrapcolumn=wrapnum)
204 |     d._styles += '''
205 |         /* customized style */
206 |         body { font-family:monospace; font-size: 9pt; }
207 |         table.diff {font-family:monospace; border:medium;}'''
208 |     html = d.make_file(fomeLines, toLines, fromTitle, toTitle, context, lines)
209 | 
210 |     try:
211 |         fp = open(outFile, 'w')
212 |         fp.write(html)
213 |         fp.close()
214 |     except IOError, dig:
215 |         sys.stderr.write(str(dig) + '\n')
216 |         sys.exit(2)
217 | 
218 | 
219 | def cdiffLines(fomeLines, toLines, outFile, fromName, toName,
220 |                fromDate, toDate, context):
221 |     '''cdiff two texts and return summary info, write to outFile.
222 |     Return code indicating whether there are differences'''
223 |     d = difflib.context_diff(fomeLines, toLines, fromName, toName,
224 |                              fromDate, toDate, n=context)
225 |     title = 'Cdiffs of %s and %s' % (fromName, toName)
226 |     summary, html = cdiffToHtml(d, title)
227 | 
228 |     try:
229 |         fp = open(outFile, 'w')
230 |         fp.write(html)
231 |         fp.close()
232 |     except IOError, dig:
233 |         sys.stderr.write(str(dig) + '\n')
234 |         sys.exit(2)
235 | 
236 |     return summary
237 | 
238 | 
239 | def udiffLines(fomeLines, toLines, outFile, fromName, toName,
240 |                fromDate, toDate, n):
241 |     '''udiff two texts and return html page, write to outFile.
242 |     Return code indicating whether there are differences'''
243 |     d = difflib.unified_diff(fomeLines, toLines, fromName, toName,
244 |                              fromDate, toDate, n)
245 |     title = 'Udiffs of %s and %s' % (fromName, toName)
246 |     html = udiffToHtml(d, title)
247 | 
248 |     try:
249 |         fp = open(outFile, 'w')
250 |         fp.write(html)
251 |         fp.close()
252 |     except IOError, dig:
253 |         sys.stderr.write(str(dig) + '\n')
254 |         sys.exit(2)
255 | 
256 | 
257 | # This is only called when diff two files specified from command line
258 | def diffFile(file1, file2, outFile, context=False, wrapnum=None, lines=3):
259 |     '''diff two files and return html page, write to outFile.
260 |     Return code indicating whether there are differences'''
261 |     #print 'call diffFile(%s, %s, %s, ...)' % (file1, file2, outFile)
262 |     #print '-' * 32
263 | 
264 |     fomeLines = getLines(file1)
265 |     toLines = getLines(file2)
266 |     fromTitle = makeTitle(file1, wrapnum)
267 |     toTitle = makeTitle(file2, wrapnum)
268 |     sdiffLines(fomeLines, toLines, outFile, fromTitle, toTitle, context,
269 |                wrapnum, lines)
270 | 
271 | 
272 | def inIgnoreList(name, ignorelist):
273 |     for pat in ignorelist:
274 |         if re.match(pat, name):
275 |             return True
276 |     return False
277 | 
278 | 
279 | def grabDir(dir):
280 |     'Get file list of dir, and remove unwanted file from the list'
281 |     flist = []
282 |     while dir[-1] == '/':   # remove unwanted trailling slash
283 |         dir = dir[:-1]
284 |     prefix = dir + '/'      # os.path.sep
285 |     plen = len(prefix)
286 | 
287 |     for root, dirs, files in os.walk(dir):
288 |         for d in dirs:
289 |             if inIgnoreList(d, _global_dir_ignore_list):
290 |                 dirs.remove(d)
291 |         for f in files:
292 |             if not inIgnoreList(f, _global_file_ignore_list):
293 |                 name = os.path.join(root, f)
294 |                 flist.append(name[plen:])
295 |     return flist
296 | 
297 | 
298 | def mergeList(list1, list2):
299 |     # important: should make a new list! don't refer to list1
300 |     list = [ i for i in list1 ] # XXX: list = list1
301 |     for i in list2:
302 |         if i not in list1: list.append(i)
303 |     return list
304 | 
305 | 
306 | def sourceToHtml(src, outfile):
307 |     """Read file `src' and convert to html, write to file `outfile'"""
308 |     #print 'call sourceToHtml(src=%s, outfile=%s)' % (src, outfile)
309 |     #print '-' * 32
310 |     try:
311 |         infp = open(src, 'r')
312 |         body = infp.read()
313 |         infp.close()
314 |         body = body.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")
315 | 
316 |         outfp = open(outfile, 'w')
317 |         # TODO: convert to syntax highlighted html
318 |         outfp.write('''<html><head><title>%s</title></head><body>
319 |             <pre style="font-family:monospace; font-size:9pt;">%s</pre>
320 |             </body></html>''' % (src, body))
321 |         outfp.close()
322 |     except IOError, dig:
323 |         sys.stderr.write(str(dig) + '\n')
324 |         sys.exit(2)
325 | 
326 | 
327 | def diffDir(dir1, dir2, outdir, wrapnum=None, lines=3):
328 |     '''diff two directories and generate an index page.  Return code
329 |     indicating whether there are differences and the summary info'''
330 | 
331 |     #print 'call diffDir(%s, %s, %s, ...)' % (dir1, dir2, outdir)
332 |     #print '_global_dirseq:', _global_dirseq
333 |     #print '=' * 72
334 | 
335 |     flist1 = grabDir(dir1)  # already filtered
336 |     flist2 = grabDir(dir2)
337 |     flist = mergeList(flist1, flist2)
338 |     return diffDirByList(dir1, dir2, outdir, flist, wrapnum, lines)
339 | 
340 | 
341 | def lstatFile(obj):
342 |     'return None in case ENOENT, else a lstat info'
343 |     try:
344 |         statinfo = os.lstat(obj)
345 |     except OSError, dig:
346 |         if dig.errno == errno.ENOENT:
347 |             statinfo = None
348 |         else:
349 |             sys.stderr.write(str(dig) + '\n')
350 |             sys.exit(2)
351 |     return statinfo
352 | 
353 | 
354 | def diffDirByList(dir1, dir2, outdir, flist, wrapnum=None, lines=3):
355 |     '''diff two directories and generate an index page.  Return code
356 |     indicating whether there are differences and the summary info'''
357 | 
358 |     data_rows = ''
359 |     data_row = ''
360 |     summary = { 'changed': 0, 'added': 0, 'deleted': 0 }
361 |     file_summary = { 'changed': 0, 'added': 0, 'deleted': 0 }
362 |     has_diff = False
363 | 
364 |     flist.sort()
365 | 
366 |     for f in flist:
367 |         # set default values
368 |         fromLines = ''
369 |         toLines = ''
370 |         fromDate = ''
371 |         toDate = ''
372 | 
373 |         target = os.path.join(outdir, f)
374 |         obj1 = os.path.join(dir1, f)
375 |         obj2 = os.path.join(dir2, f)
376 | 
377 |         # make output dir and sub dir
378 |         try:
379 |             os.makedirs(os.path.join(outdir, os.path.dirname(f)))
380 |         except OSError, dig:
381 |             if dig.errno != errno.EEXIST:
382 |                 sys.stderr.write(str(dig) + '\n')
383 |                 sys.exit(2)
384 | 
385 |         stat1 = lstatFile(obj1)
386 |         stat2 = lstatFile(obj2)
387 | 
388 |         if stat1 and not stat2: # deleted
389 |             print '%-40s |' % f,
390 |             print 'File removed',
391 |             #st_mtime = time.localtime(stat1[8])
392 |             if not stat.S_ISREG(stat1[0]) or isBinaryFile(obj1):
393 |                 print '(skipped dir/special/binary)'
394 |                 continue
395 |             print
396 |             old = sourceToHtml(obj1, target + '-.html')
397 |             data_row = _deleted_data_row_template % {'pathname': f}
398 |             summary['deleted'] += 1
399 |             has_diff = True
400 | 
401 |         elif not stat1 and stat2: # added
402 |             print '%-40s |' % f,
403 |             print 'New file',
404 |             if not stat.S_ISREG(stat2[0]) or isBinaryFile(obj2):
405 |                 print '(skipped special/binary)'
406 |                 continue
407 |             print
408 |             new = sourceToHtml(obj2, target + '.html')
409 |             data_row = _added_data_row_template % {'pathname': f}
410 |             summary['added'] += 1
411 |             has_diff = True
412 | 
413 |         elif stat1 and stat2: # same or diff
414 |             # do not compare special or binary file
415 |             if not stat.S_ISREG(stat1[0]) or isBinaryFile(obj1):
416 |                 print '%-40s |' % f,
417 |                 print '(skipped, former file is special)'
418 |                 continue
419 |             if not stat.S_ISREG(stat2[0]) or isBinaryFile(obj2):
420 |                 print '%-40s |' % f,
421 |                 print '(skipped, latter file is binary)'
422 |                 continue
423 |             if filecmp.cmp(obj1, obj2):
424 |                 continue
425 | 
426 |             has_diff = True
427 |             fromDate = time.ctime(stat1[8])
428 |             toDate = time.ctime(stat2[8])
429 |             fromLines = getLines(obj1)
430 |             toLines = getLines(obj2)
431 | 
432 |             # Cdiffs
433 |             file_summary = cdiffLines(fromLines, toLines,
434 |                 target+'.cdiff.html', obj1, obj2, fromDate, toDate, lines)
435 | 
436 |             # Udiffs
437 |             udiffLines(fromLines, toLines, target+'.udiff.html', obj1, obj2,
438 |                        fromDate, toDate, lines)
439 | 
440 |             # Sdiffs
441 |             sdiffLines(fromLines, toLines, target+'.sdiff.html', obj1, obj2,
442 |                        True, wrapnum, lines)
443 | 
444 |             # Fdiffs
445 |             sdiffLines(fromLines, toLines, target+'.fdiff.html', obj1, obj2,
446 |                        False, wrapnum, lines)
447 | 
448 |             print '%-40s |' % f,
449 |             print 'Changed/Deleted/Added: %d/%d/%d' % (\
450 |                 file_summary['changed'],
451 |                 file_summary['deleted'],
452 |                 file_summary['added'])
453 | 
454 |             old = sourceToHtml(obj1, target + '-.html')
455 |             new = sourceToHtml(obj2, target + '.html')
456 |             data_row = _diff_data_row_template % dict(
457 |                 pathname = f,
458 |                 changed = file_summary['changed'],
459 |                 deleted = file_summary['deleted'],
460 |                 added = file_summary['added'],
461 |             )
462 |             summary['changed'] += 1
463 |         else: # this case occured when controlled by master file list
464 |             print '%-40s |' % f,
465 |             print 'Not found'
466 |             data_row = ''
467 | 
468 |         data_rows += data_row
469 | 
470 |     if not has_diff:
471 |         return False
472 | 
473 |     # Generate footer info
474 |     footer_info = _footer_info_template % dict(
475 |         time = time.strftime('%a %b %d %X %Z %Y', time.localtime()),
476 |         myname = _myname,
477 |         revision = _revision
478 |     )
479 | 
480 |     # now wirte index page
481 |     try:
482 |         index = open(os.path.join(outdir, 'index.html'), 'w')
483 |     except IOError, dig:
484 |         sys.stderr.write(str(dig) + '\n')
485 |         sys.exit(2)
486 |     index.write(_file_template % dict(
487 |         title = '%s vs %s' % (dir1, dir2),
488 |         styles = _styles,
489 |         header_info = _header_info_template % dict(dir1=dir1, dir2=dir2),
490 |         summary_info = _summary_info_template % summary,
491 |         data_rows = _data_rows_template % {'data_rows': data_rows},
492 |         legend = _legend,
493 |         footer_info = footer_info,
494 |     ))
495 |     index.close()
496 |     return True
497 | 
498 | 
499 | def warnOverwrite(pathname):
500 |     'Warnning for overwriting, return True if answered yes, False if no'
501 |     msg = "`%s' exists, are you sure you want to overwrite it (yes/no)? "
502 |     while True:
503 |         sys.stderr.write(msg % pathname)
504 |         answer = raw_input('')
505 |         if answer == 'yes':
506 |             return True
507 |         elif answer == 'no':
508 |             return False
509 |         # else: prompt again
510 | 
511 | 
512 | def isBinaryFile(file):
513 |     '''I determine a binary file by reading the first 1024 bytes of the file
514 |     and counting the non-text characters, if the number is great than 8, then
515 |     the file is considered as binary file.  This is not very reliable but is
516 |     effective'''
517 |     if not file:
518 |         return False
519 | 
520 |     non_text = 0
521 |     target_count = 8
522 |     try:
523 |         fp = open(file, 'rb')
524 |         data = fp.read(1024)
525 |         for c in data:
526 |             a = ord(c)
527 |             if a < 8 or (a > 13 and a < 32): # not printable
528 |                 non_text += 1
529 |                 if non_text >= target_count: break
530 |         fp.close()
531 |     except IOError, dig:
532 |         sys.stderr.write(str(dig) + '\n')
533 |         sys.exit(2)
534 |     return non_text >= target_count
535 | 
536 | 
537 | def cdiffToHtml(cdiff, title):
538 |     '''cdiff is context diff (a list) that generated by difflib.context_diff,
539 |     return summary and html page'''
540 |     summary = { 'changed': 0, 'added': 0, 'deleted': 0 }
541 |     line_pattern = '<span class="%s">%s</span>'
542 | 
543 |     body = ''
544 |     old_group = False
545 |     for line in cdiff:
546 |         n = len(line)
547 |         line = line.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")
548 |         if n >= 4 and line[0:4] == '*** ':
549 |             old_group = True
550 |             body += line_pattern % ('fromtitle', line)
551 |         elif n >= 4 and line[0:4] == '--- ':
552 |             old_group = False
553 |             body += line_pattern % ('totitle', line)
554 |         elif n >= 2 and line[0:2] == '  ':
555 |             body += line_pattern % ('same', line)
556 |         elif n >= 2 and line[0:2] == '! ':
557 |             body += line_pattern % ('change', line)
558 |             if old_group:
559 |                 summary['changed'] += 1
560 |         elif n >= 2 and line[0:2] == '- ':
561 |             body += line_pattern % ('delete', line)
562 |             summary['deleted'] += 1
563 |         elif n >= 2 and line[0:2] == '+ ':
564 |             body += line_pattern % ('insert', line)
565 |             summary['added'] += 1
566 |         elif n >= 15 and line[0:15] == '*' * 15:
567 |             body += '<hr>'
568 |         else: # shouldn't happen
569 |             body += line
570 | 
571 |     html = '''<html><head>
572 |         <title>%s</title>
573 |         <style type="text/css">
574 |             .fromtitle {color:brown; font:bold 11pt;}
575 |             .totitle {color:green; font:bold 11pt;}
576 |             .same {color:black; font:9pt;}
577 |             .change {color:blue; font:9pt;}
578 |             .delete {color:brown; font:9pt;}
579 |             .insert {color:green; font:9pt;}
580 |         </style>
581 |         <body>
582 |             <pre>%s</pre>
583 |         </body>
584 |         </head></html>''' % (title, body)
585 |     return summary, html
586 | 
587 | 
588 | def udiffToHtml(udiff, title):
589 |     '''udiff is uniform diff (a list) that generated by difflib.uniform_diff,
590 |     return html page'''
591 | 
592 |     line_pattern = '<span class="%s">%s</span>'
593 |     body = ''
594 |     for line in udiff:
595 |         n = len(line)
596 |         line = line.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")
597 |         if n >= 4 and line[0:4] == '--- ':
598 |             body += line_pattern % ('fromtitle', line)
599 |         elif n >= 4 and line[0:4] == '+++ ':
600 |             body += line_pattern % ('totitle', line)
601 |         elif n >= 1 and line[0] == ' ':
602 |             body += line_pattern % ('same', line)
603 |         elif n >= 1 and line[0] == '-':
604 |             body += line_pattern % ('old', line)
605 |         elif n >= 1 and line[0] == '+':
606 |             body += line_pattern % ('new', line)
607 |         elif n >= 4 and line[0:4] == '@@ -':
608 |             body += '<hr>'
609 |             body += line_pattern % ('head', line)
610 |         else: # shouldn't happen
611 |             body += line
612 | 
613 |     html = '''<html><head>
614 |         <title>%s</title>
615 |         <style type="text/css">
616 |             .fromtitle {color:brown; font:bold 11pt;}
617 |             .totitle {color:green; font:bold 11pt;}
618 |             .head {color:blue; font:bold 9pt;}
619 |             .same {color:black; font:9pt;}
620 |             .old {color:brown; font:9pt;}
621 |             .new {color:green; font:9pt;}
622 |         </style>
623 |         <body>
624 |             <pre>%s</pre>
625 |         </body>
626 |         </head></html>''' % (title, body)
627 |     return html
628 | 
629 | 
630 | def stripPrefix(name, p=0):
631 |     '''strip NUM slashes, like patch(1) -pNUM
632 |     eg1: /foo/bar/a/b/x.c
633 |     -p0 gives orignal name (no change)
634 |     -p1 gives foo/bar/a/b/x.c
635 |     -p2 gives bar/a/b/x.c
636 |     -p9 gives x.c
637 | 
638 |     eg2: foo/bar/a/b/x.c
639 |     -p0 gives orignal name (no change)
640 |     -p1 gives bar/a/b/x.c
641 |     -p2 gives a/b/x.c
642 |     -p9 gives x.c
643 | 
644 |     eg3: ./foo/bar/a/b/x.c
645 |     -p0 gives orignal name (no change)
646 |     -p1 gives foo/bar/a/b/x.c
647 |     -p2 gives bar/a/b/x.c
648 |     -p9 gives x.c
649 |     '''
650 |     cur = 0
651 |     tail = len(name) - 1
652 |     while p > 0:
653 |         index = name.find('/', cur)
654 |         #print 'p:', p, 'cur:', cur, 'index:', index
655 |         if index == -1: break
656 |         while index <= tail and name[index] == '/':
657 |             index += 1
658 |         cur = index
659 |         p -= 1
660 |     return name[cur:]
661 | 
662 | 
663 | def readFileList(file, p=0):
664 |     '''Return content of filelist (a file, each line is a filename)'''
665 |     flist = []
666 |     if not file:
667 |         return []
668 |     if file == '-':  # read from stdin
669 |         flist = sys.stdin.readlines()
670 |     else:
671 |         try:
672 |             fp = open(file, 'r')
673 |             flist = fp.readlines()
674 |             fp.close()
675 |         except IOError, dig:
676 |             sys.stderr.write(str(dig) + '\n')
677 |             sys.exit(2)
678 | 
679 |     outlist = []
680 |     for i in flist:
681 |         outlist.append(stripPrefix(i, p).rstrip())
682 |     #print outlist
683 |     return outlist
684 | 
685 | 
686 | if __name__ == '__main__':
687 |     import optparse
688 | 
689 |     usage = '''
690 |     %(name)s [options] OLD NEW
691 |     %(name)s OLD NEW [options]
692 | 
693 |     Diff two files/directories and produce HTML pages.''' % \
694 |     {'name': os.path.basename(sys.argv[0])}
695 | 
696 |     parser = optparse.OptionParser(usage)
697 |     parser.add_option('-o', '--outupt', dest='output',
698 |                       help='specify output file or directory name')
699 |     parser.add_option('-c', '--context', action='store_true',
700 |                       dest='context', default=False,
701 |                       help='generate context diff (default is full diff),' + \
702 |                            ' only take effect when diffing two files')
703 |     parser.add_option('-f', '--filelist', dest='filelist', metavar='FILE',
704 |                       help='specify a file list to read from, filelist can ' + \
705 |                            'be generated by find -type f, specify - to read' + \
706 |                            ' from stdin')
707 |     parser.add_option('-p', '--striplevel', dest='striplevel',
708 |                       type='int', metavar='NUM',
709 |                       help='for all pathnames in the filelist, delete NUM ' + \
710 |                            'path name components from the beginning of each' + \
711 |                            ' path name, it is similar to patch(1) -p')
712 |     parser.add_option('-n', '--lines', dest='lines',
713 |                       type='int', metavar='NUM', default=3,
714 |                       help='specify context line count when generating ' + \
715 |                            'context diffs or unified diffs, default is 3')
716 |     parser.add_option('-w', '--wrap', dest='wrapnum',
717 |                       type='int', metavar='WIDTH',
718 |                       help='specify column number where lines are broken ' + \
719 |                       'and wrapped for sdiff, default is no line wrapping')
720 |     parser.add_option('-y', '--yes', action='store_true',
721 |                       dest='overwrite', default=False,
722 |                       help='do not prompt for overwriting')
723 | 
724 |     opts, args = parser.parse_args()
725 | 
726 |     if len(args) != 2:
727 |         sys.stderr.write("Sorry, you must specify two file/directory names\n" \
728 |                          + "type `%s -h' for help\n" % _myname)
729 |         sys.exit(1)
730 | 
731 |     if not opts.output:
732 |         sys.stderr.write("Sorry, you must specify output name (use `-o')\n")
733 |         sys.exit(2)
734 | 
735 |     try:
736 |         # Note: use stat instead lstat to permit symbolic links
737 |         stat1 = os.stat(args[0])[0]
738 |         stat2 = os.stat(args[1])[0]
739 |     except OSError, dig:
740 |         sys.stderr.write(str(dig) + '\n')
741 |         sys.exit(2)
742 | 
743 |     # Compare two files
744 |     #
745 |     if stat.S_ISREG(stat1) and stat.S_ISREG(stat2):
746 |         if not opts.overwrite and os.path.exists(opts.output):
747 |             if not warnOverwrite(opts.output): sys.exit(1)
748 |         url = 'file://%s\n' % os.path.realpath(opts.output)
749 | 
750 |         if filecmp.cmp(args[0], arg2[1]):
751 |             print 'No difference found.'
752 |         else:
753 |             diffFile(args[0], args[1], opts.output, opts.context,
754 |                     opts.wrapnum, opts.lines)
755 |             print '\nURL:\n%s' % url
756 | 
757 |     # Compare two dirs
758 |     #
759 |     elif stat.S_ISDIR(stat1) and stat.S_ISDIR(stat2):
760 |         if not opts.overwrite and os.path.exists(opts.output):
761 |             if opts.filelist == '-':
762 |                 # stdin redirected, so we cannot read answer from stdin
763 |                 print "`%s' exists, please select another output directory, " \
764 |                       "or specify '-y' to force overwriting." % opts.output
765 |                 sys.exit(1)
766 |             else:
767 |                 if not warnOverwrite(opts.output):
768 |                     sys.exit(1)
769 |         url = 'file://%s/index.html\n' % os.path.realpath(opts.output)
770 | 
771 |         if opts.filelist:
772 |             # filelist mode, contolled by master file list
773 |             # read file list, ignore lines with first char is '/'
774 |             # if see 'foo/bar/abc', makedirs foo/bar/, then put abc.diffs to it
775 |             flist = readFileList(opts.filelist, opts.striplevel)
776 |             if diffDirByList(args[0], args[1], opts.output,
777 |                              flist, opts.wrapnum, opts.lines):
778 |                 print '\nURL:\n%s' % url
779 |             else:
780 |                 print 'No difference found.'
781 |         else:
782 |             if diffDir(args[0], args[1], opts.output, opts.wrapnum,
783 |                        opts.lines):
784 |                 print '\nURL:\n%s' % url
785 |             else:
786 |                 print 'No difference found.'
787 | 
788 |     else:
789 |         sys.stderr.write("Sorry, I don't know how to compare `%s' and `%s'\n" \
790 |                          % (args[0], args[1]))
791 |         sys.exit(2)
792 | 
793 | # vim:set et sts=4 sw=4:
794 | 
-------------------------------------------------------------------------------- /demo/codediff.py.cdiff.html: -------------------------------------------------------------------------------- 1 | 2 | Cdiff of coderev-0.1/codediff.py and coderev-0.2/codediff.py 3 | 11 | 12 |
*** coderev-0.1/codediff.py Sat Aug 23 17:05:39 2008
 13 | --- coderev-0.2/codediff.py Sat Aug 23 17:07:26 2008
 14 | 
*** 3,16 **** 15 | # Homepage: http://code.google.com/p/coderev 16 | # License: GPLv2, see "COPYING" 17 | # 18 | ! # $Id: codediff.py 3 2008-08-19 04:06:17Z mattwyl $ 19 | 20 | '''Diff two files/directories and produce HTML pages.''' 21 | 22 | import sys, os, stat, errno, time, re, difflib, filecmp 23 | 24 | _myname = os.path.basename(sys.argv[0]) 25 | ! _revision = '$Revision: 3 $'.split()[1] 26 | 27 | 28 | ########## globals & templates begin ########## 29 | --- 3,16 ---- 30 | # Homepage: http://code.google.com/p/coderev 31 | # License: GPLv2, see "COPYING" 32 | # 33 | ! # $Id: codediff.py 9 2008-08-21 14:08:53Z mattwyl $ 34 | 35 | '''Diff two files/directories and produce HTML pages.''' 36 | 37 | import sys, os, stat, errno, time, re, difflib, filecmp 38 | 39 | _myname = os.path.basename(sys.argv[0]) 40 | ! _revision = '$Revision: 9 $'.split()[1] 41 | 42 | 43 | ########## globals & templates begin ########## 44 |
*** 148,167 **** 45 | Generated by %(myname)s r%(revision)s at %(time)s 46 | </i>""" 47 | 48 | ! _global_dir_ignore_list = [ 49 | ! '\bCVS$', 50 | ! '\bSCCS$', 51 | ! '\b\\.svn$', 52 | ! ] 53 | ! 54 | ! _global_file_ignore_list = [ 55 | ! '.*\\.o$', 56 | ! '.*\\.swp$', 57 | ! '.*\\.bak$', 58 | ! '.*\\.old$', 59 | ! '.*\\~$', 60 | ! '\\.cvsignore$', 61 | ! ] 62 | ########## globals & templates end ########## 63 | 64 | 65 | --- 148,167 ---- 66 | Generated by %(myname)s r%(revision)s at %(time)s 67 | </i>""" 68 | 69 | ! _global_dir_ignore_list = ( 70 | ! r'^CVS$', 71 | ! r'^SCCS$', 72 | ! r'^\.svn$', 73 | ! ) 74 | ! 75 | ! _global_file_ignore_list = ( 76 | ! r'.*\.o$', 77 | ! r'.*\.swp$', 78 | ! r'.*\.bak$', 79 | ! r'.*\.old$', 80 | ! r'.*~$', 81 | ! r'^\.cvsignore$', 82 | ! ) 83 | ########## globals & templates end ########## 84 | 85 | 86 |
*** 285,291 **** 87 | plen = len(prefix) 88 | 89 | for root, dirs, files in os.walk(dir): 90 | ! for d in dirs: 91 | if inIgnoreList(d, _global_dir_ignore_list): 92 | dirs.remove(d) 93 | for f in files: 94 | --- 285,291 ---- 95 | plen = len(prefix) 96 | 97 | for root, dirs, files in os.walk(dir): 98 | ! for d in [k for k in dirs]: 99 | if inIgnoreList(d, _global_dir_ignore_list): 100 | dirs.remove(d) 101 | for f in files: 102 |
103 | 104 | -------------------------------------------------------------------------------- /demo/codediff.py.html: -------------------------------------------------------------------------------- 1 | coderev-0.2/codediff.py
#!/usr/bin/env python
  2 | #
  3 | # Homepage: http://code.google.com/p/coderev
  4 | # License: GPLv2, see "COPYING"
  5 | #
  6 | # $Id: codediff.py 9 2008-08-21 14:08:53Z mattwyl $
  7 | 
  8 | '''Diff two files/directories and produce HTML pages.'''
  9 | 
 10 | import sys, os, stat, errno, time, re, difflib, filecmp
 11 | 
 12 | _myname = os.path.basename(sys.argv[0])
 13 | _revision = '$Revision: 9 $'.split()[1]
 14 | 
 15 | 
 16 | ########## globals & templates begin ##########
 17 | # index page layout:
 18 | #
 19 | # h1: dir1 vs dir2
 20 | # summary_info: Files changed/added/deleted: xx/yy/zz
 21 | #
 22 | # Filename C/D/A Summary      Diffs                  Sources
 23 | # Pathname x/y/z         Cdiffs Udiffs Sdiffs Fdiffs Old New
 24 | # Pathname x/y/z         Cdiffs Udiffs Sdiffs Fdiffs Old New
 25 | # Pathname x/y/z         -      -      -      -      -   New
 26 | # Pathname x/y/z         -      -      -      -      Old -
 27 | #
 28 | # Legends:
 29 | # Changed Deleted Added
 30 | # <hr>
 31 | # footer_info
 32 | #
 33 | 
 34 | _file_template = """
 35 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 36 |           "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 37 | <html>
 38 | 
 39 | <head>
 40 |     <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
 41 |     <title>
 42 |         %(title)s
 43 |     </title>
 44 |     <style type="text/css">
 45 |         %(styles)s
 46 |     </style>
 47 | </head>
 48 | 
 49 | <body>
 50 |     %(header_info)s
 51 |     %(summary_info)s
 52 |     %(data_rows)s
 53 |     %(legend)s
 54 |     <hr>
 55 |     %(footer_info)s
 56 | </body>
 57 | 
 58 | </html>"""
 59 | 
 60 | _styles = """
 61 |     body {font-family: monospace; font-size: 9pt;}
 62 |     #summary_info {font-style: italic;}
 63 |     #summary_table {
 64 |         text-align:left;font-family:monospace;
 65 |         border: 1px solid #ccc; border-collapse: collapse
 66 |     }
 67 |     td {padding-left:5px;padding-right:5px;}
 68 |     #legend {border:medium;text-align:center;}
 69 |     #footer_info {color:#333; font-size:8pt;}
 70 |     .diff {background-color:#ffd;}
 71 |     .added {background-color:#afa;}
 72 |     .deleted {background-color:#faa;}
 73 |     .table_header th {
 74 |         text-align:center;
 75 |         background-color:#f0f0f0;
 76 |         border-bottom: 1px solid #aaa;
 77 |         padding: 4px 4px 4px 4px;
 78 |     }
 79 |     """
 80 | 
 81 | _header_info_template = """
 82 |     <h1>%(dir1)s vs %(dir2)s</h1>"""
 83 | 
 84 | _summary_info_template = """
 85 |     <p id="summary_info">
 86 |         Files Changed/Deleted/Added: %(changed)d/%(deleted)d/%(added)d
 87 |     </p>"""
 88 | 
 89 | _data_rows_template = """
 90 |     <table id="summary_table" cellspacing="1" border="1" nowrap="nowrap">
 91 |     <tr class="table_header">
 92 |         <th>Filename</th>
 93 |         <th><abbr title="Changed/Deleted/Added">C/D/A</abbr> Summary</th>
 94 |         <th colspan="4">Diffs</th>
 95 |         <th colspan="2">Sources</th>
 96 |     </tr>
 97 |     %(data_rows)s
 98 |     </table>"""
 99 | 
100 | _diff_data_row_template = """
101 |     <tr class="diff">
102 |         <td>%(pathname)s</td>
103 |         <td><abbr title="Changed/Deleted/Added">%(changed)s/%(deleted)s/%(added)s</abbr></td>
104 |         <td><a href=%(pathname)s.cdiff.html title="context diffs">Cdiffs</a></td>
105 |         <td><a href=%(pathname)s.udiff.html title="unified diffs">Udiffs</a></td>
106 |         <td><a href=%(pathname)s.sdiff.html title="side-by-side context diffs">Sdiffs</a></td>
107 |         <td><a href=%(pathname)s.fdiff.html title="side-by-side full diffs">Fdiffs</a></td>
108 |         <td><a href=%(pathname)s-.html title="old file">Old</a></td>
109 |         <td><a href=%(pathname)s.html title="new file">New</a></td>
110 |     </tr>"""
111 | 
112 | _deleted_data_row_template = """
113 |     <tr class="deleted">
114 |         <td>%(pathname)s</td>
115 |         <td>-/-/-</td>
116 |         <td>-</td>
117 |         <td>-</td>
118 |         <td>-</td>
119 |         <td>-</td>
120 |         <td><a href=%(pathname)s-.html title="old file">Old</a></td>
121 |         <td>-</td>
122 |     </tr>"""
123 | 
124 | _added_data_row_template = """
125 |     <tr class="added">
126 |         <td>%(pathname)s</td>
127 |         <td>-/-/-</td>
128 |         <td>-</td>
129 |         <td>-</td>
130 |         <td>-</td>
131 |         <td>-</td>
132 |         <td>-</td>
133 |         <td><a href=%(pathname)s.html title="new file">New</a></td>
134 |     </tr>"""
135 | 
136 | _legend = """
137 |     <br><em>Legends:</em>
138 |     <table id="legend">
139 |         <tr>
140 |             <td width="20%%" class="diff">Changed</td>
141 |             <td width="20%%" class="deleted">Deleted</td>
142 |             <td width="20%%" class="added">Added</td>
143 |         </tr>
144 |     </table>"""
145 | 
146 | _footer_info_template = """
147 |     <i id="footer_info">
148 |         Generated by %(myname)s r%(revision)s at %(time)s
149 |     </i>"""
150 | 
151 | _global_dir_ignore_list = (
152 |     r'^CVS$',
153 |     r'^SCCS$',
154 |     r'^\.svn$',
155 | )
156 | 
157 | _global_file_ignore_list = (
158 |     r'.*\.o$',
159 |     r'.*\.swp$',
160 |     r'.*\.bak$',
161 |     r'.*\.old$',
162 |     r'.*~$',
163 |     r'^\.cvsignore$',
164 | )
165 | ########## globals & templates end ##########
166 | 
167 | 
168 | def makeTitle(pathname, width):
169 |     'Wrap long pathname to abbreviate name to fit the text width'
170 |     if not pathname:
171 |         return 'None'
172 | 
173 |     if not width or width <= 0:
174 |         title = pathname
175 |     elif len(pathname) > width:
176 |         if width > 3:
177 |             title = '...' + pathname[-(width-3):]
178 |         else:
179 |             title = pathname[-width:]
180 |     else:
181 |         title = pathname
182 |     return title
183 | 
184 | 
185 | def getLines(file):
186 |     '''Return content of file (a list, each is a line)'''
187 |     if not file:
188 |         return []
189 |     try:
190 |         fp = open(file, 'r')
191 |         lines = fp.readlines()
192 |         fp.close()
193 |     except IOError, dig:
194 |         sys.stderr.write(str(dig) + '\n')
195 |         sys.exit(2)
196 |     return lines
197 | 
198 | 
199 | def sdiffLines(fomeLines, toLines, outFile, fromTitle, toTitle,
200 |                context, wrapnum, lines):
201 |     '''diff two texts and return side-by-side html page, write to outFile.
202 |     Return code indicating whether there are differences'''
203 |     d = difflib.HtmlDiff(wrapcolumn=wrapnum)
204 |     d._styles += '''
205 |         /* customized style */
206 |         body { font-family:monospace; font-size: 9pt; }
207 |         table.diff {font-family:monospace; border:medium;}'''
208 |     html = d.make_file(fomeLines, toLines, fromTitle, toTitle, context, lines)
209 | 
210 |     try:
211 |         fp = open(outFile, 'w')
212 |         fp.write(html)
213 |         fp.close()
214 |     except IOError, dig:
215 |         sys.stderr.write(str(dig) + '\n')
216 |         sys.exit(2)
217 | 
218 | 
219 | def cdiffLines(fomeLines, toLines, outFile, fromName, toName,
220 |                fromDate, toDate, context):
221 |     '''cdiff two texts and return summary info, write to outFile.
222 |     Return code indicating whether there are differences'''
223 |     d = difflib.context_diff(fomeLines, toLines, fromName, toName,
224 |                              fromDate, toDate, n=context)
225 |     title = 'Cdiffs of %s and %s' % (fromName, toName)
226 |     summary, html = cdiffToHtml(d, title)
227 | 
228 |     try:
229 |         fp = open(outFile, 'w')
230 |         fp.write(html)
231 |         fp.close()
232 |     except IOError, dig:
233 |         sys.stderr.write(str(dig) + '\n')
234 |         sys.exit(2)
235 | 
236 |     return summary
237 | 
238 | 
239 | def udiffLines(fomeLines, toLines, outFile, fromName, toName,
240 |                fromDate, toDate, n):
241 |     '''udiff two texts and return html page, write to outFile.
242 |     Return code indicating whether there are differences'''
243 |     d = difflib.unified_diff(fomeLines, toLines, fromName, toName,
244 |                              fromDate, toDate, n)
245 |     title = 'Udiffs of %s and %s' % (fromName, toName)
246 |     html = udiffToHtml(d, title)
247 | 
248 |     try:
249 |         fp = open(outFile, 'w')
250 |         fp.write(html)
251 |         fp.close()
252 |     except IOError, dig:
253 |         sys.stderr.write(str(dig) + '\n')
254 |         sys.exit(2)
255 | 
256 | 
257 | # This is only called when diff two files specified from command line
258 | def diffFile(file1, file2, outFile, context=False, wrapnum=None, lines=3):
259 |     '''diff two files and return html page, write to outFile.
260 |     Return code indicating whether there are differences'''
261 |     #print 'call diffFile(%s, %s, %s, ...)' % (file1, file2, outFile)
262 |     #print '-' * 32
263 | 
264 |     fomeLines = getLines(file1)
265 |     toLines = getLines(file2)
266 |     fromTitle = makeTitle(file1, wrapnum)
267 |     toTitle = makeTitle(file2, wrapnum)
268 |     sdiffLines(fomeLines, toLines, outFile, fromTitle, toTitle, context,
269 |                wrapnum, lines)
270 | 
271 | 
272 | def inIgnoreList(name, ignorelist):
273 |     for pat in ignorelist:
274 |         if re.match(pat, name):
275 |             return True
276 |     return False
277 | 
278 | 
279 | def grabDir(dir):
280 |     'Get file list of dir, and remove unwanted file from the list'
281 |     flist = []
282 |     while dir[-1] == '/':   # remove unwanted trailling slash
283 |         dir = dir[:-1]
284 |     prefix = dir + '/'      # os.path.sep
285 |     plen = len(prefix)
286 | 
287 |     for root, dirs, files in os.walk(dir):
288 |         for d in [k for k in dirs]:
289 |             if inIgnoreList(d, _global_dir_ignore_list):
290 |                 dirs.remove(d)
291 |         for f in files:
292 |             if not inIgnoreList(f, _global_file_ignore_list):
293 |                 name = os.path.join(root, f)
294 |                 flist.append(name[plen:])
295 |     return flist
296 | 
297 | 
298 | def mergeList(list1, list2):
299 |     # important: should make a new list! don't refer to list1
300 |     list = [ i for i in list1 ] # XXX: list = list1
301 |     for i in list2:
302 |         if i not in list1: list.append(i)
303 |     return list
304 | 
305 | 
306 | def sourceToHtml(src, outfile):
307 |     """Read file `src' and convert to html, write to file `outfile'"""
308 |     #print 'call sourceToHtml(src=%s, outfile=%s)' % (src, outfile)
309 |     #print '-' * 32
310 |     try:
311 |         infp = open(src, 'r')
312 |         body = infp.read()
313 |         infp.close()
314 |         body = body.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")
315 | 
316 |         outfp = open(outfile, 'w')
317 |         # TODO: convert to syntax highlighted html
318 |         outfp.write('''<html><head><title>%s</title></head><body>
319 |             <pre style="font-family:monospace; font-size:9pt;">%s</pre>
320 |             </body></html>''' % (src, body))
321 |         outfp.close()
322 |     except IOError, dig:
323 |         sys.stderr.write(str(dig) + '\n')
324 |         sys.exit(2)
325 | 
326 | 
327 | def diffDir(dir1, dir2, outdir, wrapnum=None, lines=3):
328 |     '''diff two directories and generate an index page.  Return code
329 |     indicating whether there are differences and the summary info'''
330 | 
331 |     #print 'call diffDir(%s, %s, %s, ...)' % (dir1, dir2, outdir)
332 |     #print '_global_dirseq:', _global_dirseq
333 |     #print '=' * 72
334 | 
335 |     flist1 = grabDir(dir1)  # already filtered
336 |     flist2 = grabDir(dir2)
337 |     flist = mergeList(flist1, flist2)
338 |     return diffDirByList(dir1, dir2, outdir, flist, wrapnum, lines)
339 | 
340 | 
341 | def lstatFile(obj):
342 |     'return None in case ENOENT, else a lstat info'
343 |     try:
344 |         statinfo = os.lstat(obj)
345 |     except OSError, dig:
346 |         if dig.errno == errno.ENOENT:
347 |             statinfo = None
348 |         else:
349 |             sys.stderr.write(str(dig) + '\n')
350 |             sys.exit(2)
351 |     return statinfo
352 | 
353 | 
354 | def diffDirByList(dir1, dir2, outdir, flist, wrapnum=None, lines=3):
355 |     '''diff two directories and generate an index page.  Return code
356 |     indicating whether there are differences and the summary info'''
357 | 
358 |     data_rows = ''
359 |     data_row = ''
360 |     summary = { 'changed': 0, 'added': 0, 'deleted': 0 }
361 |     file_summary = { 'changed': 0, 'added': 0, 'deleted': 0 }
362 |     has_diff = False
363 | 
364 |     flist.sort()
365 | 
366 |     for f in flist:
367 |         # set default values
368 |         fromLines = ''
369 |         toLines = ''
370 |         fromDate = ''
371 |         toDate = ''
372 | 
373 |         target = os.path.join(outdir, f)
374 |         obj1 = os.path.join(dir1, f)
375 |         obj2 = os.path.join(dir2, f)
376 | 
377 |         # make output dir and sub dir
378 |         try:
379 |             os.makedirs(os.path.join(outdir, os.path.dirname(f)))
380 |         except OSError, dig:
381 |             if dig.errno != errno.EEXIST:
382 |                 sys.stderr.write(str(dig) + '\n')
383 |                 sys.exit(2)
384 | 
385 |         stat1 = lstatFile(obj1)
386 |         stat2 = lstatFile(obj2)
387 | 
388 |         if stat1 and not stat2: # deleted
389 |             print '%-40s |' % f,
390 |             print 'File removed',
391 |             #st_mtime = time.localtime(stat1[8])
392 |             if not stat.S_ISREG(stat1[0]) or isBinaryFile(obj1):
393 |                 print '(skipped dir/special/binary)'
394 |                 continue
395 |             print
396 |             old = sourceToHtml(obj1, target + '-.html')
397 |             data_row = _deleted_data_row_template % {'pathname': f}
398 |             summary['deleted'] += 1
399 |             has_diff = True
400 | 
401 |         elif not stat1 and stat2: # added
402 |             print '%-40s |' % f,
403 |             print 'New file',
404 |             if not stat.S_ISREG(stat2[0]) or isBinaryFile(obj2):
405 |                 print '(skipped special/binary)'
406 |                 continue
407 |             print
408 |             new = sourceToHtml(obj2, target + '.html')
409 |             data_row = _added_data_row_template % {'pathname': f}
410 |             summary['added'] += 1
411 |             has_diff = True
412 | 
413 |         elif stat1 and stat2: # same or diff
414 |             # do not compare special or binary file
415 |             if not stat.S_ISREG(stat1[0]) or isBinaryFile(obj1):
416 |                 print '%-40s |' % f,
417 |                 print '(skipped, former file is special)'
418 |                 continue
419 |             if not stat.S_ISREG(stat2[0]) or isBinaryFile(obj2):
420 |                 print '%-40s |' % f,
421 |                 print '(skipped, latter file is binary)'
422 |                 continue
423 |             if filecmp.cmp(obj1, obj2):
424 |                 continue
425 | 
426 |             has_diff = True
427 |             fromDate = time.ctime(stat1[8])
428 |             toDate = time.ctime(stat2[8])
429 |             fromLines = getLines(obj1)
430 |             toLines = getLines(obj2)
431 | 
432 |             # Cdiffs
433 |             file_summary = cdiffLines(fromLines, toLines,
434 |                 target+'.cdiff.html', obj1, obj2, fromDate, toDate, lines)
435 | 
436 |             # Udiffs
437 |             udiffLines(fromLines, toLines, target+'.udiff.html', obj1, obj2,
438 |                        fromDate, toDate, lines)
439 | 
440 |             # Sdiffs
441 |             sdiffLines(fromLines, toLines, target+'.sdiff.html', obj1, obj2,
442 |                        True, wrapnum, lines)
443 | 
444 |             # Fdiffs
445 |             sdiffLines(fromLines, toLines, target+'.fdiff.html', obj1, obj2,
446 |                        False, wrapnum, lines)
447 | 
448 |             print '%-40s |' % f,
449 |             print 'Changed/Deleted/Added: %d/%d/%d' % (\
450 |                 file_summary['changed'],
451 |                 file_summary['deleted'],
452 |                 file_summary['added'])
453 | 
454 |             old = sourceToHtml(obj1, target + '-.html')
455 |             new = sourceToHtml(obj2, target + '.html')
456 |             data_row = _diff_data_row_template % dict(
457 |                 pathname = f,
458 |                 changed = file_summary['changed'],
459 |                 deleted = file_summary['deleted'],
460 |                 added = file_summary['added'],
461 |             )
462 |             summary['changed'] += 1
463 |         else: # this case occured when controlled by master file list
464 |             print '%-40s |' % f,
465 |             print 'Not found'
466 |             data_row = ''
467 | 
468 |         data_rows += data_row
469 | 
470 |     if not has_diff:
471 |         return False
472 | 
473 |     # Generate footer info
474 |     footer_info = _footer_info_template % dict(
475 |         time = time.strftime('%a %b %d %X %Z %Y', time.localtime()),
476 |         myname = _myname,
477 |         revision = _revision
478 |     )
479 | 
480 |     # now wirte index page
481 |     try:
482 |         index = open(os.path.join(outdir, 'index.html'), 'w')
483 |     except IOError, dig:
484 |         sys.stderr.write(str(dig) + '\n')
485 |         sys.exit(2)
486 |     index.write(_file_template % dict(
487 |         title = '%s vs %s' % (dir1, dir2),
488 |         styles = _styles,
489 |         header_info = _header_info_template % dict(dir1=dir1, dir2=dir2),
490 |         summary_info = _summary_info_template % summary,
491 |         data_rows = _data_rows_template % {'data_rows': data_rows},
492 |         legend = _legend,
493 |         footer_info = footer_info,
494 |     ))
495 |     index.close()
496 |     return True
497 | 
498 | 
499 | def warnOverwrite(pathname):
500 |     'Warnning for overwriting, return True if answered yes, False if no'
501 |     msg = "`%s' exists, are you sure you want to overwrite it (yes/no)? "
502 |     while True:
503 |         sys.stderr.write(msg % pathname)
504 |         answer = raw_input('')
505 |         if answer == 'yes':
506 |             return True
507 |         elif answer == 'no':
508 |             return False
509 |         # else: prompt again
510 | 
511 | 
512 | def isBinaryFile(file):
513 |     '''I determine a binary file by reading the first 1024 bytes of the file
514 |     and counting the non-text characters, if the number is great than 8, then
515 |     the file is considered as binary file.  This is not very reliable but is
516 |     effective'''
517 |     if not file:
518 |         return False
519 | 
520 |     non_text = 0
521 |     target_count = 8
522 |     try:
523 |         fp = open(file, 'rb')
524 |         data = fp.read(1024)
525 |         for c in data:
526 |             a = ord(c)
527 |             if a < 8 or (a > 13 and a < 32): # not printable
528 |                 non_text += 1
529 |                 if non_text >= target_count: break
530 |         fp.close()
531 |     except IOError, dig:
532 |         sys.stderr.write(str(dig) + '\n')
533 |         sys.exit(2)
534 |     return non_text >= target_count
535 | 
536 | 
537 | def cdiffToHtml(cdiff, title):
538 |     '''cdiff is context diff (a list) that generated by difflib.context_diff,
539 |     return summary and html page'''
540 |     summary = { 'changed': 0, 'added': 0, 'deleted': 0 }
541 |     line_pattern = '<span class="%s">%s</span>'
542 | 
543 |     body = ''
544 |     old_group = False
545 |     for line in cdiff:
546 |         n = len(line)
547 |         line = line.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")
548 |         if n >= 4 and line[0:4] == '*** ':
549 |             old_group = True
550 |             body += line_pattern % ('fromtitle', line)
551 |         elif n >= 4 and line[0:4] == '--- ':
552 |             old_group = False
553 |             body += line_pattern % ('totitle', line)
554 |         elif n >= 2 and line[0:2] == '  ':
555 |             body += line_pattern % ('same', line)
556 |         elif n >= 2 and line[0:2] == '! ':
557 |             body += line_pattern % ('change', line)
558 |             if old_group:
559 |                 summary['changed'] += 1
560 |         elif n >= 2 and line[0:2] == '- ':
561 |             body += line_pattern % ('delete', line)
562 |             summary['deleted'] += 1
563 |         elif n >= 2 and line[0:2] == '+ ':
564 |             body += line_pattern % ('insert', line)
565 |             summary['added'] += 1
566 |         elif n >= 15 and line[0:15] == '*' * 15:
567 |             body += '<hr>'
568 |         else: # shouldn't happen
569 |             body += line
570 | 
571 |     html = '''<html><head>
572 |         <title>%s</title>
573 |         <style type="text/css">
574 |             .fromtitle {color:brown; font:bold 11pt;}
575 |             .totitle {color:green; font:bold 11pt;}
576 |             .same {color:black; font:9pt;}
577 |             .change {color:blue; font:9pt;}
578 |             .delete {color:brown; font:9pt;}
579 |             .insert {color:green; font:9pt;}
580 |         </style>
581 |         <body>
582 |             <pre>%s</pre>
583 |         </body>
584 |         </head></html>''' % (title, body)
585 |     return summary, html
586 | 
587 | 
588 | def udiffToHtml(udiff, title):
589 |     '''udiff is uniform diff (a list) that generated by difflib.uniform_diff,
590 |     return html page'''
591 | 
592 |     line_pattern = '<span class="%s">%s</span>'
593 |     body = ''
594 |     for line in udiff:
595 |         n = len(line)
596 |         line = line.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")
597 |         if n >= 4 and line[0:4] == '--- ':
598 |             body += line_pattern % ('fromtitle', line)
599 |         elif n >= 4 and line[0:4] == '+++ ':
600 |             body += line_pattern % ('totitle', line)
601 |         elif n >= 1 and line[0] == ' ':
602 |             body += line_pattern % ('same', line)
603 |         elif n >= 1 and line[0] == '-':
604 |             body += line_pattern % ('old', line)
605 |         elif n >= 1 and line[0] == '+':
606 |             body += line_pattern % ('new', line)
607 |         elif n >= 4 and line[0:4] == '@@ -':
608 |             body += '<hr>'
609 |             body += line_pattern % ('head', line)
610 |         else: # shouldn't happen
611 |             body += line
612 | 
613 |     html = '''<html><head>
614 |         <title>%s</title>
615 |         <style type="text/css">
616 |             .fromtitle {color:brown; font:bold 11pt;}
617 |             .totitle {color:green; font:bold 11pt;}
618 |             .head {color:blue; font:bold 9pt;}
619 |             .same {color:black; font:9pt;}
620 |             .old {color:brown; font:9pt;}
621 |             .new {color:green; font:9pt;}
622 |         </style>
623 |         <body>
624 |             <pre>%s</pre>
625 |         </body>
626 |         </head></html>''' % (title, body)
627 |     return html
628 | 
629 | 
630 | def stripPrefix(name, p=0):
631 |     '''strip NUM slashes, like patch(1) -pNUM
632 |     eg1: /foo/bar/a/b/x.c
633 |     -p0 gives orignal name (no change)
634 |     -p1 gives foo/bar/a/b/x.c
635 |     -p2 gives bar/a/b/x.c
636 |     -p9 gives x.c
637 | 
638 |     eg2: foo/bar/a/b/x.c
639 |     -p0 gives orignal name (no change)
640 |     -p1 gives bar/a/b/x.c
641 |     -p2 gives a/b/x.c
642 |     -p9 gives x.c
643 | 
644 |     eg3: ./foo/bar/a/b/x.c
645 |     -p0 gives orignal name (no change)
646 |     -p1 gives foo/bar/a/b/x.c
647 |     -p2 gives bar/a/b/x.c
648 |     -p9 gives x.c
649 |     '''
650 |     cur = 0
651 |     tail = len(name) - 1
652 |     while p > 0:
653 |         index = name.find('/', cur)
654 |         #print 'p:', p, 'cur:', cur, 'index:', index
655 |         if index == -1: break
656 |         while index <= tail and name[index] == '/':
657 |             index += 1
658 |         cur = index
659 |         p -= 1
660 |     return name[cur:]
661 | 
662 | 
663 | def readFileList(file, p=0):
664 |     '''Return content of filelist (a file, each line is a filename)'''
665 |     flist = []
666 |     if not file:
667 |         return []
668 |     if file == '-':  # read from stdin
669 |         flist = sys.stdin.readlines()
670 |     else:
671 |         try:
672 |             fp = open(file, 'r')
673 |             flist = fp.readlines()
674 |             fp.close()
675 |         except IOError, dig:
676 |             sys.stderr.write(str(dig) + '\n')
677 |             sys.exit(2)
678 | 
679 |     outlist = []
680 |     for i in flist:
681 |         outlist.append(stripPrefix(i, p).rstrip())
682 |     #print outlist
683 |     return outlist
684 | 
685 | 
686 | if __name__ == '__main__':
687 |     import optparse
688 | 
689 |     usage = '''
690 |     %(name)s [options] OLD NEW
691 |     %(name)s OLD NEW [options]
692 | 
693 |     Diff two files/directories and produce HTML pages.''' % \
694 |     {'name': os.path.basename(sys.argv[0])}
695 | 
696 |     parser = optparse.OptionParser(usage)
697 |     parser.add_option('-o', '--outupt', dest='output',
698 |                       help='specify output file or directory name')
699 |     parser.add_option('-c', '--context', action='store_true',
700 |                       dest='context', default=False,
701 |                       help='generate context diff (default is full diff),' + \
702 |                            ' only take effect when diffing two files')
703 |     parser.add_option('-f', '--filelist', dest='filelist', metavar='FILE',
704 |                       help='specify a file list to read from, filelist can ' + \
705 |                            'be generated by find -type f, specify - to read' + \
706 |                            ' from stdin')
707 |     parser.add_option('-p', '--striplevel', dest='striplevel',
708 |                       type='int', metavar='NUM',
709 |                       help='for all pathnames in the filelist, delete NUM ' + \
710 |                            'path name components from the beginning of each' + \
711 |                            ' path name, it is similar to patch(1) -p')
712 |     parser.add_option('-n', '--lines', dest='lines',
713 |                       type='int', metavar='NUM', default=3,
714 |                       help='specify context line count when generating ' + \
715 |                            'context diffs or unified diffs, default is 3')
716 |     parser.add_option('-w', '--wrap', dest='wrapnum',
717 |                       type='int', metavar='WIDTH',
718 |                       help='specify column number where lines are broken ' + \
719 |                       'and wrapped for sdiff, default is no line wrapping')
720 |     parser.add_option('-y', '--yes', action='store_true',
721 |                       dest='overwrite', default=False,
722 |                       help='do not prompt for overwriting')
723 | 
724 |     opts, args = parser.parse_args()
725 | 
726 |     if len(args) != 2:
727 |         sys.stderr.write("Sorry, you must specify two file/directory names\n" \
728 |                          + "type `%s -h' for help\n" % _myname)
729 |         sys.exit(1)
730 | 
731 |     if not opts.output:
732 |         sys.stderr.write("Sorry, you must specify output name (use `-o')\n")
733 |         sys.exit(2)
734 | 
735 |     try:
736 |         # Note: use stat instead lstat to permit symbolic links
737 |         stat1 = os.stat(args[0])[0]
738 |         stat2 = os.stat(args[1])[0]
739 |     except OSError, dig:
740 |         sys.stderr.write(str(dig) + '\n')
741 |         sys.exit(2)
742 | 
743 |     # Compare two files
744 |     #
745 |     if stat.S_ISREG(stat1) and stat.S_ISREG(stat2):
746 |         if not opts.overwrite and os.path.exists(opts.output):
747 |             if not warnOverwrite(opts.output): sys.exit(1)
748 |         url = 'file://%s\n' % os.path.realpath(opts.output)
749 | 
750 |         if filecmp.cmp(args[0], arg2[1]):
751 |             print 'No difference found.'
752 |         else:
753 |             diffFile(args[0], args[1], opts.output, opts.context,
754 |                     opts.wrapnum, opts.lines)
755 |             print '\nURL:\n%s' % url
756 | 
757 |     # Compare two dirs
758 |     #
759 |     elif stat.S_ISDIR(stat1) and stat.S_ISDIR(stat2):
760 |         if not opts.overwrite and os.path.exists(opts.output):
761 |             if opts.filelist == '-':
762 |                 # stdin redirected, so we cannot read answer from stdin
763 |                 print "`%s' exists, please select another output directory, " \
764 |                       "or specify '-y' to force overwriting." % opts.output
765 |                 sys.exit(1)
766 |             else:
767 |                 if not warnOverwrite(opts.output):
768 |                     sys.exit(1)
769 |         url = 'file://%s/index.html\n' % os.path.realpath(opts.output)
770 | 
771 |         if opts.filelist:
772 |             # filelist mode, contolled by master file list
773 |             # read file list, ignore lines with first char is '/'
774 |             # if see 'foo/bar/abc', makedirs foo/bar/, then put abc.diffs to it
775 |             flist = readFileList(opts.filelist, opts.striplevel)
776 |             if diffDirByList(args[0], args[1], opts.output,
777 |                              flist, opts.wrapnum, opts.lines):
778 |                 print '\nURL:\n%s' % url
779 |             else:
780 |                 print 'No difference found.'
781 |         else:
782 |             if diffDir(args[0], args[1], opts.output, opts.wrapnum,
783 |                        opts.lines):
784 |                 print '\nURL:\n%s' % url
785 |             else:
786 |                 print 'No difference found.'
787 | 
788 |     else:
789 |         sys.stderr.write("Sorry, I don't know how to compare `%s' and `%s'\n" \
790 |                          % (args[0], args[1]))
791 |         sys.exit(2)
792 | 
793 | # vim:set et sts=4 sw=4:
794 | 
-------------------------------------------------------------------------------- /demo/codediff.py.sdiff.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |

coderev-0.1/codediff.py
coderev-0.2/codediff.py
3# Homepage: http://code.google.com/p/coderev3# Homepage: http://code.google.com/p/coderev
4# License: GPLv2, see "COPYING"4# License: GPLv2, see "COPYING"
5#5#
n6# $Id: codediff.py 3 2008-08-19 04:06:17Z mattwyl $n6# $Id: codediff.py 9 2008-08-21 14:08:53Z mattwyl $
77
8'''Diff two files/directories and produce HTML pages.'''8'''Diff two files/directories and produce HTML pages.'''
99
10import sys, os, stat, errno, time, re, difflib, filecmp10import sys, os, stat, errno, time, re, difflib, filecmp
1111
12_myname = os.path.basename(sys.argv[0])12_myname = os.path.basename(sys.argv[0])
n13_revision = '$Revision: 3 $'.split()[1]n13_revision = '$Revision: 9 $'.split()[1]
1414
1515
16########## globals & templates begin ##########16########## globals & templates begin ##########
148        Generated by %(myname)s r%(revision)s at %(time)s148        Generated by %(myname)s r%(revision)s at %(time)s
149    </i>"""149    </i>"""
150150
n151_global_dir_ignore_list = [n151_global_dir_ignore_list = (
152    '\bCVS$',152    r'^CVS$',
153    '\bSCCS$',153    r'^SCCS$',
154    '\b\\.svn$',154    r'^\.svn$',
155]155)
156156
n157_global_file_ignore_list = [n157_global_file_ignore_list = (
158    '.*\\.o$',158    r'.*\.o$',
159    '.*\\.swp$',159    r'.*\.swp$',
160    '.*\\.bak$',160    r'.*\.bak$',
161    '.*\\.old$',161    r'.*\.old$',
162    '.*\\~$',162    r'.*~$',
163    '\\.cvsignore$',163    r'^\.cvsignore$',
164]164)
165########## globals & templates end ##########165########## globals & templates end ##########
166166
167167
285    plen = len(prefix)285    plen = len(prefix)
286286
287    for root, dirs, files in os.walk(dir):287    for root, dirs, files in os.walk(dir):
t288        for d in dirs:t288        for d in [k for k in dirs]:
289            if inIgnoreList(d, _global_dir_ignore_list):289            if inIgnoreList(d, _global_dir_ignore_list):
290                dirs.remove(d)290                dirs.remove(d)
291        for f in files:291        for f in files:
80 | 81 | 82 | 88 | 94 |
Legends
83 | 84 | 85 | 86 | 87 |
Colors
 Added 
Changed
Deleted
89 | 90 | 91 | 92 | 93 |
Links
(f)irst change
(n)ext change
(t)op
95 | 96 | 97 | -------------------------------------------------------------------------------- /demo/codediff.py.udiff.html: -------------------------------------------------------------------------------- 1 | 2 | Udiff of coderev-0.1/codediff.py and coderev-0.2/codediff.py 3 | 11 | 12 |
--- coderev-0.1/codediff.py Sat Aug 23 17:05:39 2008
13 | +++ coderev-0.2/codediff.py Sat Aug 23 17:07:26 2008
14 | 
@@ -3,14 +3,14 @@ 15 | # Homepage: http://code.google.com/p/coderev 16 | # License: GPLv2, see "COPYING" 17 | # 18 | -# $Id: codediff.py 3 2008-08-19 04:06:17Z mattwyl $ 19 | +# $Id: codediff.py 9 2008-08-21 14:08:53Z mattwyl $ 20 | 21 | '''Diff two files/directories and produce HTML pages.''' 22 | 23 | import sys, os, stat, errno, time, re, difflib, filecmp 24 | 25 | _myname = os.path.basename(sys.argv[0]) 26 | -_revision = '$Revision: 3 $'.split()[1] 27 | +_revision = '$Revision: 9 $'.split()[1] 28 | 29 | 30 | ########## globals & templates begin ########## 31 |
@@ -148,20 +148,20 @@ 32 | Generated by %(myname)s r%(revision)s at %(time)s 33 | </i>""" 34 | 35 | -_global_dir_ignore_list = [ 36 | - '\bCVS$', 37 | - '\bSCCS$', 38 | - '\b\\.svn$', 39 | -] 40 | - 41 | -_global_file_ignore_list = [ 42 | - '.*\\.o$', 43 | - '.*\\.swp$', 44 | - '.*\\.bak$', 45 | - '.*\\.old$', 46 | - '.*\\~$', 47 | - '\\.cvsignore$', 48 | -] 49 | +_global_dir_ignore_list = ( 50 | + r'^CVS$', 51 | + r'^SCCS$', 52 | + r'^\.svn$', 53 | +) 54 | + 55 | +_global_file_ignore_list = ( 56 | + r'.*\.o$', 57 | + r'.*\.swp$', 58 | + r'.*\.bak$', 59 | + r'.*\.old$', 60 | + r'.*~$', 61 | + r'^\.cvsignore$', 62 | +) 63 | ########## globals & templates end ########## 64 | 65 | 66 |
@@ -285,7 +285,7 @@ 67 | plen = len(prefix) 68 | 69 | for root, dirs, files in os.walk(dir): 70 | - for d in dirs: 71 | + for d in [k for k in dirs]: 72 | if inIgnoreList(d, _global_dir_ignore_list): 73 | dirs.remove(d) 74 | for f in files: 75 |
76 | 77 | -------------------------------------------------------------------------------- /demo/coderev.sh-.html: -------------------------------------------------------------------------------- 1 | coderev-0.1/coderev.sh
#!/bin/bash
  2 | #
  3 | # Homepage: http://code.google.com/p/coderev
  4 | # License: GPLv2, see "COPYING"
  5 | #
  6 | # Generate code review page of <workspace> vs <workspace>@HEAD, by using
  7 | # `codediff.py' - a standalone diff tool
  8 | #
  9 | # Usage: coderev.sh [file|subdir ...]
 10 | #
 11 | # $Id: coderev.sh 4 2008-08-19 05:24:24Z mattwyl $
 12 | 
 13 | PROG_NAME=$(basename $0)
 14 | 
 15 | function help
 16 | {
 17 |     cat << EOF
 18 | 
 19 | Usage:
 20 |     $PROG_NAME [-r revsion] [file|subdir ...]
 21 | 
 22 |     \`revision' is a revision number, or symbol (PREV, BASE, HEAD), see svn
 23 |     books for details.  Default revision is revision of your working copy
 24 |     (aka. BASE)
 25 | 
 26 |     Default \`subdir' is working dir.
 27 | 
 28 | Example 1:
 29 |     $PROG_NAME bin lib
 30 | 
 31 |     Generate coderev based on your working copy.  If you are working on the
 32 |     most up-to-date version, this is suggested way (faster).
 33 | 
 34 | Example 2:
 35 |     $PROG_NAME -r HEAD bin lib
 36 | 
 37 |     Generate coderev based on HEAD revision (up-to-date version in repository),
 38 |     this will retrive diffs from SVN server so it's slower, but most safely.
 39 | 
 40 | EOF
 41 | 
 42 |     return 0
 43 | }
 44 | 
 45 | while getopts "r:h" op; do
 46 |     case $op in
 47 |         r) REV="$OPTARG" ;;
 48 |         h) help; exit 0 ;;
 49 |         ?) help; exit 1 ;;
 50 |     esac
 51 | done
 52 | 
 53 | shift $((OPTIND - 1))
 54 | SUBDIRS="$@"
 55 | 
 56 | [[ -n "$REV" ]] && SVN_OPT="-r $REV"
 57 | 
 58 | # Get codediff path
 59 | #
 60 | BINDIR=$(cd $(dirname $0) && pwd -L) || exit 1
 61 | CODEDIFF=$BINDIR/codediff.py
 62 | 
 63 | # Retrive SVN information
 64 | #
 65 | echo "Retriving SVN information ..."
 66 | URL=$(svn info | grep '^URL:' | awk '{print $2}') || exit 1
 67 | WS_NAME=$(basename "$URL")
 68 | WS_REV=$(svn info | grep 'Revision:' | awk '{print $2}') || exit 1
 69 | BASE_REV=$(svn info $SVN_OPT | grep 'Revision:' | awk '{print $2}') || exit 1
 70 | echo "URL     : $URL"
 71 | echo "WS_REV  : $WS_REV"
 72 | echo "BASE_REV: $BASE_REV"
 73 | 
 74 | 
 75 | # Prepare file list and base source
 76 | #
 77 | LIST=$(mktemp /tmp/list.XXXXXX) || exit 1
 78 | DIFF=$(mktemp /tmp/diff.XXXXXX) || exit 1
 79 | BASE_SRC="/tmp/${WS_NAME}@${BASE_REV}"
 80 | 
 81 | for file in $(svn st $SUBDIRS | grep '^[A-Z]' | awk '{print $2}'); do
 82 |     [[ -d $file ]] && continue
 83 |     echo $file >> $LIST || exit 1
 84 | done
 85 | 
 86 | echo "Active file list:"
 87 | echo "============================"
 88 | cat $LIST
 89 | echo "============================"
 90 | 
 91 | # Generate $base_src
 92 | #
 93 | mkdir -p $BASE_SRC || exit 1
 94 | tar -cf - $(cat $LIST) | tar -C $BASE_SRC -xf - || exit 1
 95 | 
 96 | echo "Retriving diffs ..."
 97 | svn diff $SVN_OPT $(cat $LIST) > $DIFF || exit 1
 98 | cat $DIFF | patch -NER -p0 -d $BASE_SRC || exit 1
 99 | 
100 | # Generate coderev
101 | #
102 | CODEREV=/tmp/${WS_NAME}-diff-${BASE_REV}
103 | cat $LIST | $CODEDIFF -o $CODEREV -w80 -y -f- $BASE_SRC . || exit 1
104 | 
105 | echo
106 | echo "Coderev generated under $CODEREV"
107 | echo
108 | 
109 | # Cleanup
110 | #
111 | rm -rf $LIST $DIFF $BASE_SRC
112 | 
113 | 
114 | ##############################################################################
115 | #
116 | # Customize your webdir to save coderev:
117 | #
118 | # 1. define WEBHOST, SSH_USER, HOST_DIR and WEBDIR
119 | # 2. Comment out the line ":<< \__copy_to_webserver__" below
120 | #
121 | ##############################################################################
122 | 
123 | : << __copy_to_webserver__
124 | 
125 | WEBHOST=example.org
126 | SSH_USER=me
127 | HOST_DIR='~/public_html/coderev'
128 | WEBDIR="http://$WEBHOST/~$SSH_USER/coderev"
129 | 
130 | scp -r $CODEREV ${SSH_USER}@${WEBHOST}:$HOST_DIR/ || exit 1
131 | 
132 | echo
133 | echo "Coderev link:"
134 | echo "$WEBDIR/$(basename $CODEREV)"
135 | echo
136 | 
137 | exit 0
138 | 
139 | __copy_to_webserver__
140 | 
-------------------------------------------------------------------------------- /demo/coderev.sh.cdiff.html: -------------------------------------------------------------------------------- 1 | 2 | Cdiff of coderev-0.1/coderev.sh and coderev-0.2/coderev.sh 3 | 11 | 12 |
*** coderev-0.1/coderev.sh Sat Aug 23 17:05:39 2008
 13 | --- coderev-0.2/coderev.sh Sat Aug 23 17:07:26 2008
 14 | 
*** 6,37 **** 15 | # Generate code review page of <workspace> vs <workspace>@HEAD, by using 16 | # `codediff.py' - a standalone diff tool 17 | # 18 | ! # Usage: coderev.sh [file|subdir ...] 19 | # 20 | ! # $Id: coderev.sh 4 2008-08-19 05:24:24Z mattwyl $ 21 | 22 | PROG_NAME=$(basename $0) 23 | 24 | function help 25 | { 26 | cat << EOF 27 | 28 | Usage: 29 | ! $PROG_NAME [-r revsion] [file|subdir ...] 30 | 31 | ! \`revision' is a revision number, or symbol (PREV, BASE, HEAD), see svn 32 | ! books for details. Default revision is revision of your working copy 33 | ! (aka. BASE) 34 | 35 | Default \`subdir' is working dir. 36 | 37 | Example 1: 38 | ! $PROG_NAME bin lib 39 | 40 | ! Generate coderev based on your working copy. If you are working on the 41 | ! most up-to-date version, this is suggested way (faster). 42 | 43 | ! Example 2: 44 | $PROG_NAME -r HEAD bin lib 45 | 46 | Generate coderev based on HEAD revision (up-to-date version in repository), 47 | --- 6,42 ---- 48 | # Generate code review page of <workspace> vs <workspace>@HEAD, by using 49 | # `codediff.py' - a standalone diff tool 50 | # 51 | ! # Usage: cd your-workspace; coderev.sh [file|subdir ...] 52 | # 53 | ! # $Id: coderev.sh 10 2008-08-23 09:02:26Z mattwyl $ 54 | 55 | PROG_NAME=$(basename $0) 56 | + BINDIR=$(cd $(dirname $0) && pwd -L) || exit 1 57 | + CODEDIFF=$BINDIR/codediff.py 58 | 59 | function help 60 | { 61 | cat << EOF 62 | 63 | Usage: 64 | ! $PROG_NAME [-r revsion] [-w width] [file|subdir ...] 65 | 66 | ! \`revision' is a revision number, or symbol (PREV, BASE, HEAD) in svn, 67 | ! see svn books for details. Default revision is revision of your working 68 | ! copy 69 | ! 70 | ! \`width' is a number to make code review pages wrap in specific column 71 | 72 | Default \`subdir' is working dir. 73 | 74 | Example 1: 75 | ! $PROG_NAME -w80 bin lib 76 | 77 | ! Generate coderev based on your working copy, web pages wrap in column 80. 78 | ! If you are working on the most up-to-date version, this is suggested way 79 | ! (faster). 80 | 81 | ! Example 2 (for SVN): 82 | $PROG_NAME -r HEAD bin lib 83 | 84 | Generate coderev based on HEAD revision (up-to-date version in repository), 85 |
*** 42,139 **** 86 | return 0 87 | } 88 | 89 | ! while getopts "r:h" op; do 90 | case $op in 91 | - r) REV="$OPTARG" ;; 92 | h) help; exit 0 ;; 93 | ?) help; exit 1 ;; 94 | esac 95 | done 96 | 97 | ! shift $((OPTIND - 1)) 98 | ! SUBDIRS="$@" 99 | 100 | ! [[ -n "$REV" ]] && SVN_OPT="-r $REV" 101 | ! 102 | ! # Get codediff path 103 | # 104 | ! BINDIR=$(cd $(dirname $0) && pwd -L) || exit 1 105 | ! CODEDIFF=$BINDIR/codediff.py 106 | ! 107 | ! # Retrive SVN information 108 | ! # 109 | ! echo "Retriving SVN information ..." 110 | ! URL=$(svn info | grep '^URL:' | awk '{print $2}') || exit 1 111 | ! WS_NAME=$(basename "$URL") 112 | ! WS_REV=$(svn info | grep 'Revision:' | awk '{print $2}') || exit 1 113 | ! BASE_REV=$(svn info $SVN_OPT | grep 'Revision:' | awk '{print $2}') || exit 1 114 | ! echo "URL : $URL" 115 | ! echo "WS_REV : $WS_REV" 116 | ! echo "BASE_REV: $BASE_REV" 117 | ! 118 | 119 | # Prepare file list and base source 120 | # 121 | ! LIST=$(mktemp /tmp/list.XXXXXX) || exit 1 122 | ! DIFF=$(mktemp /tmp/diff.XXXXXX) || exit 1 123 | ! BASE_SRC="/tmp/${WS_NAME}@${BASE_REV}" 124 | 125 | ! for file in $(svn st $SUBDIRS | grep '^[A-Z]' | awk '{print $2}'); do 126 | ! [[ -d $file ]] && continue 127 | ! echo $file >> $LIST || exit 1 128 | ! done 129 | 130 | ! echo "Active file list:" 131 | ! echo "============================" 132 | ! cat $LIST 133 | ! echo "============================" 134 | ! 135 | ! # Generate $base_src 136 | # 137 | mkdir -p $BASE_SRC || exit 1 138 | tar -cf - $(cat $LIST) | tar -C $BASE_SRC -xf - || exit 1 139 | 140 | echo "Retriving diffs ..." 141 | ! svn diff $SVN_OPT $(cat $LIST) > $DIFF || exit 1 142 | ! cat $DIFF | patch -NER -p0 -d $BASE_SRC || exit 1 143 | 144 | # Generate coderev 145 | # 146 | ! CODEREV=/tmp/${WS_NAME}-diff-${BASE_REV} 147 | ! cat $LIST | $CODEDIFF -o $CODEREV -w80 -y -f- $BASE_SRC . || exit 1 148 | ! 149 | ! echo 150 | ! echo "Coderev generated under $CODEREV" 151 | echo 152 | 153 | # Cleanup 154 | # 155 | rm -rf $LIST $DIFF $BASE_SRC 156 | 157 | 158 | ! ############################################################################## 159 | ! # 160 | ! # Customize your webdir to save coderev: 161 | ! # 162 | ! # 1. define WEBHOST, SSH_USER, HOST_DIR and WEBDIR 163 | ! # 2. Comment out the line ":<< \__copy_to_webserver__" below 164 | ! # 165 | ! ############################################################################## 166 | 167 | ! : << __copy_to_webserver__ 168 | 169 | ! WEBHOST=example.org 170 | ! SSH_USER=me 171 | ! HOST_DIR='~/public_html/coderev' 172 | ! WEBDIR="http://$WEBHOST/~$SSH_USER/coderev" 173 | ! 174 | ! scp -r $CODEREV ${SSH_USER}@${WEBHOST}:$HOST_DIR/ || exit 1 175 | 176 | echo 177 | echo "Coderev link:" 178 | ! echo "$WEBDIR/$(basename $CODEREV)" 179 | echo 180 | 181 | exit 0 182 | - 183 | - __copy_to_webserver__ 184 | --- 47,196 ---- 185 | return 0 186 | } 187 | 188 | ! #################### VCS Operations Begin #################### 189 | ! 190 | ! # Return code: 0 - Unknown, 1 - SVN, 2 - CVS 191 | ! # 192 | ! function detect_vcs 193 | ! { 194 | ! [[ -f .svn/entries ]] && return 1 195 | ! [[ -f CVS/Entries ]] && return 2 196 | ! return 0 197 | ! } 198 | ! 199 | ! function set_vcs_ops 200 | ! { 201 | ! local i=${1?} 202 | ! local vcs_opt=${VCS_OPS_TABLE[i]} 203 | ! 204 | ! eval vcs_get_banner=\${$vcs_opt[0]} 205 | ! eval vcs_get_repository=\${$vcs_opt[1]} 206 | ! eval vcs_get_project_path=\${$vcs_opt[2]} 207 | ! eval vcs_get_working_revision=\${$vcs_opt[3]} 208 | ! eval vcs_get_active_list=\${$vcs_opt[4]} 209 | ! eval vcs_get_diff=\${$vcs_opt[5]} 210 | ! eval vcs_get_diff_opt=\${$vcs_opt[6]} 211 | ! } 212 | ! 213 | ! # VCS Operations: 214 | ! # get_banner - print banner, return 1 if not supported 215 | ! # get_repository - print repository 216 | ! # get_project_path - print project path without repository 217 | ! # get_working_revision pathname ... - print working revision 218 | ! # get_active_list pathname ... - print active file list 219 | ! # get_diff [diff_opt] pathname ... - get diffs for active files 220 | ! # get_diff_opt - print diff option and args 221 | ! 222 | ! # Unknown ops just defined here, others see libxxx.sh 223 | ! # 224 | ! UNKNOWN_OPS=( unknown_get_banner : : : : : : ) 225 | ! 226 | ! function unknown_get_banner 227 | ! { 228 | ! echo "unknown" 229 | ! return 1 230 | ! } 231 | ! 232 | ! VCS_OPS_TABLE=( UNKNOWN_OPS SVN_OPS CVS_OPS ) 233 | ! 234 | ! . $BINDIR/libsvn.sh || exit 1 235 | ! . $BINDIR/libcvs.sh || exit 1 236 | ! 237 | ! # Detect VCS (Version Control System) and set handler 238 | ! # 239 | ! detect_vcs 240 | ! set_vcs_ops $? 241 | ! 242 | ! #################### VCS Operations End #################### 243 | ! 244 | ! # Main Proc 245 | ! # 246 | ! while getopts "hr:w:" op; do 247 | case $op in 248 | h) help; exit 0 ;; 249 | + r) REV_ARG="$OPTARG" ;; 250 | + w) CODEDIFF_OPT="-w $OPTARG" ;; 251 | ?) help; exit 1 ;; 252 | esac 253 | done 254 | + shift $((OPTIND - 1)) 255 | + PATHNAME="${@:-.}" 256 | 257 | ! BANNER=$($vcs_get_banner) || { 258 | ! echo "Unsupported version control system ($BANNER)." >&2 259 | ! exit 1 260 | ! } 261 | ! echo "Version control system \"$BANNER\" detected." 262 | 263 | ! # Retrive information 264 | # 265 | ! echo "Retriving information ..." 266 | ! PROJ_PATH=$($vcs_get_project_path) 267 | ! WS_NAME=$(basename $PROJ_PATH) 268 | ! WS_REV=$($vcs_get_working_revision $PATHNAME) 269 | ! echo "Repository : $($vcs_get_repository)" 270 | ! echo "Project path : $PROJ_PATH" 271 | ! echo "Working revision : $WS_REV" 272 | 273 | # Prepare file list and base source 274 | # 275 | ! TMPDIR=$(mktemp -d /tmp/coderev.XXXXXX) || exit 1 276 | ! LIST="$TMPDIR/activelist" 277 | ! DIFF="$TMPDIR/diffs" 278 | ! BASE_SRC="$TMPDIR/$WS_NAME-base" 279 | 280 | ! $vcs_get_active_list $PATHNAME > $LIST || exit 1 281 | ! echo "========== Active file list ==========" 282 | ! cat $LIST 283 | ! echo "========================================" 284 | 285 | ! # Generate $BASE_SRC 286 | # 287 | mkdir -p $BASE_SRC || exit 1 288 | tar -cf - $(cat $LIST) | tar -C $BASE_SRC -xf - || exit 1 289 | 290 | echo "Retriving diffs ..." 291 | ! VCS_REV_OPT="" 292 | ! [[ -n $REV_ARG ]] && VCS_REV_OPT="$($vcs_get_diff_opt $REV_ARG)" 293 | ! $vcs_get_diff $VCS_REV_OPT $(cat $LIST) > $DIFF || exit 1 294 | ! patch -NER -p0 -d $BASE_SRC < $DIFF || exit 1 295 | 296 | # Generate coderev 297 | # 298 | ! echo "Generating code review ..." 299 | ! CODEREV=$TMPDIR/${WS_NAME}-r${WS_REV}-diff 300 | ! $CODEDIFF $CODEDIFF_OPT -o $CODEREV -y -f- $BASE_SRC . < $LIST || exit 1 301 | ! echo "Coderev pages generated at $CODEREV" 302 | echo 303 | 304 | # Cleanup 305 | # 306 | rm -rf $LIST $DIFF $BASE_SRC 307 | 308 | + # Copy to web host 309 | + # 310 | + [[ -r ~/.coderevrc ]] || { 311 | + echo "[*] Hint: if you want to copy coderev pages to a remote host" 312 | + echo " automatically, see coderevrc.sample" 313 | + echo 314 | + exit 0 315 | + } 316 | 317 | ! . ~/.coderevrc || { 318 | ! echo "Reading ~/.coderevrc failed." >&2 319 | ! exit 1 320 | ! } 321 | 322 | ! : ${WEB_HOST?"WEB_HOST not defined."} 323 | ! : ${SSH_USER?"SSH_USER not defined."} 324 | ! : ${HOST_DIR?"HOST_DIR not defined."} 325 | ! : ${WEB_URL?"WEB_URL not defined."} 326 | 327 | ! scp -r $CODEREV ${SSH_USER}@${WEB_HOST}:$HOST_DIR/ || exit 1 328 | 329 | echo 330 | echo "Coderev link:" 331 | ! echo "$WEB_URL/$(basename $CODEREV)" 332 | echo 333 | 334 | exit 0 335 |
336 | 337 | -------------------------------------------------------------------------------- /demo/coderev.sh.html: -------------------------------------------------------------------------------- 1 | coderev-0.2/coderev.sh
#!/bin/bash
  2 | #
  3 | # Homepage: http://code.google.com/p/coderev
  4 | # License: GPLv2, see "COPYING"
  5 | #
  6 | # Generate code review page of <workspace> vs <workspace>@HEAD, by using
  7 | # `codediff.py' - a standalone diff tool
  8 | #
  9 | # Usage: cd your-workspace; coderev.sh [file|subdir ...]
 10 | #
 11 | # $Id: coderev.sh 10 2008-08-23 09:02:26Z mattwyl $
 12 | 
 13 | PROG_NAME=$(basename $0)
 14 | BINDIR=$(cd $(dirname $0) && pwd -L) || exit 1
 15 | CODEDIFF=$BINDIR/codediff.py
 16 | 
 17 | function help
 18 | {
 19 |     cat << EOF
 20 | 
 21 | Usage:
 22 |     $PROG_NAME [-r revsion] [-w width] [file|subdir ...]
 23 | 
 24 |     \`revision' is a revision number, or symbol (PREV, BASE, HEAD) in svn,
 25 |     see svn books for details.  Default revision is revision of your working
 26 |     copy
 27 | 
 28 |     \`width' is a number to make code review pages wrap in specific column
 29 | 
 30 |     Default \`subdir' is working dir.
 31 | 
 32 | Example 1:
 33 |     $PROG_NAME -w80 bin lib
 34 | 
 35 |     Generate coderev based on your working copy, web pages wrap in column 80.
 36 |     If you are working on the most up-to-date version, this is suggested way
 37 |     (faster).
 38 | 
 39 | Example 2 (for SVN):
 40 |     $PROG_NAME -r HEAD bin lib
 41 | 
 42 |     Generate coderev based on HEAD revision (up-to-date version in repository),
 43 |     this will retrive diffs from SVN server so it's slower, but most safely.
 44 | 
 45 | EOF
 46 | 
 47 |     return 0
 48 | }
 49 | 
 50 | ####################  VCS Operations Begin #################### 
 51 | 
 52 | # Return code: 0 - Unknown, 1 - SVN, 2 - CVS
 53 | #
 54 | function detect_vcs
 55 | {
 56 |     [[ -f .svn/entries ]] && return 1
 57 |     [[ -f CVS/Entries ]] && return 2
 58 |     return 0
 59 | }
 60 | 
 61 | function set_vcs_ops
 62 | {
 63 |     local i=${1?}
 64 |     local vcs_opt=${VCS_OPS_TABLE[i]}
 65 | 
 66 |     eval vcs_get_banner=\${$vcs_opt[0]}
 67 |     eval vcs_get_repository=\${$vcs_opt[1]}
 68 |     eval vcs_get_project_path=\${$vcs_opt[2]}
 69 |     eval vcs_get_working_revision=\${$vcs_opt[3]}
 70 |     eval vcs_get_active_list=\${$vcs_opt[4]}
 71 |     eval vcs_get_diff=\${$vcs_opt[5]}
 72 |     eval vcs_get_diff_opt=\${$vcs_opt[6]}
 73 | }
 74 | 
 75 | # VCS Operations: 
 76 | #   get_banner                        - print banner, return 1 if not supported
 77 | #   get_repository                    - print repository
 78 | #   get_project_path                  - print project path without repository
 79 | #   get_working_revision pathname ... - print working revision
 80 | #   get_active_list pathname ...      - print active file list
 81 | #   get_diff [diff_opt] pathname ...  - get diffs for active files
 82 | #   get_diff_opt                      - print diff option and args
 83 | 
 84 | # Unknown ops just defined here, others see libxxx.sh
 85 | #
 86 | UNKNOWN_OPS=( unknown_get_banner : : : : : : )
 87 | 
 88 | function unknown_get_banner
 89 | {
 90 |     echo "unknown"
 91 |     return 1
 92 | }
 93 | 
 94 | VCS_OPS_TABLE=( UNKNOWN_OPS  SVN_OPS  CVS_OPS )
 95 | 
 96 | . $BINDIR/libsvn.sh || exit 1
 97 | . $BINDIR/libcvs.sh || exit 1
 98 | 
 99 | # Detect VCS (Version Control System) and set handler
100 | #
101 | detect_vcs
102 | set_vcs_ops $?
103 | 
104 | ####################  VCS Operations End #################### 
105 | 
106 | # Main Proc
107 | #
108 | while getopts "hr:w:" op; do
109 |     case $op in
110 |         h) help; exit 0 ;;
111 |         r) REV_ARG="$OPTARG" ;;
112 |         w) CODEDIFF_OPT="-w $OPTARG" ;;
113 |         ?) help; exit 1 ;;
114 |     esac
115 | done
116 | shift $((OPTIND - 1))
117 | PATHNAME="${@:-.}"
118 | 
119 | BANNER=$($vcs_get_banner) || {
120 |     echo "Unsupported version control system ($BANNER)." >&2
121 |     exit 1
122 | }
123 | echo "Version control system \"$BANNER\" detected."
124 | 
125 | # Retrive information
126 | #
127 | echo "Retriving information ..."
128 | PROJ_PATH=$($vcs_get_project_path)
129 | WS_NAME=$(basename $PROJ_PATH)
130 | WS_REV=$($vcs_get_working_revision $PATHNAME)
131 | echo "Repository       : $($vcs_get_repository)"
132 | echo "Project path     : $PROJ_PATH"
133 | echo "Working revision : $WS_REV"
134 | 
135 | # Prepare file list and base source
136 | #
137 | TMPDIR=$(mktemp -d /tmp/coderev.XXXXXX) || exit 1
138 | LIST="$TMPDIR/activelist"
139 | DIFF="$TMPDIR/diffs"
140 | BASE_SRC="$TMPDIR/$WS_NAME-base"
141 | 
142 | $vcs_get_active_list $PATHNAME > $LIST || exit 1
143 | echo "==========  Active file list  =========="
144 | cat $LIST
145 | echo "========================================"
146 | 
147 | # Generate $BASE_SRC
148 | #
149 | mkdir -p $BASE_SRC || exit 1
150 | tar -cf - $(cat $LIST) | tar -C $BASE_SRC -xf - || exit 1
151 | 
152 | echo "Retriving diffs ..."
153 | VCS_REV_OPT=""
154 | [[ -n $REV_ARG ]] && VCS_REV_OPT="$($vcs_get_diff_opt $REV_ARG)"
155 | $vcs_get_diff $VCS_REV_OPT $(cat $LIST) > $DIFF || exit 1
156 | patch -NER -p0 -d $BASE_SRC < $DIFF || exit 1
157 | 
158 | # Generate coderev
159 | #
160 | echo "Generating code review ..."
161 | CODEREV=$TMPDIR/${WS_NAME}-r${WS_REV}-diff
162 | $CODEDIFF $CODEDIFF_OPT -o $CODEREV -y -f- $BASE_SRC . < $LIST || exit 1
163 | echo "Coderev pages generated at $CODEREV"
164 | echo
165 | 
166 | # Cleanup
167 | #
168 | rm -rf $LIST $DIFF $BASE_SRC
169 | 
170 | # Copy to web host
171 | #
172 | [[ -r ~/.coderevrc ]] || {
173 |     echo "[*] Hint: if you want to copy coderev pages to a remote host"
174 |     echo "    automatically, see coderevrc.sample"
175 |     echo
176 |     exit 0
177 | }
178 | 
179 | . ~/.coderevrc || {
180 |     echo "Reading ~/.coderevrc failed." >&2
181 |     exit 1
182 | }
183 | 
184 | : ${WEB_HOST?"WEB_HOST not defined."}
185 | : ${SSH_USER?"SSH_USER not defined."}
186 | : ${HOST_DIR?"HOST_DIR not defined."}
187 | : ${WEB_URL?"WEB_URL not defined."}
188 | 
189 | scp -r $CODEREV ${SSH_USER}@${WEB_HOST}:$HOST_DIR/ || exit 1
190 | 
191 | echo
192 | echo "Coderev link:"
193 | echo "$WEB_URL/$(basename $CODEREV)"
194 | echo
195 | 
196 | exit 0
197 | 
-------------------------------------------------------------------------------- /demo/coderev.sh.udiff.html: -------------------------------------------------------------------------------- 1 | 2 | Udiff of coderev-0.1/coderev.sh and coderev-0.2/coderev.sh 3 | 11 | 12 |
--- coderev-0.1/coderev.sh Sat Aug 23 17:05:39 2008
 13 | +++ coderev-0.2/coderev.sh Sat Aug 23 17:07:26 2008
 14 | 
@@ -6,32 +6,37 @@ 15 | # Generate code review page of <workspace> vs <workspace>@HEAD, by using 16 | # `codediff.py' - a standalone diff tool 17 | # 18 | -# Usage: coderev.sh [file|subdir ...] 19 | +# Usage: cd your-workspace; coderev.sh [file|subdir ...] 20 | # 21 | -# $Id: coderev.sh 4 2008-08-19 05:24:24Z mattwyl $ 22 | +# $Id: coderev.sh 10 2008-08-23 09:02:26Z mattwyl $ 23 | 24 | PROG_NAME=$(basename $0) 25 | +BINDIR=$(cd $(dirname $0) && pwd -L) || exit 1 26 | +CODEDIFF=$BINDIR/codediff.py 27 | 28 | function help 29 | { 30 | cat << EOF 31 | 32 | Usage: 33 | - $PROG_NAME [-r revsion] [file|subdir ...] 34 | + $PROG_NAME [-r revsion] [-w width] [file|subdir ...] 35 | 36 | - \`revision' is a revision number, or symbol (PREV, BASE, HEAD), see svn 37 | - books for details. Default revision is revision of your working copy 38 | - (aka. BASE) 39 | + \`revision' is a revision number, or symbol (PREV, BASE, HEAD) in svn, 40 | + see svn books for details. Default revision is revision of your working 41 | + copy 42 | + 43 | + \`width' is a number to make code review pages wrap in specific column 44 | 45 | Default \`subdir' is working dir. 46 | 47 | Example 1: 48 | - $PROG_NAME bin lib 49 | + $PROG_NAME -w80 bin lib 50 | 51 | - Generate coderev based on your working copy. If you are working on the 52 | - most up-to-date version, this is suggested way (faster). 53 | + Generate coderev based on your working copy, web pages wrap in column 80. 54 | + If you are working on the most up-to-date version, this is suggested way 55 | + (faster). 56 | 57 | -Example 2: 58 | +Example 2 (for SVN): 59 | $PROG_NAME -r HEAD bin lib 60 | 61 | Generate coderev based on HEAD revision (up-to-date version in repository), 62 |
@@ -42,98 +47,150 @@ 63 | return 0 64 | } 65 | 66 | -while getopts "r:h" op; do 67 | +#################### VCS Operations Begin #################### 68 | + 69 | +# Return code: 0 - Unknown, 1 - SVN, 2 - CVS 70 | +# 71 | +function detect_vcs 72 | +{ 73 | + [[ -f .svn/entries ]] && return 1 74 | + [[ -f CVS/Entries ]] && return 2 75 | + return 0 76 | +} 77 | + 78 | +function set_vcs_ops 79 | +{ 80 | + local i=${1?} 81 | + local vcs_opt=${VCS_OPS_TABLE[i]} 82 | + 83 | + eval vcs_get_banner=\${$vcs_opt[0]} 84 | + eval vcs_get_repository=\${$vcs_opt[1]} 85 | + eval vcs_get_project_path=\${$vcs_opt[2]} 86 | + eval vcs_get_working_revision=\${$vcs_opt[3]} 87 | + eval vcs_get_active_list=\${$vcs_opt[4]} 88 | + eval vcs_get_diff=\${$vcs_opt[5]} 89 | + eval vcs_get_diff_opt=\${$vcs_opt[6]} 90 | +} 91 | + 92 | +# VCS Operations: 93 | +# get_banner - print banner, return 1 if not supported 94 | +# get_repository - print repository 95 | +# get_project_path - print project path without repository 96 | +# get_working_revision pathname ... - print working revision 97 | +# get_active_list pathname ... - print active file list 98 | +# get_diff [diff_opt] pathname ... - get diffs for active files 99 | +# get_diff_opt - print diff option and args 100 | + 101 | +# Unknown ops just defined here, others see libxxx.sh 102 | +# 103 | +UNKNOWN_OPS=( unknown_get_banner : : : : : : ) 104 | + 105 | +function unknown_get_banner 106 | +{ 107 | + echo "unknown" 108 | + return 1 109 | +} 110 | + 111 | +VCS_OPS_TABLE=( UNKNOWN_OPS SVN_OPS CVS_OPS ) 112 | + 113 | +. $BINDIR/libsvn.sh || exit 1 114 | +. $BINDIR/libcvs.sh || exit 1 115 | + 116 | +# Detect VCS (Version Control System) and set handler 117 | +# 118 | +detect_vcs 119 | +set_vcs_ops $? 120 | + 121 | +#################### VCS Operations End #################### 122 | + 123 | +# Main Proc 124 | +# 125 | +while getopts "hr:w:" op; do 126 | case $op in 127 | - r) REV="$OPTARG" ;; 128 | h) help; exit 0 ;; 129 | + r) REV_ARG="$OPTARG" ;; 130 | + w) CODEDIFF_OPT="-w $OPTARG" ;; 131 | ?) help; exit 1 ;; 132 | esac 133 | done 134 | +shift $((OPTIND - 1)) 135 | +PATHNAME="${@:-.}" 136 | 137 | -shift $((OPTIND - 1)) 138 | -SUBDIRS="$@" 139 | +BANNER=$($vcs_get_banner) || { 140 | + echo "Unsupported version control system ($BANNER)." >&2 141 | + exit 1 142 | +} 143 | +echo "Version control system \"$BANNER\" detected." 144 | 145 | -[[ -n "$REV" ]] && SVN_OPT="-r $REV" 146 | - 147 | -# Get codediff path 148 | +# Retrive information 149 | # 150 | -BINDIR=$(cd $(dirname $0) && pwd -L) || exit 1 151 | -CODEDIFF=$BINDIR/codediff.py 152 | - 153 | -# Retrive SVN information 154 | -# 155 | -echo "Retriving SVN information ..." 156 | -URL=$(svn info | grep '^URL:' | awk '{print $2}') || exit 1 157 | -WS_NAME=$(basename "$URL") 158 | -WS_REV=$(svn info | grep 'Revision:' | awk '{print $2}') || exit 1 159 | -BASE_REV=$(svn info $SVN_OPT | grep 'Revision:' | awk '{print $2}') || exit 1 160 | -echo "URL : $URL" 161 | -echo "WS_REV : $WS_REV" 162 | -echo "BASE_REV: $BASE_REV" 163 | - 164 | +echo "Retriving information ..." 165 | +PROJ_PATH=$($vcs_get_project_path) 166 | +WS_NAME=$(basename $PROJ_PATH) 167 | +WS_REV=$($vcs_get_working_revision $PATHNAME) 168 | +echo "Repository : $($vcs_get_repository)" 169 | +echo "Project path : $PROJ_PATH" 170 | +echo "Working revision : $WS_REV" 171 | 172 | # Prepare file list and base source 173 | # 174 | -LIST=$(mktemp /tmp/list.XXXXXX) || exit 1 175 | -DIFF=$(mktemp /tmp/diff.XXXXXX) || exit 1 176 | -BASE_SRC="/tmp/${WS_NAME}@${BASE_REV}" 177 | +TMPDIR=$(mktemp -d /tmp/coderev.XXXXXX) || exit 1 178 | +LIST="$TMPDIR/activelist" 179 | +DIFF="$TMPDIR/diffs" 180 | +BASE_SRC="$TMPDIR/$WS_NAME-base" 181 | 182 | -for file in $(svn st $SUBDIRS | grep '^[A-Z]' | awk '{print $2}'); do 183 | - [[ -d $file ]] && continue 184 | - echo $file >> $LIST || exit 1 185 | -done 186 | +$vcs_get_active_list $PATHNAME > $LIST || exit 1 187 | +echo "========== Active file list ==========" 188 | +cat $LIST 189 | +echo "========================================" 190 | 191 | -echo "Active file list:" 192 | -echo "============================" 193 | -cat $LIST 194 | -echo "============================" 195 | - 196 | -# Generate $base_src 197 | +# Generate $BASE_SRC 198 | # 199 | mkdir -p $BASE_SRC || exit 1 200 | tar -cf - $(cat $LIST) | tar -C $BASE_SRC -xf - || exit 1 201 | 202 | echo "Retriving diffs ..." 203 | -svn diff $SVN_OPT $(cat $LIST) > $DIFF || exit 1 204 | -cat $DIFF | patch -NER -p0 -d $BASE_SRC || exit 1 205 | +VCS_REV_OPT="" 206 | +[[ -n $REV_ARG ]] && VCS_REV_OPT="$($vcs_get_diff_opt $REV_ARG)" 207 | +$vcs_get_diff $VCS_REV_OPT $(cat $LIST) > $DIFF || exit 1 208 | +patch -NER -p0 -d $BASE_SRC < $DIFF || exit 1 209 | 210 | # Generate coderev 211 | # 212 | -CODEREV=/tmp/${WS_NAME}-diff-${BASE_REV} 213 | -cat $LIST | $CODEDIFF -o $CODEREV -w80 -y -f- $BASE_SRC . || exit 1 214 | - 215 | -echo 216 | -echo "Coderev generated under $CODEREV" 217 | +echo "Generating code review ..." 218 | +CODEREV=$TMPDIR/${WS_NAME}-r${WS_REV}-diff 219 | +$CODEDIFF $CODEDIFF_OPT -o $CODEREV -y -f- $BASE_SRC . < $LIST || exit 1 220 | +echo "Coderev pages generated at $CODEREV" 221 | echo 222 | 223 | # Cleanup 224 | # 225 | rm -rf $LIST $DIFF $BASE_SRC 226 | 227 | +# Copy to web host 228 | +# 229 | +[[ -r ~/.coderevrc ]] || { 230 | + echo "[*] Hint: if you want to copy coderev pages to a remote host" 231 | + echo " automatically, see coderevrc.sample" 232 | + echo 233 | + exit 0 234 | +} 235 | 236 | -############################################################################## 237 | -# 238 | -# Customize your webdir to save coderev: 239 | -# 240 | -# 1. define WEBHOST, SSH_USER, HOST_DIR and WEBDIR 241 | -# 2. Comment out the line ":<< \__copy_to_webserver__" below 242 | -# 243 | -############################################################################## 244 | +. ~/.coderevrc || { 245 | + echo "Reading ~/.coderevrc failed." >&2 246 | + exit 1 247 | +} 248 | 249 | -: << __copy_to_webserver__ 250 | +: ${WEB_HOST?"WEB_HOST not defined."} 251 | +: ${SSH_USER?"SSH_USER not defined."} 252 | +: ${HOST_DIR?"HOST_DIR not defined."} 253 | +: ${WEB_URL?"WEB_URL not defined."} 254 | 255 | -WEBHOST=example.org 256 | -SSH_USER=me 257 | -HOST_DIR='~/public_html/coderev' 258 | -WEBDIR="http://$WEBHOST/~$SSH_USER/coderev" 259 | - 260 | -scp -r $CODEREV ${SSH_USER}@${WEBHOST}:$HOST_DIR/ || exit 1 261 | +scp -r $CODEREV ${SSH_USER}@${WEB_HOST}:$HOST_DIR/ || exit 1 262 | 263 | echo 264 | echo "Coderev link:" 265 | -echo "$WEBDIR/$(basename $CODEREV)" 266 | +echo "$WEB_URL/$(basename $CODEREV)" 267 | echo 268 | 269 | exit 0 270 | - 271 | -__copy_to_webserver__ 272 |
273 | 274 | -------------------------------------------------------------------------------- /demo/coderevrc.sample.html: -------------------------------------------------------------------------------- 1 | coderev-0.2/coderevrc.sample
# Copy to ~/.coderevrc, then customize with your own choices
 2 | #
 3 | 
 4 | WEB_HOST="example.org"
 5 | SSH_USER="me"
 6 | HOST_DIR="~/public_html/coderev"
 7 | WEB_URL="http://$WEB_HOST/~$SSH_USER/coderev"
 8 | 
 9 | # (TODO) Optional
10 | EDITOR="/usr/bin/vim"
11 | 
-------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | coderev-0.1 vs coderev-0.2 10 | 11 | 33 | 34 | 35 | 36 | 37 |

coderev-0.1 vs coderev-0.2

38 | 39 |

Comments:

40 |
example comments here
41 | 42 |

Summary of file changes:

43 | 44 | 45 | 46 | 47 | 48 | 49 |
3 Changed0 Deleted3 Added

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
FilenameC/D/A SummaryDiffsSources
README 10/1/0Cdiff Udiff Sdiff FdiffOldNew
codediff.py 17/0/0Cdiff Udiff Sdiff FdiffOldNew
coderev.sh 66/3/14Cdiff Udiff Sdiff FdiffOldNew
coderevrc.sample-/-/------New
libcvs.sh-/-/------New
libsvn.sh-/-/------New
120 |
121 | 122 | 123 | Generated by codediff.py r12 at Sun Sep 07 18:06:02 CST 2008 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /demo/libcvs.sh.html: -------------------------------------------------------------------------------- 1 | coderev-0.2/libcvs.sh
# Homepage: http://code.google.com/p/coderev
 2 | # License: GPLv2, see "COPYING"
 3 | #
 4 | # This library implements cvs operations, see comments in coderev.sh
 5 | #
 6 | # $Id: libcvs.sh 10 2008-08-23 09:02:26Z mattwyl $
 7 | 
 8 | CVS_OPS=(
 9 |     cvs_get_banner
10 |     cvs_get_repository
11 |     cvs_get_project_path
12 |     cvs_get_working_revision
13 |     cvs_get_active_list
14 |     cvs_get_diff
15 |     cvs_get_diff_opt
16 | )
17 | 
18 | function cvs_get_banner
19 | {
20 |     echo "CVS"
21 |     return 0
22 | }
23 | 
24 | function cvs_get_repository
25 | {
26 |     cat CVS/Root
27 | }
28 | 
29 | function cvs_get_project_path
30 | {
31 |     cat CVS/Repository
32 | }
33 | 
34 | function cvs_get_working_revision
35 | {
36 |     # CVS doesn't have a global revision number, just fetch revision of the
37 |     # first file or "."
38 |     #
39 |     local pathname="."
40 |     [[ -n $1 ]] && [[ -z $2 ]] && pathname=$1
41 |     cvs st $pathname 2>/dev/null | grep 'Working revision:.*\.' | head -1 \
42 |         | sed 's/.*Working revision://' | awk '{print $1}'
43 | }
44 | 
45 | function cvs_get_active_list
46 | {
47 |     cvs st $@ | grep File: \
48 |         | awk '$4 != "Up-to-date" && $4 != "Unknown" {print $2}'
49 | }
50 | 
51 | function cvs_get_diff
52 | {
53 |     local op diff_opt OPTIND OPTARG
54 | 
55 |     while getopts "r:" op; do
56 |         case $op in
57 |             r) diff_opt="-r $OPTARG" ;;
58 |             ?) echo "Unknown option: -$op" >&2; exit 1;;
59 |         esac
60 |     done
61 |     shift $((OPTIND - 1))
62 | 
63 |     cvs diff -uN $diff_opt $@
64 |     return 0 # cvs diff return 1 when there're changes
65 | }
66 | 
67 | function cvs_get_diff_opt
68 | {
69 |     echo "-r $1"
70 | }
71 | 
-------------------------------------------------------------------------------- /demo/libsvn.sh.html: -------------------------------------------------------------------------------- 1 | coderev-0.2/libsvn.sh
# Homepage: http://code.google.com/p/coderev
 2 | # License: GPLv2, see "COPYING"
 3 | #
 4 | # This library implements svn operations, see comments in coderev.sh
 5 | #
 6 | # $Id: libsvn.sh 10 2008-08-23 09:02:26Z mattwyl $
 7 | 
 8 | SVN_OPS=(
 9 |     svn_get_banner
10 |     svn_get_repository
11 |     svn_get_project_path
12 |     svn_get_working_revision
13 |     svn_get_active_list
14 |     svn_get_diff
15 |     svn_get_diff_opt
16 | )
17 | 
18 | function svn_get_banner
19 | {
20 |     echo "Subversion"
21 |     return 0
22 | }
23 | 
24 | function svn_get_repository
25 | {
26 |     svn info . | grep '^Repository Root:' | cut -c18-
27 | }
28 | 
29 | function svn_get_project_path
30 | {
31 |     local root=$(svn_get_repository)
32 |     local url=$(svn info . | grep '^URL:' | cut -c6-)
33 |     echo ${url#${root}/}
34 | }
35 | 
36 | function svn_get_working_revision
37 | {
38 |     local pathname="."
39 |     [[ -n $1 ]] && [[ -z $2 ]] && pathname=$1
40 |     svn info $pathname | grep '^Revision:' | cut -c11-
41 | }
42 | 
43 | function svn_get_active_list
44 | {
45 |     svn st $@ | grep '^[A-Z]' | awk '{print $2}'
46 | }
47 | 
48 | function svn_get_diff
49 | {
50 |     local op diff_opt OPTIND OPTARG
51 | 
52 |     while getopts "r:" op; do
53 |         case $op in
54 |             r) diff_opt="-r $OPTARG" ;;
55 |             ?) echo "Unknown option: -$op" >&2; exit 1;;
56 |         esac
57 |     done
58 |     shift $((OPTIND - 1))
59 | 
60 |     svn diff $diff_opt $@ | sed '/^Property changes on:/,/^$/d' | grep -v '^$'
61 | }
62 | 
63 | function svn_get_diff_opt
64 | {
65 |     echo "-r $1"
66 | }
67 | 
-------------------------------------------------------------------------------- /libcvs.sh: -------------------------------------------------------------------------------- 1 | # Homepage: http://code.google.com/p/coderev 2 | # License: GPLv2, see "COPYING" 3 | # 4 | # This library implements cvs operations, see comments in coderev.sh 5 | # 6 | # $Id: libcvs.sh 38 2010-01-12 10:03:08Z mattwyl $ 7 | 8 | function cvs_get_banner 9 | { 10 | echo "CVS" 11 | return 0 12 | } 13 | 14 | function cvs_get_repository 15 | { 16 | cat CVS/Root 17 | } 18 | 19 | function cvs_get_project_path 20 | { 21 | cat CVS/Repository 22 | } 23 | 24 | function cvs_get_working_revision 25 | { 26 | # CVS doesn't have a global revision number, just fetch revision of the 27 | # first file or "." 28 | # 29 | local pathname="." 30 | [[ -n $1 ]] && [[ -z $2 ]] && pathname=$1 31 | cvs st $pathname 2>/dev/null | grep 'Working revision:.*\.' | head -1 \ 32 | | sed 's/.*Working revision://' | awk '{print $1}' 33 | } 34 | 35 | function cvs_get_active_list 36 | { 37 | cvs st $@ | grep File: \ 38 | | awk '$4 != "Up-to-date" && $4 != "Unknown" {print $2}' 39 | } 40 | 41 | function cvs_get_diff 42 | { 43 | local op diff_opt OPTIND OPTARG 44 | 45 | while getopts "r:" op; do 46 | case $op in 47 | r) diff_opt="-r $OPTARG" ;; 48 | ?) echo "Unknown option: -$op" >&2; exit 1;; 49 | esac 50 | done 51 | shift $((OPTIND - 1)) 52 | 53 | # patch utility sometimes fails if no context line 54 | # Issue 2 suffers by keywords in context 55 | cvs diff -U5 $diff_opt $@ 56 | return 0 # cvs diff return 1 when there're changes 57 | } 58 | 59 | function cvs_get_diff_opt 60 | { 61 | echo "-r $1" 62 | } 63 | -------------------------------------------------------------------------------- /libsvn.sh: -------------------------------------------------------------------------------- 1 | # Homepage: http://code.google.com/p/coderev 2 | # License: GPLv2, see "COPYING" 3 | # 4 | # This library implements svn operations, see comments in coderev.sh 5 | # 6 | # $Id: libsvn.sh 38 2010-01-12 10:03:08Z mattwyl $ 7 | 8 | function svn_get_banner 9 | { 10 | echo "Subversion" 11 | return 0 12 | } 13 | 14 | function svn_get_repository 15 | { 16 | svn info . | grep '^Repository Root:' | cut -c18- 17 | } 18 | 19 | function svn_get_project_path 20 | { 21 | local root=$(svn_get_repository) 22 | local url=$(svn info . | grep '^URL:' | cut -c6-) 23 | echo ${url#${root}/} 24 | } 25 | 26 | function svn_get_working_revision 27 | { 28 | local pathname="." 29 | [[ -n $1 ]] && [[ -z $2 ]] && pathname=$1 30 | svn info $pathname | grep '^Revision:' | cut -c11- 31 | } 32 | 33 | function svn_get_active_list 34 | { 35 | # svn 1.5 has 6 columns plus a space while svn 1.6+ has 7 columns 36 | svn st $@ | grep '^[A-Z]' | cut -c8- | sed 's/^ *//' 37 | } 38 | 39 | function svn_get_diff 40 | { 41 | local op diff_opt OPTIND OPTARG 42 | 43 | while getopts "r:" op; do 44 | case $op in 45 | r) diff_opt="-r $OPTARG" ;; 46 | ?) echo "Unknown option: -$op" >&2; exit 1;; 47 | esac 48 | done 49 | shift $((OPTIND - 1)) 50 | 51 | # patch utility sometimes fails if no context line 52 | # Issue 2 suffers by keywords in context 53 | svn diff --diff-cmd /usr/bin/diff -x -U5 $diff_opt $@ \ 54 | | sed '/^Property changes on:/,/^$/d' | grep -v '^$' 55 | } 56 | 57 | function svn_get_diff_opt 58 | { 59 | echo "-r $1" 60 | } 61 | --------------------------------------------------------------------------------