├── .github └── workflows │ └── release.yml ├── .gitignore ├── .travis.yml ├── ChangeLog ├── LICENSE ├── MANIFEST.in ├── README.md ├── git-icdiff ├── icdiff ├── icdiff.py ├── requirements-dev.txt ├── setup.py ├── test.sh └── tests ├── a ├── 1 ├── c │ ├── e │ └── f └── j ├── b ├── 1 ├── c │ ├── e │ ├── f │ ├── g │ └── h ├── d │ └── q └── i ├── gitdiff-only-newlines.txt ├── gold-12-subcolors.txt ├── gold-12-t.txt ├── gold-12.txt ├── gold-3.txt ├── gold-45-95.txt ├── gold-45-h-nb.txt ├── gold-45-h.txt ├── gold-45-h3.txt ├── gold-45-l.txt ├── gold-45-lbrb.txt ├── gold-45-ln-color.txt ├── gold-45-ln.txt ├── gold-45-lr.txt ├── gold-45-nb.txt ├── gold-45-nh.txt ├── gold-45-pipe.txt ├── gold-45-sas-h-nb.txt ├── gold-45-sas-h.txt ├── gold-45-sas.txt ├── gold-45.txt ├── gold-4dn.txt ├── gold-67-ln.txt ├── gold-67-u3.txt ├── gold-67-wf.txt ├── gold-67.txt ├── gold-bad-encoding.txt ├── gold-dir.txt ├── gold-dn5.txt ├── gold-exclude.txt ├── gold-exit-process-sub ├── gold-file-not-found.txt ├── gold-hide-cr-if-dos ├── gold-identical-on.txt ├── gold-no-cr-indent ├── gold-permissions-diff-binary.txt ├── gold-permissions-diff-text.txt ├── gold-permissions-diff.txt ├── gold-permissions-same.txt ├── gold-recursive-with-exclude.txt ├── gold-recursive-with-exclude2.txt ├── gold-recursive.txt ├── gold-sas.txt ├── gold-show-spaces.txt ├── gold-sns.txt ├── gold-strip-cr-off.txt ├── gold-strip-cr-on.txt ├── gold-subcolors-bad-cat ├── gold-subcolors-bad-color ├── gold-subcolors-bad-fmt ├── gold-tabs-4.txt ├── gold-tabs-default.txt ├── input-1.txt ├── input-10.txt ├── input-11.txt ├── input-2.txt ├── input-3.txt ├── input-4-cr.txt ├── input-4-partial-cr.txt ├── input-4.txt ├── input-5-cr.txt ├── input-5.txt ├── input-6.txt ├── input-7.txt ├── input-8.txt ├── input-9.txt └── test-with-exclude ├── a ├── 1 ├── c │ ├── e │ └── f ├── exclude │ └── text.txt └── j └── b ├── 1 ├── c ├── e ├── f ├── g └── h ├── d └── q ├── exclude └── text.txt └── i /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Package 2 | 3 | on: 4 | push: 5 | tags: ["release-*"] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | build_package: 12 | name: Build Package 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Build sdist and wheel 17 | run: pipx run build --sdist --wheel 18 | - uses: actions/upload-artifact@v3 19 | with: 20 | path: dist 21 | 22 | pypi-publish: 23 | needs: [build_package] 24 | name: Upload release to PyPI 25 | runs-on: ubuntu-latest 26 | environment: 27 | name: pypi 28 | permissions: 29 | id-token: write 30 | steps: 31 | - uses: actions/download-artifact@v3 32 | with: 33 | name: artifact 34 | path: dist 35 | - name: Publish package distributions to PyPI 36 | uses: pypa/gh-action-pypi-publish@release/v1 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /venv/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: required 3 | dist: xenial 4 | python: 5 | - "3.4" 6 | - "3.5" 7 | - "3.6" 8 | - "3.7" 9 | - "3.8" 10 | - "3.9" 11 | - "nightly" 12 | script: 13 | - pip install -r requirements-dev.txt 14 | - ./test.sh python 15 | git: 16 | depth: false -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2.0.7 2 | Add --show-no-spaces #173 3 | 4 | 2.0.6 5 | Fix process exit code for non-file inputs #205 6 | Make fullwidth characters take two columns #202 7 | Add -x/--exclude optin to to exclude files matching patterns #199 8 | 9 | 2.0.5 10 | Set process exit code to indicate differences #195 11 | Support -P/--permissions option #197 12 | 13 | 2.0.4 14 | Include LICENSE in package 15 | 16 | 2.0.3 17 | Attempts to publisher 2.0.1 and 2.0.2 to pypi gave broken packages 18 | 19 | 2.0.1 20 | Add -t/--truncate option #184 21 | 22 | 2.0.0 23 | Drop support for Python 2 24 | Clearer error on unknown encodings #145 25 | Stop printing an error on closed pipes #156 26 | Fix displayed filed names with git #159 27 | Fix testcase that assumed a git repo #166 28 | Implement --report-identical-files #168 29 | Improved handling of very long lines #180 30 | 31 | 1.9.4 32 | Allow {path} and {basename} in --label #139 33 | Properly implement git difftool protocol #140 34 | 35 | 1.9.3 36 | Properly set the version number 37 | 38 | 1.9.2 39 | Add --exclude-lines (-E) which can exclude comments 40 | Add --color-map so you can choose which colors to use for what #121 #117 41 | Allow highlighted characters to be bold #122 42 | Support configuring git-icdiff with gitconfig 43 | Don't choke on bad terminal sizes #113 44 | Print proper error messages instead of raising exceptions 45 | Allow the line numbers to be colorized 46 | Add a LICENSE file 47 | 48 | 1.9.1 49 | Handle files with CR characters better and add --strip-trailing-cr 50 | 51 | 1.9.0 52 | Fix setup.py by symlinking icdiff to icdiff.py 53 | 54 | 1.8.2 55 | Add short flags for --highlight (-H), --line-numbers (-N), and --whole-file (-W). 56 | Fix use with bash process substitution and other special files 57 | 58 | 1.8.1 59 | Updated remaining copy of unicode test file (b/1) 60 | 61 | 1.8.0 62 | Updated unicode test file (input-3) 63 | Allow testing installed version 64 | Allow importing as a module 65 | Minor deduplication tweak to git-icdiff 66 | Add pip instructions to readme 67 | Allow using --tabsize 68 | Allow non-recursive directory diffing 69 | 70 | 1.7.6 71 | Fixed copyright. 72 | 73 | 1.7.3 74 | Fix git-icdiff to handle filenames with spaces as arguments. 75 | 76 | 1.7.2 77 | Don't stop diffing recursively when encountering a binary file. 78 | 79 | 1.7.1 80 | Don't treat files with identical (mode, size, mtime) as equal. 81 | 82 | 1.7.0 83 | Add tests 84 | 85 | 1.6.4 86 | Unbreak --recursive again 87 | 88 | 1.6.3 89 | Stop setting LESS_IS_MORE with git-icdiff, fixing #33. 90 | 91 | 1.6.2 92 | Add support for setting the output encoding and default to utf8 93 | 94 | 1.6.1 95 | Unbreak --recursive 96 | 97 | 1.6.0 98 | Add support for setting the encoding, and handle fullwidth chars 99 | in python2 100 | 101 | 1.5.3 102 | Support use as an svn difftool. 103 | Support -U and -L, and allow but ignore -u. 104 | 105 | 1.5.2 106 | Various pager improvements in git-icdiff. 107 | 108 | 1.5.1 109 | Make --highlight and --no-bold play nice. 110 | 111 | 1.5.0 112 | Pass arguments through to icdiff when using git-icdiff. 113 | 114 | 1.4.0 115 | Use less with "git icdiff" by default. 116 | 117 | 1.3.2 118 | Fix linewrapping with unicode. 119 | 120 | 1.3.1 121 | 1.3.0 was completely borked. 122 | 123 | 1.3.0 124 | Use setup.py to support standard python installation. 125 | 126 | 1.2.2 127 | Start printing output as soon as its ready instead of waiting for 128 | the whole file to complete. 129 | 130 | 1.2.1 131 | Space fullwidth characters properly when treating input as unicode. 132 | 133 | 1.2.0 134 | Add --recursive to support diffing directory trees. 135 | 136 | 1.1.2 137 | Flush stdout when done. 138 | 139 | 1.1.1 140 | Don't print stack traces on Ctrl+C or when piping into something 141 | that quits. 142 | 143 | 1.1.0 144 | Add --no-bold option useful with the solarized colorscheme and for 145 | people who just don't like bold. 146 | 147 | 1.0.0 148 | First Release 149 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | A. HISTORY OF THE SOFTWARE 2 | ========================== 3 | 4 | Python was created in the early 1990s by Guido van Rossum at Stichting 5 | Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands 6 | as a successor of a language called ABC. Guido remains Python's 7 | principal author, although it includes many contributions from others. 8 | 9 | In 1995, Guido continued his work on Python at the Corporation for 10 | National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) 11 | in Reston, Virginia where he released several versions of the 12 | software. 13 | 14 | In May 2000, Guido and the Python core development team moved to 15 | BeOpen.com to form the BeOpen PythonLabs team. In October of the same 16 | year, the PythonLabs team moved to Digital Creations, which became 17 | Zope Corporation. In 2001, the Python Software Foundation (PSF, see 18 | https://www.python.org/psf/) was formed, a non-profit organization 19 | created specifically to own Python-related Intellectual Property. 20 | Zope Corporation was a sponsoring member of the PSF. 21 | 22 | All Python releases are Open Source (see http://www.opensource.org for 23 | the Open Source Definition). Historically, most, but not all, Python 24 | releases have also been GPL-compatible; the table below summarizes 25 | the various releases. 26 | 27 | Release Derived Year Owner GPL- 28 | from compatible? (1) 29 | 30 | 0.9.0 thru 1.2 1991-1995 CWI yes 31 | 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 32 | 1.6 1.5.2 2000 CNRI no 33 | 2.0 1.6 2000 BeOpen.com no 34 | 1.6.1 1.6 2001 CNRI yes (2) 35 | 2.1 2.0+1.6.1 2001 PSF no 36 | 2.0.1 2.0+1.6.1 2001 PSF yes 37 | 2.1.1 2.1+2.0.1 2001 PSF yes 38 | 2.1.2 2.1.1 2002 PSF yes 39 | 2.1.3 2.1.2 2002 PSF yes 40 | 2.2 and above 2.1.1 2001-now PSF yes 41 | 42 | Footnotes: 43 | 44 | (1) GPL-compatible doesn't mean that we're distributing Python under 45 | the GPL. All Python licenses, unlike the GPL, let you distribute 46 | a modified version without making your changes open source. The 47 | GPL-compatible licenses make it possible to combine Python with 48 | other software that is released under the GPL; the others don't. 49 | 50 | (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, 51 | because its license has a choice of law clause. According to 52 | CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 53 | is "not incompatible" with the GPL. 54 | 55 | Thanks to the many outside volunteers who have worked under Guido's 56 | direction to make these releases possible. 57 | 58 | 59 | B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON 60 | =============================================================== 61 | 62 | PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 63 | -------------------------------------------- 64 | 65 | 1. This LICENSE AGREEMENT is between the Python Software Foundation 66 | ("PSF"), and the Individual or Organization ("Licensee") accessing and 67 | otherwise using this software ("Python") in source or binary form and 68 | its associated documentation. 69 | 70 | 2. Subject to the terms and conditions of this License Agreement, PSF hereby 71 | grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, 72 | analyze, test, perform and/or display publicly, prepare derivative works, 73 | distribute, and otherwise use Python alone or in any derivative version, 74 | provided, however, that PSF's License Agreement and PSF's notice of copyright, 75 | i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 76 | 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All 77 | Rights Reserved" are retained in Python alone or in any derivative version 78 | prepared by Licensee. 79 | 80 | 3. In the event Licensee prepares a derivative work that is based on 81 | or incorporates Python or any part thereof, and wants to make 82 | the derivative work available to others as provided herein, then 83 | Licensee hereby agrees to include in any such work a brief summary of 84 | the changes made to Python. 85 | 86 | 4. PSF is making Python available to Licensee on an "AS IS" 87 | basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 88 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND 89 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 90 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT 91 | INFRINGE ANY THIRD PARTY RIGHTS. 92 | 93 | 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 94 | FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 95 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, 96 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 97 | 98 | 6. This License Agreement will automatically terminate upon a material 99 | breach of its terms and conditions. 100 | 101 | 7. Nothing in this License Agreement shall be deemed to create any 102 | relationship of agency, partnership, or joint venture between PSF and 103 | Licensee. This License Agreement does not grant permission to use PSF 104 | trademarks or trade name in a trademark sense to endorse or promote 105 | products or services of Licensee, or any third party. 106 | 107 | 8. By copying, installing or otherwise using Python, Licensee 108 | agrees to be bound by the terms and conditions of this License 109 | Agreement. 110 | 111 | 112 | BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 113 | ------------------------------------------- 114 | 115 | BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 116 | 117 | 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an 118 | office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the 119 | Individual or Organization ("Licensee") accessing and otherwise using 120 | this software in source or binary form and its associated 121 | documentation ("the Software"). 122 | 123 | 2. Subject to the terms and conditions of this BeOpen Python License 124 | Agreement, BeOpen hereby grants Licensee a non-exclusive, 125 | royalty-free, world-wide license to reproduce, analyze, test, perform 126 | and/or display publicly, prepare derivative works, distribute, and 127 | otherwise use the Software alone or in any derivative version, 128 | provided, however, that the BeOpen Python License is retained in the 129 | Software, alone or in any derivative version prepared by Licensee. 130 | 131 | 3. BeOpen is making the Software available to Licensee on an "AS IS" 132 | basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 133 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND 134 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 135 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT 136 | INFRINGE ANY THIRD PARTY RIGHTS. 137 | 138 | 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE 139 | SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS 140 | AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY 141 | DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 142 | 143 | 5. This License Agreement will automatically terminate upon a material 144 | breach of its terms and conditions. 145 | 146 | 6. This License Agreement shall be governed by and interpreted in all 147 | respects by the law of the State of California, excluding conflict of 148 | law provisions. Nothing in this License Agreement shall be deemed to 149 | create any relationship of agency, partnership, or joint venture 150 | between BeOpen and Licensee. This License Agreement does not grant 151 | permission to use BeOpen trademarks or trade names in a trademark 152 | sense to endorse or promote products or services of Licensee, or any 153 | third party. As an exception, the "BeOpen Python" logos available at 154 | http://www.pythonlabs.com/logos.html may be used according to the 155 | permissions granted on that web page. 156 | 157 | 7. By copying, installing or otherwise using the software, Licensee 158 | agrees to be bound by the terms and conditions of this License 159 | Agreement. 160 | 161 | 162 | CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 163 | --------------------------------------- 164 | 165 | 1. This LICENSE AGREEMENT is between the Corporation for National 166 | Research Initiatives, having an office at 1895 Preston White Drive, 167 | Reston, VA 20191 ("CNRI"), and the Individual or Organization 168 | ("Licensee") accessing and otherwise using Python 1.6.1 software in 169 | source or binary form and its associated documentation. 170 | 171 | 2. Subject to the terms and conditions of this License Agreement, CNRI 172 | hereby grants Licensee a nonexclusive, royalty-free, world-wide 173 | license to reproduce, analyze, test, perform and/or display publicly, 174 | prepare derivative works, distribute, and otherwise use Python 1.6.1 175 | alone or in any derivative version, provided, however, that CNRI's 176 | License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 177 | 1995-2001 Corporation for National Research Initiatives; All Rights 178 | Reserved" are retained in Python 1.6.1 alone or in any derivative 179 | version prepared by Licensee. Alternately, in lieu of CNRI's License 180 | Agreement, Licensee may substitute the following text (omitting the 181 | quotes): "Python 1.6.1 is made available subject to the terms and 182 | conditions in CNRI's License Agreement. This Agreement together with 183 | Python 1.6.1 may be located on the Internet using the following 184 | unique, persistent identifier (known as a handle): 1895.22/1013. This 185 | Agreement may also be obtained from a proxy server on the Internet 186 | using the following URL: http://hdl.handle.net/1895.22/1013". 187 | 188 | 3. In the event Licensee prepares a derivative work that is based on 189 | or incorporates Python 1.6.1 or any part thereof, and wants to make 190 | the derivative work available to others as provided herein, then 191 | Licensee hereby agrees to include in any such work a brief summary of 192 | the changes made to Python 1.6.1. 193 | 194 | 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" 195 | basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 196 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND 197 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 198 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT 199 | INFRINGE ANY THIRD PARTY RIGHTS. 200 | 201 | 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 202 | 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 203 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, 204 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 205 | 206 | 6. This License Agreement will automatically terminate upon a material 207 | breach of its terms and conditions. 208 | 209 | 7. This License Agreement shall be governed by the federal 210 | intellectual property law of the United States, including without 211 | limitation the federal copyright law, and, to the extent such 212 | U.S. federal law does not apply, by the law of the Commonwealth of 213 | Virginia, excluding Virginia's conflict of law provisions. 214 | Notwithstanding the foregoing, with regard to derivative works based 215 | on Python 1.6.1 that incorporate non-separable material that was 216 | previously distributed under the GNU General Public License (GPL), the 217 | law of the Commonwealth of Virginia shall govern this License 218 | Agreement only as to issues arising under or with respect to 219 | Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this 220 | License Agreement shall be deemed to create any relationship of 221 | agency, partnership, or joint venture between CNRI and Licensee. This 222 | License Agreement does not grant permission to use CNRI trademarks or 223 | trade name in a trademark sense to endorse or promote products or 224 | services of Licensee, or any third party. 225 | 226 | 8. By clicking on the "ACCEPT" button where indicated, or by copying, 227 | installing or otherwise using Python 1.6.1, Licensee agrees to be 228 | bound by the terms and conditions of this License Agreement. 229 | 230 | ACCEPT 231 | 232 | 233 | CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 234 | -------------------------------------------------- 235 | 236 | Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, 237 | The Netherlands. All rights reserved. 238 | 239 | Permission to use, copy, modify, and distribute this software and its 240 | documentation for any purpose and without fee is hereby granted, 241 | provided that the above copyright notice appear in all copies and that 242 | both that copyright notice and this permission notice appear in 243 | supporting documentation, and that the name of Stichting Mathematisch 244 | Centrum or CWI not be used in advertising or publicity pertaining to 245 | distribution of the software without specific, written prior 246 | permission. 247 | 248 | STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO 249 | THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 250 | FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE 251 | FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 252 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 253 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 254 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 255 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Icdiff 2 | 3 | Improved colored diff 4 | 5 |  6 | 7 | ## Installation 8 | 9 | Download the [latest](https://github.com/jeffkaufman/icdiff/tags) `icdiff` and put it on your PATH. 10 | 11 | Alternatively, install with packaging tools: 12 | 13 | ``` 14 | # pip 15 | pip install icdiff 16 | 17 | # apt 18 | sudo apt install icdiff 19 | 20 | # homebrew 21 | brew install icdiff 22 | 23 | # aur 24 | yay -S icdiff 25 | 26 | # nix 27 | nix-env -i icdiff 28 | ``` 29 | 30 | ## Usage 31 | 32 | ```sh 33 | icdiff [options] left_file right_file 34 | ``` 35 | 36 | Show differences between files in a two column view. 37 | 38 | ### Options 39 | 40 | ``` 41 | --version show program's version number and exit 42 | -h, --help show this help message and exit 43 | --cols=COLS specify the width of the screen. Autodetection is Unix 44 | only 45 | --encoding=ENCODING specify the file encoding; defaults to utf8 46 | -E MATCHER, --exclude-lines=MATCHER 47 | Do not diff lines that match this regex. Not 48 | compatible with the 'line-numbers' option 49 | --head=HEAD consider only the first N lines of each file 50 | -H, --highlight color by changing the background color instead of the 51 | foreground color. Very fast, ugly, displays all 52 | changes 53 | -L LABELS, --label=LABELS 54 | override file labels with arbitrary tags. Use twice, 55 | one for each file 56 | -N, --line-numbers generate output with line numbers. Not compatible with 57 | the 'exclude-lines' option. 58 | --no-bold use non-bold colors; recommended for solarized 59 | --no-headers don't label the left and right sides with their file 60 | names 61 | --output-encoding=OUTPUT_ENCODING 62 | specify the output encoding; defaults to utf8 63 | -r, --recursive recursively compare subdirectories 64 | -s, --report-identical-files 65 | report when two files are the same 66 | --show-all-spaces color all non-matching whitespace including that which 67 | is not needed for drawing the eye to changes. Slow, 68 | ugly, displays all changes 69 | --tabsize=TABSIZE tab stop spacing 70 | -t, --truncate truncate long lines instead of wrapping them 71 | -u, --patch generate patch. This is always true, and only exists 72 | for compatibility 73 | -U NUM, --unified=NUM, --numlines=NUM 74 | how many lines of context to print; can't be combined 75 | with --whole-file 76 | -W, --whole-file show the whole file instead of just changed lines and 77 | context 78 | --strip-trailing-cr strip any trailing carriage return at the end of an 79 | input line 80 | --color-map=COLOR_MAP 81 | choose which colors are used for which items. Default 82 | is --color-map='add:green_bold,change:yellow_bold,desc 83 | ription:blue,meta:magenta,separator:blue,subtract:red_ 84 | bold'. You don't have to override all of them: 85 | '--color-map=separator:white,description:cyan 86 | ``` 87 | 88 | ## Using with Git 89 | 90 | To see what it looks like, try: 91 | 92 | ```sh 93 | git difftool --extcmd icdiff 94 | ``` 95 | 96 | To install this as a tool you can use with Git, copy 97 | `git-icdiff` into your PATH and run: 98 | 99 | ```sh 100 | git icdiff 101 | ``` 102 | 103 | You can configure `git-icdiff` in Git's config: 104 | 105 | ``` 106 | git config --global icdiff.options '--highlight --line-numbers' 107 | ``` 108 | 109 | ## Using with subversion 110 | 111 | To try it out, run: 112 | 113 | ```sh 114 | svn diff --diff-cmd icdiff 115 | ``` 116 | 117 | ## Using with Mercurial 118 | 119 | Add the following to your `~/.hgrc`: 120 | 121 | ```sh 122 | [extensions] 123 | extdiff= 124 | 125 | [extdiff] 126 | cmd.icdiff=icdiff 127 | opts.icdiff=--recursive --line-numbers 128 | ``` 129 | 130 | Or check more [in-depth setup instructions](https://ianobermiller.com/blog/2016/07/14/side-by-side-diffs-for-mercurial-hg-icdiff-revisited/). 131 | 132 | ## Setting up a dev environment 133 | 134 | Create a virtualenv and install the dev dependencies. 135 | This is not needed for normal usage. 136 | 137 | ```sh 138 | virtualenv venv 139 | source venv/bin/activate 140 | pip install -r requirements-dev.txt 141 | ``` 142 | 143 | ## Running tests 144 | 145 | ```sh 146 | ./test.sh python3 147 | ``` 148 | 149 | ## Making a release 150 | 151 | - Update ChangeLog with all the changes since the last release 152 | - Update `__version__` in `icdiff` 153 | - Run tests, make sure they pass 154 | - `git commit -a -m "release ${version}"` 155 | - `git push` 156 | - `git tag release-${version}` 157 | - `git push origin release-${version}` 158 | - A GitHub Action should be triggered due to the release tag being pushed, and will upload to PyPI. 159 | 160 | ## License 161 | 162 | This file is derived from `difflib.HtmlDiff` which is under [license](https://www.python.org/download/releases/2.6.2/license/). 163 | I release my changes here under the same license. This is GPL compatible. 164 | -------------------------------------------------------------------------------- /git-icdiff: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ICDIFF_OPTIONS=$(git config --get icdiff.options) 3 | ICDIFF_OPTIONS="${ICDIFF_OPTIONS} --is-git-diff" 4 | GITPAGER=$(git config --get icdiff.pager) 5 | 6 | if [ -z "$GITPAGER" ]; then 7 | GITPAGER=$(git config --get core.pager) 8 | fi 9 | 10 | if [ -z "$GITPAGER" ]; then 11 | GITPAGER="${PAGER:-less}" 12 | fi 13 | 14 | if [ "${GITPAGER%% *}" = "more" ] || [ "${GITPAGER%% *}" = "less" ]; then 15 | GITPAGER="$GITPAGER -R" 16 | fi 17 | 18 | git difftool --no-prompt --extcmd="icdiff $ICDIFF_OPTIONS" "$@" | $GITPAGER 19 | -------------------------------------------------------------------------------- /icdiff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ icdiff.py 4 | 5 | Author: Jeff Kaufman, derived from difflib.HtmlDiff 6 | 7 | License: This code is usable under the same open terms as the rest of 8 | python. See: http://www.python.org/psf/license/ 9 | 10 | Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; 11 | All Rights Reserved 12 | 13 | Based on Python's difflib.HtmlDiff, 14 | with changes to provide console output instead of html output. """ 15 | 16 | import os 17 | import stat 18 | import sys 19 | import errno 20 | import difflib 21 | from optparse import Option, OptionParser 22 | import re 23 | import filecmp 24 | import unicodedata 25 | import codecs 26 | import fnmatch 27 | 28 | __version__ = "2.0.7" 29 | 30 | # Exit code constants 31 | EXIT_CODE_SUCCESS = 0 32 | EXIT_CODE_DIFF = 1 33 | EXIT_CODE_ERROR = 2 34 | 35 | color_codes = { 36 | "black": "\033[0;30m", 37 | "red": "\033[0;31m", 38 | "green": "\033[0;32m", 39 | "yellow": "\033[0;33m", 40 | "blue": "\033[0;34m", 41 | "magenta": "\033[0;35m", 42 | "cyan": "\033[0;36m", 43 | "white": "\033[0;37m", 44 | "none": "\033[m", 45 | "black_bold": "\033[1;30m", 46 | "red_bold": "\033[1;31m", 47 | "green_bold": "\033[1;32m", 48 | "yellow_bold": "\033[1;33m", 49 | "blue_bold": "\033[1;34m", 50 | "magenta_bold": "\033[1;35m", 51 | "cyan_bold": "\033[1;36m", 52 | "white_bold": "\033[1;37m", 53 | } 54 | 55 | 56 | color_mapping = { 57 | "add": "green_bold", 58 | "subtract": "red_bold", 59 | "change": "yellow_bold", 60 | "separator": "blue", 61 | "description": "blue", 62 | "permissions": "yellow", 63 | "meta": "magenta", 64 | "line-numbers": "white", 65 | } 66 | 67 | 68 | class ConsoleDiff(object): 69 | """Console colored side by side comparison with change highlights. 70 | 71 | Based on difflib.HtmlDiff 72 | 73 | This class can be used to create a text-mode table showing a side 74 | 75 | by side, line by line comparison of text with inter-line and 76 | intra-line change highlights in ansi color escape sequences as 77 | intra-line change highlights in ansi color escape sequences as 78 | read by xterm. The table can be generated in either full or 79 | contextual difference mode. 80 | 81 | To generate the table, call make_table. 82 | 83 | Usage is the almost the same as HtmlDiff except only make_table is 84 | implemented and the file can be invoked on the command line. 85 | Run:: 86 | 87 | python icdiff.py --help 88 | 89 | for command line usage information. 90 | 91 | """ 92 | 93 | def __init__( 94 | self, 95 | tabsize=8, 96 | wrapcolumn=None, 97 | linejunk=None, 98 | charjunk=difflib.IS_CHARACTER_JUNK, 99 | cols=80, 100 | line_numbers=False, 101 | show_all_spaces=False, 102 | show_no_spaces=False, 103 | highlight=False, 104 | truncate=False, 105 | strip_trailing_cr=False, 106 | ): 107 | """ConsoleDiff instance initializer 108 | 109 | Arguments: 110 | tabsize -- tab stop spacing, defaults to 8. 111 | wrapcolumn -- column number where lines are broken and wrapped, 112 | defaults to None where lines are not wrapped. 113 | linejunk, charjunk -- keyword arguments passed into ndiff() (used by 114 | ConsoleDiff() to generate the side by side differences). See 115 | ndiff() documentation for argument default values and descriptions. 116 | """ 117 | 118 | self._tabsize = tabsize 119 | self.line_numbers = line_numbers 120 | self.cols = cols 121 | self.show_all_spaces = show_all_spaces 122 | self.show_no_spaces = show_no_spaces 123 | self.highlight = highlight 124 | self.strip_trailing_cr = strip_trailing_cr 125 | self.truncate = truncate 126 | 127 | if wrapcolumn is None: 128 | if not line_numbers: 129 | wrapcolumn = self.cols // 2 - 2 130 | else: 131 | wrapcolumn = self.cols // 2 - 10 132 | 133 | self._wrapcolumn = wrapcolumn 134 | self._linejunk = linejunk 135 | self._charjunk = charjunk 136 | 137 | def _tab_newline_replace(self, fromlines, tolines): 138 | """Returns from/to line lists with tabs expanded and newlines removed. 139 | 140 | Instead of tab characters being replaced by the number of spaces 141 | needed to fill in to the next tab stop, this function will fill 142 | the space with tab characters. This is done so that the difference 143 | algorithms can identify changes in a file when tabs are replaced by 144 | spaces and vice versa. At the end of the table generation, the tab 145 | characters will be replaced with a space. 146 | """ 147 | 148 | def expand_tabs(line): 149 | # hide real spaces 150 | line = line.replace(" ", "\0") 151 | # expand tabs into spaces 152 | line = line.expandtabs(self._tabsize) 153 | # replace spaces from expanded tabs back into tab characters 154 | # (we'll replace them with markup after we do differencing) 155 | line = line.replace(" ", "\t") 156 | return line.replace("\0", " ").rstrip("\n") 157 | 158 | fromlines = [expand_tabs(line) for line in fromlines] 159 | tolines = [expand_tabs(line) for line in tolines] 160 | return fromlines, tolines 161 | 162 | def _strip_trailing_cr(self, lines): 163 | """Remove windows return carriage""" 164 | lines = [line.rstrip("\r") for line in lines] 165 | return lines 166 | 167 | def _all_cr_nl(self, lines): 168 | """Whether a file is entirely \r\n line endings""" 169 | return all(line.endswith("\r") for line in lines) 170 | 171 | def _display_len(self, s): 172 | # Handle wide characters like Chinese. 173 | def width(c): 174 | if isinstance(c, type("")) and unicodedata.east_asian_width(c) in [ 175 | "F", 176 | "W", 177 | ]: 178 | return 2 179 | elif c == "\r": 180 | return 2 181 | return 1 182 | 183 | return sum(width(c) for c in s) 184 | 185 | def _split_line(self, data_list, line_num, text): 186 | """Builds list of text lines by splitting text lines at wrap point 187 | 188 | This function will determine if the input text line needs to be 189 | wrapped (split) into separate lines. If so, the first wrap point 190 | will be determined and the first line appended to the output 191 | text line list. This function is used recursively to handle 192 | the second part of the split line to further split it. 193 | """ 194 | 195 | while True: 196 | # if blank line or context separator, just add it to the output 197 | # list 198 | if not line_num: 199 | data_list.append((line_num, text)) 200 | return 201 | 202 | # if line text doesn't need wrapping, just add it to the output 203 | # list 204 | if ( 205 | self._display_len(text) - (text.count("\0") * 3) 206 | <= self._wrapcolumn 207 | ): 208 | data_list.append((line_num, text)) 209 | return 210 | 211 | # scan text looking for the wrap point, keeping track if the wrap 212 | # point is inside markers 213 | i = 0 214 | n = 0 215 | mark = "" 216 | while n < self._wrapcolumn and i < len(text): 217 | if text[i] == "\0": 218 | i += 1 219 | mark = text[i] 220 | i += 1 221 | elif text[i] == "\1": 222 | i += 1 223 | mark = "" 224 | else: 225 | n += self._display_len(text[i]) 226 | i += 1 227 | 228 | # wrap point is inside text, break it up into separate lines 229 | line1 = text[:i] 230 | line2 = text[i:] 231 | 232 | # if wrap point is inside markers, place end marker at end of first 233 | # line and start marker at beginning of second line because each 234 | # line will have its own table tag markup around it. 235 | if mark: 236 | line1 = line1 + "\1" 237 | line2 = "\0" + mark + line2 238 | 239 | # tack on first line onto the output list 240 | data_list.append((line_num, line1)) 241 | 242 | # use this routine again to wrap the remaining text 243 | # unless truncate is set 244 | if self.truncate: 245 | return 246 | line_num = ">" 247 | text = line2 248 | 249 | def _line_wrapper(self, diffs): 250 | """Returns iterator that splits (wraps) mdiff text lines""" 251 | 252 | # pull from/to data and flags from mdiff iterator 253 | for fromdata, todata, flag in diffs: 254 | # check for context separators and pass them through 255 | if flag is None: 256 | yield fromdata, todata, flag 257 | continue 258 | (fromline, fromtext), (toline, totext) = fromdata, todata 259 | # for each from/to line split it at the wrap column to form 260 | # list of text lines. 261 | fromlist, tolist = [], [] 262 | self._split_line(fromlist, fromline, fromtext) 263 | self._split_line(tolist, toline, totext) 264 | # yield from/to line in pairs inserting blank lines as 265 | # necessary when one side has more wrapped lines 266 | while fromlist or tolist: 267 | if fromlist: 268 | fromdata = fromlist.pop(0) 269 | else: 270 | fromdata = ("", " ") 271 | if tolist: 272 | todata = tolist.pop(0) 273 | else: 274 | todata = ("", " ") 275 | yield fromdata, todata, flag 276 | 277 | def _collect_lines(self, diffs): 278 | """Collects mdiff output into separate lists 279 | 280 | Before storing the mdiff from/to data into a list, it is converted 281 | into a single line of text with console markup. 282 | """ 283 | 284 | # pull from/to data and flags from mdiff style iterator 285 | for fromdata, todata, flag in diffs: 286 | if (fromdata, todata, flag) == (None, None, None): 287 | yield None 288 | else: 289 | yield ( 290 | self._format_line(*fromdata), 291 | self._format_line(*todata), 292 | ) 293 | 294 | def _format_line(self, linenum, text): 295 | text = text.rstrip() 296 | if not self.line_numbers: 297 | return text 298 | return self._add_line_numbers(linenum, text) 299 | 300 | def _add_line_numbers(self, linenum, text): 301 | try: 302 | lid = "%d" % linenum 303 | except TypeError: 304 | # handle blank lines where linenum is '>' or '' 305 | lid = "" 306 | return text 307 | return "%s %s" % ( 308 | self._rpad(simple_colorize(str(lid), "line-numbers"), 8), 309 | text, 310 | ) 311 | 312 | def _real_len(self, s): 313 | s_len = 0 314 | in_esc = False 315 | prev = " " 316 | for c in replace_all( 317 | {"\0+": "", "\0-": "", "\0^": "", "\1": "", "\t": " "}, s 318 | ): 319 | if in_esc: 320 | if c == "m": 321 | in_esc = False 322 | else: 323 | if c == "[" and prev == "\033": 324 | in_esc = True 325 | s_len -= 1 # we counted prev when we shouldn't have 326 | else: 327 | s_len += self._display_len(c) 328 | prev = c 329 | 330 | return s_len 331 | 332 | def _rpad(self, s, field_width): 333 | return self._pad(s, field_width) + s 334 | 335 | def _pad(self, s, field_width): 336 | return " " * (field_width - self._real_len(s)) 337 | 338 | def _lpad(self, s, field_width): 339 | return s + self._pad(s, field_width) 340 | 341 | def make_table( 342 | self, 343 | fromlines, 344 | tolines, 345 | fromdesc="", 346 | todesc="", 347 | fromperms=None, 348 | toperms=None, 349 | context=False, 350 | numlines=5, 351 | ): 352 | """Generates table of side by side comparison with change highlights 353 | 354 | Arguments: 355 | fromlines -- list of "from" lines 356 | tolines -- list of "to" lines 357 | fromdesc -- "from" file column header string 358 | todesc -- "to" file column header string 359 | fromperms -- "from" file permissions 360 | toperms -- "to" file permissions 361 | context -- set to True for contextual differences (defaults to False 362 | which shows full differences). 363 | numlines -- number of context lines. When context is set True, 364 | controls number of lines displayed before and after the change. 365 | When context is False, controls the number of lines to place 366 | the "next" link anchors before the next change (so click of 367 | "next" link jumps to just before the change). 368 | """ 369 | if context: 370 | context_lines = numlines 371 | else: 372 | context_lines = None 373 | 374 | # change tabs to spaces before it gets more difficult after we insert 375 | # markup 376 | fromlines, tolines = self._tab_newline_replace(fromlines, tolines) 377 | 378 | if self.strip_trailing_cr or ( 379 | self._all_cr_nl(fromlines) and self._all_cr_nl(tolines) 380 | ): 381 | fromlines = self._strip_trailing_cr(fromlines) 382 | tolines = self._strip_trailing_cr(tolines) 383 | 384 | # create diffs iterator which generates side by side from/to data 385 | diffs = difflib._mdiff( 386 | fromlines, 387 | tolines, 388 | context_lines, 389 | linejunk=self._linejunk, 390 | charjunk=self._charjunk, 391 | ) 392 | 393 | # set up iterator to wrap lines that exceed desired width 394 | if self._wrapcolumn: 395 | diffs = self._line_wrapper(diffs) 396 | diffs = self._collect_lines(diffs) 397 | 398 | for left, right in self._generate_table( 399 | fromdesc, todesc, fromperms, toperms, diffs 400 | ): 401 | yield self.colorize( 402 | "%s %s" 403 | % ( 404 | self._lpad(left, self.cols // 2 - 1), 405 | self._lpad(right, self.cols // 2 - 1), 406 | ) 407 | ) 408 | 409 | def _generate_table(self, fromdesc, todesc, fromperms, toperms, diffs): 410 | if fromdesc or todesc: 411 | yield ( 412 | simple_colorize(fromdesc, "description"), 413 | simple_colorize(todesc, "description"), 414 | ) 415 | 416 | if fromperms != toperms: 417 | yield ( 418 | simple_colorize( 419 | f"{stat.filemode(fromperms)} ({fromperms:o})", 420 | "permissions", 421 | ), 422 | simple_colorize( 423 | f"{stat.filemode(toperms)} ({toperms:o})", "permissions" 424 | ), 425 | ) 426 | 427 | for i, line in enumerate(diffs): 428 | if line is None: 429 | # mdiff yields None on separator lines; skip the bogus ones 430 | # generated for the first line 431 | if i > 0: 432 | yield ( 433 | simple_colorize("---", "separator"), 434 | simple_colorize("---", "separator"), 435 | ) 436 | else: 437 | yield line 438 | 439 | def colorize(self, s): 440 | def background(color): 441 | return replace_all( 442 | {"\033[1;": "\033[7;1;", "\033[0;": "\033[7;"}, color 443 | ) 444 | 445 | C_ADD = color_codes[color_mapping["add"]] 446 | C_SUB = color_codes[color_mapping["subtract"]] 447 | C_CHG = color_codes[color_mapping["change"]] 448 | 449 | if self.highlight: 450 | C_ADD, C_SUB, C_CHG = ( 451 | background(C_ADD), 452 | background(C_SUB), 453 | background(C_CHG), 454 | ) 455 | 456 | C_NONE = color_codes["none"] 457 | colors = (C_ADD, C_SUB, C_CHG, C_NONE) 458 | 459 | s = replace_all( 460 | { 461 | "\0+": C_ADD, 462 | "\0-": C_SUB, 463 | "\0^": C_CHG, 464 | "\1": C_NONE, 465 | "\t": " ", 466 | "\r": "\\r", 467 | }, 468 | s, 469 | ) 470 | 471 | if self.highlight: 472 | return s 473 | 474 | if self.show_no_spaces: 475 | # Don't show whitespace even if it's a whitespace-only line. 476 | return re.sub( 477 | "\033\\[[01];3([01234567])m(\\s+)(\033\\[)", 478 | "\033[0;3\\1m\\2\\3", 479 | s, 480 | ) 481 | 482 | elif not self.show_all_spaces: 483 | # If there's a change consisting entirely of whitespace, 484 | # don't color it. 485 | return re.sub( 486 | "\033\\[[01];3([01234567])m(\\s+)(\033\\[)", 487 | "\033[7;3\\1m\\2\\3", 488 | s, 489 | ) 490 | 491 | def will_see_coloredspace(i, s): 492 | while i < len(s) and s[i].isspace(): 493 | i += 1 494 | if i < len(s) and s[i] == "\033": 495 | return False 496 | return True 497 | 498 | n_s = [] 499 | in_color = False 500 | seen_coloredspace = False 501 | for i, c in enumerate(s): 502 | if len(n_s) > 6 and n_s[-1] == "m": 503 | ns_end = "".join(n_s[-7:]) 504 | for color in colors: 505 | if ns_end.endswith(color): 506 | if color != in_color: 507 | seen_coloredspace = False 508 | in_color = color 509 | if ns_end.endswith(C_NONE): 510 | in_color = False 511 | 512 | if ( 513 | c.isspace() 514 | and in_color 515 | and ( 516 | self.show_all_spaces 517 | or not (seen_coloredspace or will_see_coloredspace(i, s)) 518 | ) 519 | ): 520 | n_s.extend([C_NONE, background(in_color), c, C_NONE, in_color]) 521 | else: 522 | if in_color: 523 | seen_coloredspace = True 524 | n_s.append(c) 525 | 526 | joined = "".join(n_s) 527 | 528 | return joined 529 | 530 | 531 | def raw_colorize(s, color): 532 | return "%s%s%s" % (color_codes[color], s, color_codes["none"]) 533 | 534 | 535 | def simple_colorize(s, category): 536 | return raw_colorize(s, color_mapping[category]) 537 | 538 | 539 | def replace_all(replacements, string): 540 | for search, replace in replacements.items(): 541 | string = string.replace(search, replace) 542 | return string 543 | 544 | 545 | class MultipleOption(Option): 546 | ACTIONS = Option.ACTIONS + ("extend",) 547 | STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",) 548 | TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",) 549 | ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",) 550 | 551 | def take_action(self, action, dest, opt, value, values, parser): 552 | if action == "extend": 553 | values.ensure_value(dest, []).append(value) 554 | else: 555 | Option.take_action(self, action, dest, opt, value, values, parser) 556 | 557 | 558 | def create_option_parser(): 559 | # If you change any of these, also update README. 560 | parser = OptionParser( 561 | usage="usage: %prog [options] left_file right_file", 562 | version="icdiff version %s" % __version__, 563 | description="Show differences between files in a " "two column view.", 564 | option_class=MultipleOption, 565 | ) 566 | parser.add_option( 567 | "--cols", 568 | default=None, 569 | help="specify the width of the screen. Autodetection is " "Unix only", 570 | ) 571 | parser.add_option( 572 | "--encoding", 573 | default="utf-8", 574 | help="specify the file encoding; defaults to utf8", 575 | ) 576 | parser.add_option( 577 | "-E", 578 | "--exclude-lines", 579 | action="store", 580 | type="string", 581 | dest="matcher", 582 | help="Do not diff lines that match this regex. Not " 583 | "compatible with the 'line-numbers' option", 584 | ) 585 | parser.add_option( 586 | "--head", 587 | default=0, 588 | help="consider only the first N lines of each file", 589 | ) 590 | parser.add_option( 591 | "-H", 592 | "--highlight", 593 | default=False, 594 | action="store_true", 595 | help="color by changing the background color instead of " 596 | "the foreground color. Very fast, ugly, displays all " 597 | "changes", 598 | ) 599 | parser.add_option( 600 | "-L", 601 | "--label", 602 | action="extend", 603 | type="string", 604 | dest="labels", 605 | help="override file labels with arbitrary tags. " 606 | "Use twice, one for each file. You may include the " 607 | "formatting strings '{path}' and '{basename}'", 608 | ) 609 | parser.add_option( 610 | "-N", 611 | "--line-numbers", 612 | default=False, 613 | action="store_true", 614 | help="generate output with line numbers. Not compatible " 615 | "with the 'exclude-lines' option.", 616 | ) 617 | parser.add_option( 618 | "--no-bold", 619 | default=False, 620 | action="store_true", 621 | help="use non-bold colors; recommended for solarized", 622 | ) 623 | parser.add_option( 624 | "--no-headers", 625 | default=False, 626 | action="store_true", 627 | help="don't label the left and right sides " "with their file names", 628 | ) 629 | parser.add_option( 630 | "--output-encoding", 631 | default="utf-8", 632 | help="specify the output encoding; defaults to utf8", 633 | ) 634 | parser.add_option( 635 | "-r", 636 | "--recursive", 637 | default=False, 638 | action="store_true", 639 | help="recursively compare subdirectories", 640 | ) 641 | parser.add_option( 642 | "-s", 643 | "--report-identical-files", 644 | default=False, 645 | action="store_true", 646 | help="report when two files are the same", 647 | ) 648 | parser.add_option( 649 | "--show-all-spaces", 650 | default=False, 651 | action="store_true", 652 | help="color all non-matching whitespace including " 653 | "that which is not needed for drawing the eye to " 654 | "changes. Slow, ugly, displays all changes", 655 | ) 656 | parser.add_option( 657 | "--show-no-spaces", 658 | default=False, 659 | action="store_true", 660 | help="don't color whitespace-only changes", 661 | ) 662 | parser.add_option("--tabsize", default=8, help="tab stop spacing") 663 | parser.add_option( 664 | "-t", 665 | "--truncate", 666 | default=False, 667 | action="store_true", 668 | help="truncate long lines instead of wrapping them", 669 | ) 670 | parser.add_option( 671 | "-u", 672 | "--patch", 673 | default=True, 674 | action="store_true", 675 | help="generate patch. This is always true, " 676 | "and only exists for compatibility", 677 | ) 678 | parser.add_option( 679 | "-U", 680 | "--unified", 681 | "--numlines", 682 | default=5, 683 | metavar="NUM", 684 | help="how many lines of context to print; " 685 | "can't be combined with --whole-file", 686 | ) 687 | parser.add_option( 688 | "-W", 689 | "--whole-file", 690 | default=False, 691 | action="store_true", 692 | help="show the whole file instead of just changed " 693 | "lines and context", 694 | ) 695 | parser.add_option( 696 | "-P", 697 | "--permissions", 698 | default=False, 699 | action="store_true", 700 | help="compare the file permissions as well as the " 701 | "content of the file", 702 | ) 703 | parser.add_option( 704 | "--strip-trailing-cr", 705 | default=False, 706 | action="store_true", 707 | help="strip any trailing carriage return at the end of " 708 | "an input line", 709 | ) 710 | parser.add_option( 711 | "--color-map", 712 | default=None, 713 | help="choose which colors are used for which items. " 714 | "Default is --color-map='" 715 | + ",".join("%s:%s" % x for x in sorted(color_mapping.items())) 716 | + "'" 717 | ". You don't have to override all of them: " 718 | "'--color-map=separator:white,description:cyan", 719 | ) 720 | parser.add_option( 721 | "--is-git-diff", 722 | default=False, 723 | action="store_true", 724 | help="Show the real file name when displaying " "git-diff result", 725 | ) 726 | parser.add_option( 727 | "-x", 728 | "--exclude", 729 | metavar="PAT", 730 | action="append", 731 | default=[], 732 | help="exclude files that match PAT", 733 | ) 734 | return parser 735 | 736 | 737 | def set_cols_option(options): 738 | if os.name == "nt": 739 | try: 740 | import struct 741 | from ctypes import windll, create_string_buffer 742 | 743 | fh = windll.kernel32.GetStdHandle(-12) # stderr is -12 744 | csbi = create_string_buffer(22) 745 | windll.kernel32.GetConsoleScreenBufferInfo(fh, csbi) 746 | res = struct.unpack("hhhhHhhhhhh", csbi.raw) 747 | options.cols = res[7] - res[5] + 1 # right - left + 1 748 | return 749 | 750 | except Exception: 751 | pass 752 | 753 | else: 754 | 755 | def ioctl_GWINSZ(fd): 756 | try: 757 | import fcntl 758 | import termios 759 | import struct 760 | 761 | cr = struct.unpack( 762 | "hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234") 763 | ) 764 | except Exception: 765 | return None 766 | return cr 767 | 768 | cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) 769 | if cr and cr[1] > 0: 770 | options.cols = cr[1] 771 | return 772 | options.cols = 80 773 | 774 | 775 | def validate_has_two_arguments(parser, args): 776 | if len(args) != 2: 777 | parser.print_help() 778 | sys.exit(EXIT_CODE_DIFF) 779 | 780 | 781 | def start(): 782 | diffs_found = False 783 | parser = create_option_parser() 784 | options, args = parser.parse_args() 785 | validate_has_two_arguments(parser, args) 786 | if not options.cols: 787 | set_cols_option(options) 788 | try: 789 | diffs_found = diff(options, *args) 790 | except KeyboardInterrupt: 791 | pass 792 | except IOError as e: 793 | if e.errno == errno.EPIPE: 794 | pass 795 | else: 796 | raise 797 | 798 | # Close stderr to prevent printing errors when icdiff is piped to 799 | # something that closes before icdiff is done writing 800 | # 801 | # See: https://stackoverflow.com/questions/26692284/... 802 | # ...how-to-prevent-brokenpipeerror-when-doing-a-flush-in-python 803 | sys.stderr.close() 804 | sys.exit(EXIT_CODE_DIFF if diffs_found else EXIT_CODE_SUCCESS) 805 | 806 | 807 | def codec_print(s, options): 808 | s = "%s\n" % s 809 | if hasattr(sys.stdout, "buffer"): 810 | sys.stdout.buffer.write(s.encode(options.output_encoding)) 811 | else: 812 | sys.stdout.write(s.encode(options.output_encoding)) 813 | 814 | 815 | def cmp_perms(options, a, b): 816 | return not options.permissions or ( 817 | os.lstat(a).st_mode == os.lstat(b).st_mode 818 | ) 819 | 820 | 821 | def should_be_excluded(name, pats): 822 | return any(fnmatch.fnmatchcase(name, pat) for pat in pats) 823 | 824 | 825 | def diff(options, a, b): 826 | def print_meta(s): 827 | codec_print(simple_colorize(s, "meta"), options) 828 | 829 | # We start out and assume that no diffs have been found (so far) 830 | diffs_found = False 831 | 832 | # Don't use os.path.isfile; it returns False for file-like entities like 833 | # bash's process substitution (/dev/fd/N). 834 | is_a_file = not os.path.isdir(a) 835 | is_b_file = not os.path.isdir(b) 836 | 837 | if is_a_file and is_b_file: 838 | try: 839 | if not ( 840 | filecmp.cmp(a, b, shallow=False) and cmp_perms(options, a, b) 841 | ): 842 | diffs_found = diffs_found | diff_files(options, a, b) 843 | elif options.report_identical_files: 844 | print("Files %s and %s are identical." % (a, b)) 845 | except OSError as e: 846 | if e.errno == errno.ENOENT: 847 | print_meta("error: file '%s' was not found" % e.filename) 848 | sys.exit(EXIT_CODE_ERROR) 849 | else: 850 | raise (e) 851 | 852 | elif not is_a_file and not is_b_file: 853 | a_contents = set(os.listdir(a)) 854 | b_contents = set(os.listdir(b)) 855 | 856 | for child in sorted(a_contents.union(b_contents)): 857 | if should_be_excluded(child, options.exclude): 858 | continue 859 | if child not in b_contents: 860 | print_meta("Only in %s: %s" % (a, child)) 861 | elif child not in a_contents: 862 | print_meta("Only in %s: %s" % (b, child)) 863 | elif options.recursive: 864 | diffs_found = diffs_found | diff( 865 | options, os.path.join(a, child), os.path.join(b, child) 866 | ) 867 | elif not is_a_file and is_b_file: 868 | print_meta("File %s is a directory while %s is a file" % (a, b)) 869 | diffs_found = True 870 | 871 | elif is_a_file and not is_b_file: 872 | print_meta("File %s is a file while %s is a directory" % (a, b)) 873 | diffs_found = True 874 | 875 | return diffs_found 876 | 877 | 878 | def read_file(fname, options): 879 | try: 880 | with codecs.open(fname, encoding=options.encoding, mode="rb") as inf: 881 | return inf.readlines() 882 | except UnicodeDecodeError as e: 883 | codec_print( 884 | "error: file '%s' not valid with encoding '%s': <%s> at %s-%s." 885 | % (fname, options.encoding, e.reason, e.start, e.end), 886 | options, 887 | ) 888 | raise 889 | except LookupError: 890 | codec_print( 891 | "error: encoding '%s' was not found." % (options.encoding), options 892 | ) 893 | sys.exit(EXIT_CODE_ERROR) 894 | 895 | 896 | def format_label(path, label="{path}"): 897 | """Format a label using a file's path and basename. 898 | 899 | Example: 900 | For file `/foo/bar.py` and label "Yours: {basename}" - 901 | The output is "Yours: bar.py" 902 | """ 903 | return label.format(path=path, basename=os.path.basename(path)) 904 | 905 | 906 | def diff_files(options, a, b): 907 | diff_found = False 908 | if options.is_git_diff is True: 909 | # Use $BASE as label when displaying git-diff result 910 | base = os.getenv("BASE") 911 | headers = [format_label(a, base), format_label(b, base)] 912 | else: 913 | if options.labels: 914 | if len(options.labels) == 2: 915 | headers = [ 916 | format_label(a, options.labels[0]), 917 | format_label(b, options.labels[1]), 918 | ] 919 | else: 920 | codec_print( 921 | "error: to use arbitrary file labels, " 922 | "specify -L twice.", 923 | options, 924 | ) 925 | sys.exit(EXIT_CODE_ERROR) 926 | else: 927 | headers = a, b 928 | if options.no_headers: 929 | headers = None, None 930 | 931 | head = int(options.head) 932 | 933 | assert not os.path.isdir(a) 934 | assert not os.path.isdir(b) 935 | 936 | try: 937 | lines_a = read_file(a, options) 938 | lines_b = read_file(b, options) 939 | except UnicodeDecodeError: 940 | return diff_found 941 | 942 | if head != 0: 943 | lines_a = lines_a[:head] 944 | lines_b = lines_b[:head] 945 | 946 | if options.matcher: 947 | lines_a = [ 948 | line_a 949 | for line_a in lines_a 950 | if not re.search(options.matcher, line_a) 951 | ] 952 | lines_b = [ 953 | line_b 954 | for line_b in lines_b 955 | if not re.search(options.matcher, line_b) 956 | ] 957 | 958 | # Determine if a difference has been detected 959 | diff_found = lines_a != lines_b or not cmp_perms(options, a, b) 960 | 961 | if options.no_bold: 962 | for key in color_mapping: 963 | color_mapping[key] = color_mapping[key].replace("_bold", "") 964 | 965 | if options.color_map: 966 | command_for_errors = '--color-map="%s"' % (options.color_map) 967 | for mapping in options.color_map.split(","): 968 | category, color = mapping.split(":", 1) 969 | 970 | if category not in color_mapping: 971 | print( 972 | "Invalid category '%s' in '%s'. Valid categories are: %s." 973 | % ( 974 | category, 975 | command_for_errors, 976 | ", ".join(sorted(color_mapping.keys())), 977 | ) 978 | ) 979 | sys.exit(EXIT_CODE_ERROR) 980 | 981 | if color not in color_codes: 982 | print( 983 | "Invalid color '%s' in '%s'. Valid colors are: %s." 984 | % ( 985 | color, 986 | command_for_errors, 987 | ", ".join( 988 | [ 989 | raw_colorize(x, x) 990 | for x in sorted(color_codes.keys()) 991 | ] 992 | ), 993 | ) 994 | ) 995 | sys.exit(EXIT_CODE_ERROR) 996 | 997 | color_mapping[category] = color 998 | 999 | if options.permissions: 1000 | mode_a = os.lstat(a).st_mode 1001 | mode_b = os.lstat(b).st_mode 1002 | else: 1003 | mode_a = None 1004 | mode_b = None 1005 | 1006 | cd = ConsoleDiff( 1007 | cols=int(options.cols), 1008 | show_all_spaces=options.show_all_spaces, 1009 | show_no_spaces=options.show_no_spaces, 1010 | highlight=options.highlight, 1011 | line_numbers=options.line_numbers, 1012 | tabsize=int(options.tabsize), 1013 | truncate=options.truncate, 1014 | strip_trailing_cr=options.strip_trailing_cr, 1015 | ) 1016 | for line in cd.make_table( 1017 | lines_a, 1018 | lines_b, 1019 | headers[0], 1020 | headers[1], 1021 | mode_a, 1022 | mode_b, 1023 | context=(not options.whole_file), 1024 | numlines=int(options.unified), 1025 | ): 1026 | codec_print(line, options) 1027 | sys.stdout.flush() 1028 | 1029 | return diff_found 1030 | 1031 | 1032 | if __name__ == "__main__": 1033 | start() 1034 | -------------------------------------------------------------------------------- /icdiff.py: -------------------------------------------------------------------------------- 1 | icdiff -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | flake8==2.4.0 2 | mccabe==0.3 3 | pep8==1.5.7 4 | pyflakes==0.8.1 5 | black==22.3.0 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from icdiff import __version__ 3 | 4 | setup( 5 | name="icdiff", 6 | version=__version__, 7 | url="https://www.jefftk.com/icdiff", 8 | project_urls={ 9 | "Source": "https://github.com/jeffkaufman/icdiff", 10 | }, 11 | classifiers=[ 12 | "License :: OSI Approved :: Python Software Foundation License" 13 | ], 14 | author="Jeff Kaufman", 15 | author_email="jeff@jefftk.com", 16 | description="improved colored diff", 17 | long_description=open('README.md').read(), 18 | long_description_content_type='text/markdown', 19 | scripts=['git-icdiff'], 20 | py_modules=['icdiff'], 21 | entry_points={ 22 | 'console_scripts': [ 23 | 'icdiff=icdiff:start', 24 | ], 25 | }, 26 | ) 27 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: ./test.sh [--regold] [test-name] python[3] 4 | # Example: 5 | # Run all tests: 6 | # ./test.sh python3 7 | # Regold all tests: 8 | # ./test.sh --regold python3 9 | # Run one test: 10 | # ./test.sh tests/gold-45-sas-h-nb.txt python3 11 | # Regold one test: 12 | # ./test.sh --regold tests/gold-45-sas-h-nb.txt python3 13 | 14 | if [ "$#" -gt 1 -a "$1" = "--regold" ]; then 15 | REGOLD=true 16 | shift 17 | else 18 | REGOLD=false 19 | fi 20 | 21 | TEST_NAME=all 22 | if [ "$#" -gt 1 ]; then 23 | TEST_NAME=$1 24 | shift 25 | fi 26 | 27 | if [ "$#" != 1 ]; then 28 | echo "Usage: '$0 [--regold] [test-name] python[3]'" 29 | exit 1 30 | fi 31 | 32 | PYTHON="$1" 33 | ICDIFF="icdiff" 34 | 35 | if [ ! -z "$INSTALLED" ]; then 36 | INVOCATION="$ICDIFF" 37 | else 38 | INVOCATION="$PYTHON $ICDIFF" 39 | fi 40 | 41 | function fail() { 42 | echo "FAIL" 43 | exit 1 44 | } 45 | 46 | function check_gold() { 47 | local error_code 48 | local expect=$1 49 | local gold=tests/$2 50 | shift 51 | shift 52 | 53 | if [ $TEST_NAME != "all" -a $TEST_NAME != $gold ]; then 54 | return 55 | fi 56 | 57 | echo " check_gold $gold matches $@" 58 | local tmp=/tmp/icdiff.output 59 | $INVOCATION "$@" &> $tmp 60 | error_code=$? 61 | 62 | if $REGOLD; then 63 | if [ -e $gold ] && diff $tmp $gold > /dev/null; then 64 | echo "Did not need to regold $gold" 65 | else 66 | cat $tmp 67 | read -p "Is this correct? y/n > " -n 1 -r 68 | echo 69 | if [[ $REPLY =~ ^[Yy]$ ]]; then 70 | mv $tmp $gold 71 | echo "Regolded $gold." 72 | else 73 | echo "Did not regold $gold." 74 | fi 75 | fi 76 | return 77 | fi 78 | 79 | if ! diff $gold $tmp; then 80 | echo "Got: ($tmp)" 81 | cat $tmp 82 | echo "Expected: ($gold)" 83 | cat $gold 84 | fail 85 | fi 86 | 87 | if [[ $error_code != $expect ]]; then 88 | echo "Got error code: $error_code" 89 | echo "Expected error code: $expect" 90 | fail 91 | fi 92 | } 93 | 94 | FIRST_TIME_CHECK_GIT_DIFF=true 95 | function check_git_diff() { 96 | local gitdiff=tests/$1 97 | shift 98 | 99 | echo " check_gitdiff $gitdiff matches git icdiff $@" 100 | # Check when using icdiff in git 101 | if $FIRST_TIME_CHECK_GIT_DIFF; then 102 | FIRST_TIME_CHECK_GIT_DIFF=false 103 | # Set default args when first time check git diff 104 | yes | git difftool --extcmd icdiff > /dev/null 105 | git config --global icdiff.options '--cols=80' 106 | export PATH="$(pwd)":$PATH 107 | fi 108 | local tmp=/tmp/git-icdiff.output 109 | git icdiff $1 $2 &> $tmp 110 | if ! diff $tmp $gitdiff; then 111 | echo "Got: ($tmp)" 112 | cat $tmp 113 | echo "Expected: ($gitdiff)" 114 | fail 115 | fi 116 | } 117 | 118 | check_gold 1 gold-recursive.txt --recursive tests/{a,b} --cols=80 119 | check_gold 1 gold-exclude.txt --exclude-lines '^#| pad' tests/input-4-cr.txt tests/input-4-partial-cr.txt --cols=80 120 | check_gold 0 gold-dir.txt tests/{a,b} --cols=80 121 | check_gold 1 gold-12.txt tests/input-{1,2}.txt --cols=80 122 | check_gold 1 gold-12-t.txt tests/input-{1,2}.txt --cols=80 --truncate 123 | check_gold 0 gold-3.txt tests/input-{3,3}.txt 124 | check_gold 1 gold-45.txt tests/input-{4,5}.txt --cols=80 125 | check_gold 1 gold-45-95.txt tests/input-{4,5}.txt --cols=95 126 | check_gold 1 gold-45-sas.txt tests/input-{4,5}.txt --cols=80 --show-all-spaces 127 | check_gold 1 gold-45-h.txt tests/input-{4,5}.txt --cols=80 --highlight 128 | check_gold 1 gold-45-nb.txt tests/input-{4,5}.txt --cols=80 --no-bold 129 | check_gold 1 gold-45-sas-h.txt tests/input-{4,5}.txt --cols=80 --show-all-spaces --highlight 130 | check_gold 1 gold-45-sas-h-nb.txt tests/input-{4,5}.txt --cols=80 --show-all-spaces --highlight --no-bold 131 | check_gold 1 gold-sas.txt tests/input-{10,11}.txt --cols=80 --show-all-spaces 132 | check_gold 1 gold-sns.txt tests/input-{10,11}.txt --cols=80 --show-no-spaces 133 | check_gold 1 gold-show-spaces.txt tests/input-{10,11}.txt --cols=80 134 | check_gold 1 gold-45-h-nb.txt tests/input-{4,5}.txt --cols=80 --highlight --no-bold 135 | check_gold 1 gold-45-ln.txt tests/input-{4,5}.txt --cols=80 --line-numbers 136 | check_gold 1 gold-45-ln-color.txt tests/input-{4,5}.txt --cols=80 --line-numbers --color-map='line-numbers:cyan' 137 | check_gold 1 gold-45-nh.txt tests/input-{4,5}.txt --cols=80 --no-headers 138 | check_gold 1 gold-45-h3.txt tests/input-{4,5}.txt --cols=80 --head=3 139 | check_gold 2 gold-45-l.txt tests/input-{4,5}.txt --cols=80 -L left 140 | check_gold 1 gold-45-lr.txt tests/input-{4,5}.txt --cols=80 -L left -L right 141 | check_gold 1 gold-45-lbrb.txt tests/input-{4,5}.txt --cols=80 -L "L {basename}" -L "R {basename}" 142 | check_gold 1 gold-45-pipe.txt tests/input-4.txt <(cat tests/input-5.txt) --cols=80 --no-headers 143 | check_gold 1 gold-4dn.txt tests/input-4.txt /dev/null --cols=80 -L left -L right 144 | check_gold 1 gold-dn5.txt /dev/null tests/input-5.txt --cols=80 -L left -L right 145 | check_gold 1 gold-67.txt tests/input-{6,7}.txt --cols=80 146 | check_gold 1 gold-67-wf.txt tests/input-{6,7}.txt --cols=80 --whole-file 147 | check_gold 1 gold-67-ln.txt tests/input-{6,7}.txt --cols=80 --line-numbers 148 | check_gold 1 gold-67-u3.txt tests/input-{6,7}.txt --cols=80 -U 3 149 | check_gold 1 gold-tabs-default.txt tests/input-{8,9}.txt --cols=80 150 | check_gold 1 gold-tabs-4.txt tests/input-{8,9}.txt --cols=80 --tabsize=4 151 | check_gold 2 gold-file-not-found.txt tests/input-4.txt nonexistent_file 152 | check_gold 1 gold-strip-cr-off.txt tests/input-4.txt tests/input-4-cr.txt --cols=80 153 | check_gold 1 gold-strip-cr-on.txt tests/input-4.txt tests/input-4-cr.txt --cols=80 --strip-trailing-cr 154 | check_gold 1 gold-no-cr-indent tests/input-4-cr.txt tests/input-4-partial-cr.txt --cols=80 155 | check_gold 1 gold-hide-cr-if-dos tests/input-4-cr.txt tests/input-5-cr.txt --cols=80 156 | check_gold 1 gold-12-subcolors.txt tests/input-{1,2}.txt --cols=80 --color-map='change:magenta,description:cyan_bold' 157 | check_gold 2 gold-subcolors-bad-color tests/input-{1,2}.txt --cols=80 --color-map='change:mageta,description:cyan_bold' 158 | check_gold 2 gold-subcolors-bad-cat tests/input-{1,2}.txt --cols=80 --color-map='chnge:magenta,description:cyan_bold' 159 | check_gold 2 gold-subcolors-bad-fmt tests/input-{1,2}.txt --cols=80 --color-map='change:magenta:gold,description:cyan_bold' 160 | check_gold 0 gold-identical-on.txt tests/input-{1,1}.txt -s 161 | check_gold 2 gold-bad-encoding.txt tests/input-{1,2}.txt --encoding=nonexistend_encoding 162 | check_gold 0 gold-recursive-with-exclude.txt --recursive -x c tests/{a,b} --cols=80 163 | check_gold 1 gold-recursive-with-exclude2.txt --recursive -x 'excl*' tests/test-with-exclude/{a,b} --cols=80 164 | check_gold 0 gold-exit-process-sub tests/input-1.txt <(cat tests/input-1.txt) --no-headers --cols=80 165 | 166 | rm -f tests/permissions-{a,b} 167 | touch tests/permissions-{a,b} 168 | check_gold 0 gold-permissions-same.txt tests/permissions-{a,b} -P --cols=80 169 | 170 | chmod 666 tests/permissions-a 171 | chmod 665 tests/permissions-b 172 | check_gold 1 gold-permissions-diff.txt tests/permissions-{a,b} -P --cols=80 173 | 174 | echo "some text" >> tests/permissions-a 175 | check_gold 1 gold-permissions-diff-text.txt tests/permissions-{a,b} -P --cols=80 176 | 177 | echo -e "\04" >> tests/permissions-b 178 | check_gold 1 gold-permissions-diff-binary.txt tests/permissions-{a,b} -P --cols=80 179 | rm -f tests/permissions-{a,b} 180 | 181 | if git show 4e86205629 &> /dev/null; then 182 | # We're in the repo, so test git. 183 | check_git_diff gitdiff-only-newlines.txt 4e86205629~1 4e86205629 184 | else 185 | echo "Not in icdiff repo; skipping git test" 186 | fi 187 | 188 | # Testing pipe behavior doesn't fit well with the check_gold system 189 | $INVOCATION tests/input-{4,5}.txt 2>/tmp/icdiff-pipe-error-output | head -n 1 190 | if [ -s /tmp/icdiff-pipe-error-output ]; then 191 | echo 'emitting errors on early pipe closure' 192 | fail 193 | fi 194 | 195 | VERSION=$($INVOCATION --version | awk '{print $NF}') 196 | if [ "$VERSION" != $(head -n 1 ChangeLog) ]; then 197 | echo "Version mismatch between ChangeLog and icdiff source." 198 | fail 199 | fi 200 | 201 | function ensure_installed() { 202 | if ! command -v "$1" >/dev/null 2>&1; then 203 | echo "Could not find $1." 204 | echo 'Ensure it is installed and on your $PATH.' 205 | if [ -z "$VIRTUAL_ENV" ]; then 206 | echo 'It appears you have have forgotten to activate your virtualenv.' 207 | fi 208 | echo 'See README.md for details on setting up your environment.' 209 | fail 210 | fi 211 | } 212 | 213 | ensure_installed "black" 214 | echo 'Running black formatter...' 215 | if ! black icdiff --quiet --line-length 79 --check; then 216 | echo "" 217 | echo 'Consider running `black icdiff --line-length 79`' 218 | fail 219 | fi 220 | 221 | ensure_installed "flake8" 222 | echo 'Running flake8 linter...' 223 | if ! flake8 icdiff; then 224 | fail 225 | fi 226 | 227 | if ! $REGOLD; then 228 | echo PASS 229 | fi 230 | -------------------------------------------------------------------------------- /tests/a/1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffkaufman/icdiff/7b3692c79555e981e3cdc6da1fdfabf5e7b0a4ea/tests/a/1 -------------------------------------------------------------------------------- /tests/a/c/e: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/a/c/f: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /tests/a/j: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /tests/b/1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffkaufman/icdiff/7b3692c79555e981e3cdc6da1fdfabf5e7b0a4ea/tests/b/1 -------------------------------------------------------------------------------- /tests/b/c/e: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/b/c/f: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /tests/b/c/g: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /tests/b/c/h: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /tests/b/d/q: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /tests/b/i: -------------------------------------------------------------------------------- 1 | 6 2 | -------------------------------------------------------------------------------- /tests/gitdiff-only-newlines.txt: -------------------------------------------------------------------------------- 1 | [0;34mREADME[m [0;34mREADME[m 2 | --show-all-spaces color all non-m --show-all-spaces color all non-m 3 | atching whitespace including atching whitespace including 4 | that which is n that which is n 5 | ot needed for drawing the eye to ot needed for drawing the eye to 6 | changes. Slow, changes. Slow, 7 | ugly, displays all changes ugly, displays all changes 8 | --print-headers label the left --print-headers label the left 9 | and right sides with their file and right sides with their file 10 | names names 11 | [7;32m [m 12 | [1;32mLicense:[m 13 | [7;32m [m 14 | [1;32m This file is derived from difflib.Ht[m 15 | [1;32mmlDiff which is under the license:[m 16 | [7;32m [m 17 | [1;32m http://www.python.org/download/rel[m 18 | [1;32meases/2.6.2/license/[m 19 | [7;32m [m 20 | [1;32m I release my changes here under the [m 21 | [1;32msame license. This is GPL compatible.[m 22 | [7;32m [m 23 | -------------------------------------------------------------------------------- /tests/gold-12-subcolors.txt: -------------------------------------------------------------------------------- 1 | [1;36mtests/input-1.txt[m [1;36mtests/input-2.txt[m 2 | 测试行a[0;35mb[mc测试测试行a[0;35mb[mc,测试测试行a[0;35mb[mc测 测试行a[0;35md[mc测试测试行a[0;35md[mc,测试测试行a[0;35md[mc测 3 | 试测试行a[0;35mb[mc测试测试行a[0;35mb[mc测试测试行a[0;35mb[mc测 试测试行a[0;35md[mc测试测试行a[0;35md[mc测试测试行a[0;35md[mc测 4 | 试测试行a[0;35mb[mc测试测试行a[0;35mb[mc测试测试行a[0;35mb[mc测 试测试行a[0;35md[mc测试测试行a[0;35md[mc测试测试行a[0;35md[mc测 5 | 试 试 6 | -------------------------------------------------------------------------------- /tests/gold-12-t.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-1.txt[m [0;34mtests/input-2.txt[m 2 | 测试行a[1;33mb[mc测试测试行a[1;33mb[mc,测试测试行a[1;33mb[mc测 测试行a[1;33md[mc测试测试行a[1;33md[mc,测试测试行a[1;33md[mc测 3 | -------------------------------------------------------------------------------- /tests/gold-12.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-1.txt[m [0;34mtests/input-2.txt[m 2 | 测试行a[1;33mb[mc测试测试行a[1;33mb[mc,测试测试行a[1;33mb[mc测 测试行a[1;33md[mc测试测试行a[1;33md[mc,测试测试行a[1;33md[mc测 3 | 试测试行a[1;33mb[mc测试测试行a[1;33mb[mc测试测试行a[1;33mb[mc测 试测试行a[1;33md[mc测试测试行a[1;33md[mc测试测试行a[1;33md[mc测 4 | 试测试行a[1;33mb[mc测试测试行a[1;33mb[mc测试测试行a[1;33mb[mc测 试测试行a[1;33md[mc测试测试行a[1;33md[mc测试测试行a[1;33md[mc测 5 | 试 试 6 | -------------------------------------------------------------------------------- /tests/gold-3.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffkaufman/icdiff/7b3692c79555e981e3cdc6da1fdfabf5e7b0a4ea/tests/gold-3.txt -------------------------------------------------------------------------------- /tests/gold-45-95.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [1;33m35[m0px; width: [1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [1;32m font-size: 30px;[m 6 | margin: 0[1;31mpx[m; margin: 0; 7 | padding: 0[1;31mpx[m; padding: 0; 8 | [1;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45-h-nb.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [7;33m35[m0px; width: [7;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [7;32m font-size: 30px;[m 6 | margin: 0[7;31mpx[m; margin: 0; 7 | padding: 0[7;31mpx[m; padding: 0; 8 | [7;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45-h.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [7;1;33m35[m0px; width: [7;1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [7;1;32m font-size: 30px;[m 6 | margin: 0[7;1;31mpx[m; margin: 0; 7 | padding: 0[7;1;31mpx[m; padding: 0; 8 | [7;1;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45-h3.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [1;33m35[m0px; width: [1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | -------------------------------------------------------------------------------- /tests/gold-45-l.txt: -------------------------------------------------------------------------------- 1 | error: to use arbitrary file labels, specify -L twice. 2 | -------------------------------------------------------------------------------- /tests/gold-45-lbrb.txt: -------------------------------------------------------------------------------- 1 | [0;34mL input-4.txt[m [0;34mR input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [1;33m35[m0px; width: [1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [1;32m font-size: 30px;[m 6 | margin: 0[1;31mpx[m; margin: 0; 7 | padding: 0[1;31mpx[m; padding: 0; 8 | [1;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45-ln-color.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | [0;36m1[m #input, #button { [0;36m1[m #input, #button { 3 | [0;36m2[m width: [1;33m35[m0px; [0;36m2[m width: [1;33m40[m0px; 4 | [0;36m3[m height: 40px; [0;36m3[m height: 40px; 5 | [0;36m4[m [1;32m font-size: 30px;[m 6 | [0;36m4[m margin: 0[1;31mpx[m; [0;36m5[m margin: 0; 7 | [0;36m5[m padding: 0[1;31mpx[m; [0;36m6[m padding: 0; 8 | [0;36m6[m [1;31m margin-bottom: 15px;[m 9 | [0;36m7[m text-align: center; [0;36m7[m text-align: center; 10 | [0;36m8[m } [0;36m8[m } 11 | -------------------------------------------------------------------------------- /tests/gold-45-ln.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | [0;37m1[m #input, #button { [0;37m1[m #input, #button { 3 | [0;37m2[m width: [1;33m35[m0px; [0;37m2[m width: [1;33m40[m0px; 4 | [0;37m3[m height: 40px; [0;37m3[m height: 40px; 5 | [0;37m4[m [1;32m font-size: 30px;[m 6 | [0;37m4[m margin: 0[1;31mpx[m; [0;37m5[m margin: 0; 7 | [0;37m5[m padding: 0[1;31mpx[m; [0;37m6[m padding: 0; 8 | [0;37m6[m [1;31m margin-bottom: 15px;[m 9 | [0;37m7[m text-align: center; [0;37m7[m text-align: center; 10 | [0;37m8[m } [0;37m8[m } 11 | -------------------------------------------------------------------------------- /tests/gold-45-lr.txt: -------------------------------------------------------------------------------- 1 | [0;34mleft[m [0;34mright[m 2 | #input, #button { #input, #button { 3 | width: [1;33m35[m0px; width: [1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [1;32m font-size: 30px;[m 6 | margin: 0[1;31mpx[m; margin: 0; 7 | padding: 0[1;31mpx[m; padding: 0; 8 | [1;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45-nb.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [0;33m35[m0px; width: [0;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [0;32m font-size: 30px;[m 6 | margin: 0[0;31mpx[m; margin: 0; 7 | padding: 0[0;31mpx[m; padding: 0; 8 | [0;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45-nh.txt: -------------------------------------------------------------------------------- 1 | #input, #button { #input, #button { 2 | width: [1;33m35[m0px; width: [1;33m40[m0px; 3 | height: 40px; height: 40px; 4 | [1;32m font-size: 30px;[m 5 | margin: 0[1;31mpx[m; margin: 0; 6 | padding: 0[1;31mpx[m; padding: 0; 7 | [1;31m margin-bottom: 15px;[m 8 | text-align: center; text-align: center; 9 | } } 10 | -------------------------------------------------------------------------------- /tests/gold-45-pipe.txt: -------------------------------------------------------------------------------- 1 | #input, #button { #input, #button { 2 | width: [1;33m35[m0px; width: [1;33m40[m0px; 3 | height: 40px; height: 40px; 4 | [1;32m font-size: 30px;[m 5 | margin: 0[1;31mpx[m; margin: 0; 6 | padding: 0[1;31mpx[m; padding: 0; 7 | [1;31m margin-bottom: 15px;[m 8 | text-align: center; text-align: center; 9 | } } 10 | -------------------------------------------------------------------------------- /tests/gold-45-sas-h-nb.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [7;33m35[m0px; width: [7;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [7;32m font-size: 30px;[m 6 | margin: 0[7;31mpx[m; margin: 0; 7 | padding: 0[7;31mpx[m; padding: 0; 8 | [7;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45-sas-h.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [7;1;33m35[m0px; width: [7;1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [7;1;32m font-size: 30px;[m 6 | margin: 0[7;1;31mpx[m; margin: 0; 7 | padding: 0[7;1;31mpx[m; padding: 0; 8 | [7;1;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45-sas.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [1;33m35[m0px; width: [1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [1;32m[m[7;1;32m [m[1;32m[m[7;1;32m [m[1;32mfont-size:[m[7;1;32m [m[1;32m30px;[m 6 | margin: 0[1;31mpx[m; margin: 0; 7 | padding: 0[1;31mpx[m; padding: 0; 8 | [1;31m[m[7;1;31m [m[1;31m[m[7;1;31m [m[1;31mmargin-bottom:[m[7;1;31m [m[1;31m15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-45.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-5.txt[m 2 | #input, #button { #input, #button { 3 | width: [1;33m35[m0px; width: [1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [1;32m font-size: 30px;[m 6 | margin: 0[1;31mpx[m; margin: 0; 7 | padding: 0[1;31mpx[m; padding: 0; 8 | [1;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-4dn.txt: -------------------------------------------------------------------------------- 1 | [0;34mleft[m [0;34mright[m 2 | [1;31m#input, #button {[m 3 | [1;31m width: 350px;[m 4 | [1;31m height: 40px;[m 5 | [1;31m margin: 0px;[m 6 | [1;31m padding: 0px;[m 7 | [1;31m margin-bottom: 15px;[m 8 | [1;31m text-align: center;[m 9 | [1;31m}[m 10 | -------------------------------------------------------------------------------- /tests/gold-67-ln.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-6.txt[m [0;34mtests/input-7.txt[m 2 | [0;37m8[m h [0;37m8[m h 3 | [0;37m9[m i [0;37m9[m i 4 | [0;37m10[m j [0;37m10[m j 5 | [0;37m11[m k [0;37m11[m k 6 | [0;37m12[m l [0;37m12[m l 7 | [0;37m13[m [1;32mm[m 8 | [0;37m14[m [1;32mm[m 9 | [0;37m13[m m [0;37m15[m m 10 | [0;37m14[m n [0;37m16[m n 11 | [0;37m15[m o [0;37m17[m o 12 | [0;37m16[m p [0;37m18[m p 13 | [0;37m17[m q [0;37m19[m q 14 | -------------------------------------------------------------------------------- /tests/gold-67-u3.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-6.txt[m [0;34mtests/input-7.txt[m 2 | j j 3 | k k 4 | l l 5 | [1;32mm[m 6 | [1;32mm[m 7 | m m 8 | n n 9 | o o 10 | -------------------------------------------------------------------------------- /tests/gold-67-wf.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-6.txt[m [0;34mtests/input-7.txt[m 2 | a a 3 | b b 4 | c c 5 | d d 6 | e e 7 | f f 8 | g g 9 | h h 10 | i i 11 | j j 12 | k k 13 | l l 14 | [1;32mm[m 15 | [1;32mm[m 16 | m m 17 | n n 18 | o o 19 | p p 20 | q q 21 | r r 22 | s s 23 | t t 24 | u u 25 | z z 26 | w w 27 | x x 28 | y y 29 | z z 30 | -------------------------------------------------------------------------------- /tests/gold-67.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-6.txt[m [0;34mtests/input-7.txt[m 2 | h h 3 | i i 4 | j j 5 | k k 6 | l l 7 | [1;32mm[m 8 | [1;32mm[m 9 | m m 10 | n n 11 | o o 12 | p p 13 | q q 14 | -------------------------------------------------------------------------------- /tests/gold-bad-encoding.txt: -------------------------------------------------------------------------------- 1 | error: encoding 'nonexistend_encoding' was not found. 2 | -------------------------------------------------------------------------------- /tests/gold-dir.txt: -------------------------------------------------------------------------------- 1 | [0;35mOnly in tests/b: d[m 2 | [0;35mOnly in tests/b: i[m 3 | [0;35mOnly in tests/a: j[m 4 | -------------------------------------------------------------------------------- /tests/gold-dn5.txt: -------------------------------------------------------------------------------- 1 | [0;34mleft[m [0;34mright[m 2 | [1;32m#input, #button {[m 3 | [1;32m width: 400px;[m 4 | [1;32m height: 40px;[m 5 | [1;32m font-size: 30px;[m 6 | [1;32m margin: 0;[m 7 | [1;32m padding: 0;[m 8 | [1;32m text-align: center;[m 9 | [1;32m}[m 10 | -------------------------------------------------------------------------------- /tests/gold-exclude.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4-cr.txt[m [0;34mtests/input-4-partial-cr.txt[m 2 | width: 350px; width: 350px; 3 | height: 40px; height: 40px; 4 | margin: 0px; margin: 0px; 5 | margin-bottom: 15px; margin-bottom: 15px; 6 | text-align: center;[1;31m\r[m text-align: center; 7 | } } 8 | -------------------------------------------------------------------------------- /tests/gold-exit-process-sub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffkaufman/icdiff/7b3692c79555e981e3cdc6da1fdfabf5e7b0a4ea/tests/gold-exit-process-sub -------------------------------------------------------------------------------- /tests/gold-file-not-found.txt: -------------------------------------------------------------------------------- 1 | [0;35merror: file 'nonexistent_file' was not found[m 2 | -------------------------------------------------------------------------------- /tests/gold-hide-cr-if-dos: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4-cr.txt[m [0;34mtests/input-5-cr.txt[m 2 | #input, #button { #input, #button { 3 | width: [1;33m35[m0px; width: [1;33m40[m0px; 4 | height: 40px; height: 40px; 5 | [1;32m font-size: 30px;[m 6 | margin: 0[1;31mpx[m; margin: 0; 7 | padding: 0[1;31mpx[m; padding: 0; 8 | [1;31m margin-bottom: 15px;[m 9 | text-align: center; text-align: center; 10 | } } 11 | -------------------------------------------------------------------------------- /tests/gold-identical-on.txt: -------------------------------------------------------------------------------- 1 | Files tests/input-1.txt and tests/input-1.txt are identical. 2 | -------------------------------------------------------------------------------- /tests/gold-no-cr-indent: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4-cr.txt[m [0;34mtests/input-4-partial-cr.txt[m 2 | width: 350px; width: 350px; 3 | height: 40px; height: 40px; 4 | margin: 0px; margin: 0px; 5 | padding: 0px; padding: 0px; 6 | margin-bottom: 15px; margin-bottom: 15px; 7 | text-align: center;[1;31m\r[m text-align: center; 8 | } } 9 | -------------------------------------------------------------------------------- /tests/gold-permissions-diff-binary.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/permissions-a[m [0;34mtests/permissions-b[m 2 | [0;33m-rw-rw-rw- (100666)[m [0;33m-rw-rw-r-x (100665)[m 3 | [1;31msome text[m [1;32m[m 4 | -------------------------------------------------------------------------------- /tests/gold-permissions-diff-text.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/permissions-a[m [0;34mtests/permissions-b[m 2 | [0;33m-rw-rw-rw- (100666)[m [0;33m-rw-rw-r-x (100665)[m 3 | [1;31msome text[m 4 | -------------------------------------------------------------------------------- /tests/gold-permissions-diff.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/permissions-a[m [0;34mtests/permissions-b[m 2 | [0;33m-rw-rw-rw- (100666)[m [0;33m-rw-rw-r-x (100665)[m 3 | -------------------------------------------------------------------------------- /tests/gold-permissions-same.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffkaufman/icdiff/7b3692c79555e981e3cdc6da1fdfabf5e7b0a4ea/tests/gold-permissions-same.txt -------------------------------------------------------------------------------- /tests/gold-recursive-with-exclude.txt: -------------------------------------------------------------------------------- 1 | error: file 'tests/b/1' not valid with encoding 'utf-8': <invalid start byte> at 4461-4462. 2 | [0;35mOnly in tests/b: d[m 3 | [0;35mOnly in tests/b: i[m 4 | [0;35mOnly in tests/a: j[m 5 | -------------------------------------------------------------------------------- /tests/gold-recursive-with-exclude2.txt: -------------------------------------------------------------------------------- 1 | error: file 'tests/test-with-exclude/b/1' not valid with encoding 'utf-8': <invalid start byte> at 4461-4462. 2 | [0;34mtests/test-with-exclude/a/c/f[m [0;34mtests/test-with-exclude/b/c/f[m 3 | [1;31m2[m [1;32m3[m 4 | [0;35mOnly in tests/test-with-exclude/b/c: g[m 5 | [0;35mOnly in tests/test-with-exclude/b/c: h[m 6 | [0;35mOnly in tests/test-with-exclude/b: d[m 7 | [0;35mOnly in tests/test-with-exclude/b: i[m 8 | [0;35mOnly in tests/test-with-exclude/a: j[m 9 | -------------------------------------------------------------------------------- /tests/gold-recursive.txt: -------------------------------------------------------------------------------- 1 | error: file 'tests/b/1' not valid with encoding 'utf-8': <invalid start byte> at 4461-4462. 2 | [0;34mtests/a/c/f[m [0;34mtests/b/c/f[m 3 | [1;31m2[m [1;32m3[m 4 | [0;35mOnly in tests/b/c: g[m 5 | [0;35mOnly in tests/b/c: h[m 6 | [0;35mOnly in tests/b: d[m 7 | [0;35mOnly in tests/b: i[m 8 | [0;35mOnly in tests/a: j[m 9 | -------------------------------------------------------------------------------- /tests/gold-sas.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-10.txt[m [0;34mtests/input-11.txt[m 2 | void main () void main () 3 | { { 4 | int x; int x; 5 | int y; int y; 6 | } } 7 | [1;32m[m[7;1;32m [m[1;32m[m 8 | [1;32mvoid[m[7;1;32m [m[1;32mfoo[m[7;1;32m [m[1;32m()[m 9 | [1;32m{[m 10 | [1;32m[m[7;1;32m [m[1;32m[m 11 | [1;32m}[m 12 | -------------------------------------------------------------------------------- /tests/gold-show-spaces.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-10.txt[m [0;34mtests/input-11.txt[m 2 | void main () void main () 3 | { { 4 | int x; int x; 5 | int y; int y; 6 | } } 7 | [7;32m [m 8 | [1;32mvoid foo ()[m 9 | [1;32m{[m 10 | [7;32m [m 11 | [1;32m}[m 12 | -------------------------------------------------------------------------------- /tests/gold-sns.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-10.txt[m [0;34mtests/input-11.txt[m 2 | void main () void main () 3 | { { 4 | int x; int x; 5 | int y; int y; 6 | } } 7 | [0;32m [m 8 | [1;32mvoid foo ()[m 9 | [1;32m{[m 10 | [0;32m [m 11 | [1;32m}[m 12 | -------------------------------------------------------------------------------- /tests/gold-strip-cr-off.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-4-cr.txt[m 2 | #input, #button { #input, #button {[1;32m\r[m 3 | width: 350px; width: 350px;[1;32m\r[m 4 | height: 40px; height: 40px;[1;32m\r[m 5 | margin: 0px; margin: 0px;[1;32m\r[m 6 | padding: 0px; padding: 0px;[1;32m\r[m 7 | margin-bottom: 15px; margin-bottom: 15px;[1;32m\r[m 8 | text-align: center; text-align: center;[1;32m\r[m 9 | [1;31m}[m [1;32m}\r[m 10 | -------------------------------------------------------------------------------- /tests/gold-strip-cr-on.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-4.txt[m [0;34mtests/input-4-cr.txt[m 2 | -------------------------------------------------------------------------------- /tests/gold-subcolors-bad-cat: -------------------------------------------------------------------------------- 1 | Invalid category 'chnge' in '--color-map="chnge:magenta,description:cyan_bold"'. Valid categories are: add, change, description, line-numbers, meta, permissions, separator, subtract. 2 | -------------------------------------------------------------------------------- /tests/gold-subcolors-bad-color: -------------------------------------------------------------------------------- 1 | Invalid color 'mageta' in '--color-map="change:mageta,description:cyan_bold"'. Valid colors are: [0;30mblack[m, [1;30mblack_bold[m, [0;34mblue[m, [1;34mblue_bold[m, [0;36mcyan[m, [1;36mcyan_bold[m, [0;32mgreen[m, [1;32mgreen_bold[m, [0;35mmagenta[m, [1;35mmagenta_bold[m, [mnone[m, [0;31mred[m, [1;31mred_bold[m, [0;37mwhite[m, [1;37mwhite_bold[m, [0;33myellow[m, [1;33myellow_bold[m. 2 | -------------------------------------------------------------------------------- /tests/gold-subcolors-bad-fmt: -------------------------------------------------------------------------------- 1 | Invalid color 'magenta:gold' in '--color-map="change:magenta:gold,description:cyan_bold"'. Valid colors are: [0;30mblack[m, [1;30mblack_bold[m, [0;34mblue[m, [1;34mblue_bold[m, [0;36mcyan[m, [1;36mcyan_bold[m, [0;32mgreen[m, [1;32mgreen_bold[m, [0;35mmagenta[m, [1;35mmagenta_bold[m, [mnone[m, [0;31mred[m, [1;31mred_bold[m, [0;37mwhite[m, [1;37mwhite_bold[m, [0;33myellow[m, [1;33myellow_bold[m. 2 | -------------------------------------------------------------------------------- /tests/gold-tabs-4.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-8.txt[m [0;34mtests/input-9.txt[m 2 | a[1;33mb[mc d[1;33me[mf a[1;33mQ[mc d[1;33mQ[mf 3 | -------------------------------------------------------------------------------- /tests/gold-tabs-default.txt: -------------------------------------------------------------------------------- 1 | [0;34mtests/input-8.txt[m [0;34mtests/input-9.txt[m 2 | a[1;33mb[mc d[1;33me[mf a[1;33mQ[mc d[1;33mQ[mf 3 | -------------------------------------------------------------------------------- /tests/input-1.txt: -------------------------------------------------------------------------------- 1 | 测试行abc测试测试行abc,测试测试行abc测试测试行abc测试测试行abc测试测试行abc测试测试行abc测试测试行abc测试测试行abc测试 2 | -------------------------------------------------------------------------------- /tests/input-10.txt: -------------------------------------------------------------------------------- 1 | void main () 2 | { 3 | int x; 4 | int y; 5 | } 6 | -------------------------------------------------------------------------------- /tests/input-11.txt: -------------------------------------------------------------------------------- 1 | void main () 2 | { 3 | int x; 4 | int y; 5 | } 6 | 7 | void foo () 8 | { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/input-2.txt: -------------------------------------------------------------------------------- 1 | 测试行adc测试测试行adc,测试测试行adc测试测试行adc测试测试行adc测试测试行adc测试测试行adc测试测试行adc测试测试行adc测试 2 | -------------------------------------------------------------------------------- /tests/input-3.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffkaufman/icdiff/7b3692c79555e981e3cdc6da1fdfabf5e7b0a4ea/tests/input-3.txt -------------------------------------------------------------------------------- /tests/input-4-cr.txt: -------------------------------------------------------------------------------- 1 | #input, #button { 2 | width: 350px; 3 | height: 40px; 4 | margin: 0px; 5 | padding: 0px; 6 | margin-bottom: 15px; 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /tests/input-4-partial-cr.txt: -------------------------------------------------------------------------------- 1 | #input, #button { 2 | width: 350px; 3 | height: 40px; 4 | margin: 0px; 5 | padding: 0px; 6 | margin-bottom: 15px; 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /tests/input-4.txt: -------------------------------------------------------------------------------- 1 | #input, #button { 2 | width: 350px; 3 | height: 40px; 4 | margin: 0px; 5 | padding: 0px; 6 | margin-bottom: 15px; 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /tests/input-5-cr.txt: -------------------------------------------------------------------------------- 1 | #input, #button { 2 | width: 400px; 3 | height: 40px; 4 | font-size: 30px; 5 | margin: 0; 6 | padding: 0; 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /tests/input-5.txt: -------------------------------------------------------------------------------- 1 | #input, #button { 2 | width: 400px; 3 | height: 40px; 4 | font-size: 30px; 5 | margin: 0; 6 | padding: 0; 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /tests/input-6.txt: -------------------------------------------------------------------------------- 1 | a 2 | b 3 | c 4 | d 5 | e 6 | f 7 | g 8 | h 9 | i 10 | j 11 | k 12 | l 13 | m 14 | n 15 | o 16 | p 17 | q 18 | r 19 | s 20 | t 21 | u 22 | z 23 | w 24 | x 25 | y 26 | z 27 | -------------------------------------------------------------------------------- /tests/input-7.txt: -------------------------------------------------------------------------------- 1 | a 2 | b 3 | c 4 | d 5 | e 6 | f 7 | g 8 | h 9 | i 10 | j 11 | k 12 | l 13 | m 14 | m 15 | m 16 | n 17 | o 18 | p 19 | q 20 | r 21 | s 22 | t 23 | u 24 | z 25 | w 26 | x 27 | y 28 | z 29 | -------------------------------------------------------------------------------- /tests/input-8.txt: -------------------------------------------------------------------------------- 1 | abc def 2 | -------------------------------------------------------------------------------- /tests/input-9.txt: -------------------------------------------------------------------------------- 1 | aQc dQf 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/a/1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffkaufman/icdiff/7b3692c79555e981e3cdc6da1fdfabf5e7b0a4ea/tests/test-with-exclude/a/1 -------------------------------------------------------------------------------- /tests/test-with-exclude/a/c/e: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/a/c/f: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/a/exclude/text.txt: -------------------------------------------------------------------------------- 1 | excluded a 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/a/j: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/b/1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffkaufman/icdiff/7b3692c79555e981e3cdc6da1fdfabf5e7b0a4ea/tests/test-with-exclude/b/1 -------------------------------------------------------------------------------- /tests/test-with-exclude/b/c/e: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/b/c/f: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/b/c/g: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/b/c/h: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/b/d/q: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/b/exclude/text.txt: -------------------------------------------------------------------------------- 1 | excluded b 2 | -------------------------------------------------------------------------------- /tests/test-with-exclude/b/i: -------------------------------------------------------------------------------- 1 | 6 2 | --------------------------------------------------------------------------------