├── .gitignore ├── .travis.yml ├── LICENSE ├── NOTICE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main └── java │ └── io │ └── reflectoring │ └── diffparser │ ├── api │ ├── DiffParser.java │ ├── UnifiedDiffParser.java │ └── model │ │ ├── Diff.java │ │ ├── Hunk.java │ │ ├── Line.java │ │ └── Range.java │ └── unified │ ├── ParseWindow.java │ ├── ParserState.java │ └── ResizingParseWindow.java └── test ├── java └── io │ └── reflectoring │ └── diffparser │ └── unified │ ├── GitDiffTest.java │ ├── SvnDiffTest.java │ └── TortoiseDiffTest.java └── resources ├── io └── reflectoring │ └── diffparser │ └── unified │ ├── svn.diff │ └── tortoise.diff ├── log4j.xml └── org └── wickedsource └── diffparser └── unified └── git.diff /.gitignore: -------------------------------------------------------------------------------- 1 | /diffparser.iml 2 | /build 3 | /.idea 4 | /.gradle 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | install: true 3 | 4 | sudo: false 5 | addons: 6 | apt: 7 | packages: 8 | - oracle-java8-installer 9 | 10 | before_install: 11 | - chmod +x gradlew 12 | 13 | stages: 14 | - name: build 15 | - name: snapshot 16 | # only publish snapshots from "master" branch and not in pull requests 17 | if: branch = master AND type IN (push) 18 | - name: release 19 | # only publish releases from "release" branch and not in pull requests 20 | if: branch = release AND type IN (push) 21 | 22 | jobs: 23 | include: 24 | 25 | # run gradle build 26 | - stage: build 27 | script: ./gradlew build 28 | 29 | # publish snapshot to oss.jfrog.org 30 | - stage: snapshot 31 | script: ./gradlew artifactoryPublish -x test -Dsnapshot=true -Dbintray.user=$BINTRAY_USER -Dbintray.key=$BINTRAY_KEY -Dbuild.number=$TRAVIS_BUILD_NUMBER 32 | 33 | # release a new stable version to bintray 34 | - stage: release 35 | script: ./gradlew bintrayUpload -x test -Dbintray.user=$BINTRAY_USER -Dbintray.key=$BINTRAY_KEY -Dbuild.number=$TRAVIS_BUILD_NUMBER 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2013-2017 Tom Hombergs (tom.hombergs@gmail.com | http://reflectoring.io) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### diffparser 2 | 3 | [![Build Status](https://travis-ci.org/thombergs/diffparser.svg?branch=master)](https://travis-ci.org/thombergs/diffparser) 4 | 5 | Parse unified diffs with Java. 6 | 7 | ### Code example 8 | ``` 9 | DiffParser parser = new UnifiedDiffParser(); 10 | InputStream in = new FileInputStream("/path/to/file.diff"); 11 | List diff = parser.parse(in); 12 | ``` 13 | 14 | ### What Diff formats can be parsed? 15 | Currently, the only implementation of the DiffParser interface is UnifiedDiffParser, which supports parsing of diffs like the following: 16 | ``` 17 | Modified: trunk/test1.txt 18 | =================================================================== 19 | --- /trunk/test1.txt 2013-10-23 19:41:56 UTC (rev 46) 20 | +++ /trunk/test1.txt 2013-10-23 19:44:39 UTC (rev 47) 21 | @@ -1,4 +1,3 @@ 22 | test1 23 | -test1 24 | +test234 25 | 26 | -test1 27 | \ No newline at end of file 28 | @@ -5,9 +6,10 @@ 29 | -test1 30 | -test1 31 | +aösdhasd 32 | +asdasd 33 | ``` 34 | 35 | An input stream may contain several sections like the above, delimited by an empty line. Each such section will be parsed into an object 36 | of class Diff. 37 | 38 | ### Latest Stable Release 39 | 40 | #### Download 41 | 42 | [ ![Download](https://api.bintray.com/packages/reflectoring/maven-releases/diffparser/images/download.svg) ](https://bintray.com/reflectoring/maven-releases/diffparser/_latestVersion) 43 | 44 | #### Maven 45 | ```xml 46 | ... 47 | 48 | 49 | jcenter 50 | https://jcenter.bintray.com/ 51 | 52 | 53 | 54 | 55 | 56 | io.reflectoring.diffparser 57 | diffparser 58 | 1.4 59 | 60 | 61 | ... 62 | ``` 63 | 64 | #### Gradle 65 | ```groovy 66 | repositories { 67 | jcenter() 68 | } 69 | 70 | dependencies { 71 | compile('io.reflectoring.diffparser:diffparser:1.4') 72 | } 73 | ``` 74 | 75 | ### Snapshots 76 | 77 | You can access the latest snapshot by adding "-SNAPSHOT" to the version number and 78 | adding the repository `https://oss.jfrog.org/artifactory/oss-snapshot-local` 79 | to your build. 80 | 81 | You can also reference a specific snapshot like `1.4-20171220.195055-1`. 82 | Here's the [list of snapshot versions](https://oss.jfrog.org/webapp/#/artifacts/browse/tree/General/oss-snapshot-local/io/reflectoring/diffparser/diffparser). 83 | 84 | #### Maven 85 | ```xml 86 | ... 87 | 88 | 89 | oss-snapshot-local 90 | https://oss.jfrog.org/webapp/#/artifacts/browse/tree/General/oss-snapshot-local/io/reflectoring/diffparser/diffparser 91 | 92 | 93 | 94 | 95 | 96 | io.reflectoring.diffparser 97 | diffparser 98 | 1.5-SNAPSHOT 99 | 100 | 101 | ... 102 | ``` 103 | 104 | #### Gradle 105 | ```groovy 106 | repositories { 107 | maven { url 'https://oss.jfrog.org/artifactory/oss-snapshot-local' } 108 | } 109 | 110 | dependencies { 111 | compile('io.reflectoring.diffparser:diffparser:1.5-SNAPSHOT') 112 | } 113 | ``` 114 | 115 | 116 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java" 3 | id "com.jfrog.artifactory" version "4.5.4" 4 | id "com.jfrog.bintray" version "1.7.3" 5 | id "maven-publish" 6 | } 7 | 8 | repositories { 9 | mavenLocal() 10 | mavenCentral() 11 | jcenter() 12 | } 13 | 14 | // run gradle with "-Dsnapshot=true" to automatically append "-SNAPSHOT" to the version 15 | version = '1.5' + (Boolean.valueOf(System.getProperty("snapshot")) ? "-SNAPSHOT" : "") 16 | sourceCompatibility = 1.8 17 | 18 | ext{ 19 | bintrayUser = System.getProperty("bintray.user") 20 | bintrayKey = System.getProperty("bintray.key") 21 | buildNumber = System.getProperty("build.number") 22 | } 23 | 24 | dependencies { 25 | compile('org.slf4j:slf4j-api:1.7.25') 26 | testCompile('org.slf4j:slf4j-log4j12:1.7.25') 27 | testCompile('org.testng:testng:6.8.7') 28 | } 29 | 30 | task sourcesJar(type: Jar, dependsOn: classes) { 31 | classifier = 'sources' 32 | from sourceSets.main.allSource 33 | } 34 | 35 | javadoc.failOnError = false 36 | task javadocJar(type: Jar, dependsOn: javadoc) { 37 | classifier = 'javadoc' 38 | from javadoc.destinationDir 39 | } 40 | 41 | artifacts { 42 | archives sourcesJar 43 | archives javadocJar 44 | } 45 | 46 | def pomConfig = { 47 | licenses { 48 | license { 49 | name "The Apache Software License, Version 2.0" 50 | url "http://www.apache.org/licenses/LICENSE-2.0.txt" 51 | distribution "repo" 52 | } 53 | } 54 | developers { 55 | developer { 56 | id "thombergs" 57 | name "Tom Hombergs" 58 | email "tom.hombergs@gmail.com" 59 | } 60 | } 61 | 62 | scm { 63 | url "https://github.com/thombergs/diffparser" 64 | } 65 | } 66 | 67 | publishing { 68 | publications { 69 | mavenPublication(MavenPublication) { 70 | from components.java 71 | artifact sourcesJar { 72 | classifier "sources" 73 | } 74 | artifact javadocJar { 75 | classifier "javadoc" 76 | } 77 | groupId 'io.reflectoring.diffparser' 78 | artifactId 'diffparser' 79 | version project.version 80 | pom.withXml { 81 | def root = asNode() 82 | root.appendNode('description', 'Parse textual Diffs with Java') 83 | root.appendNode('name', 'DiffParser') 84 | root.appendNode('url', 'https://github.com/thombergs/diffparser') 85 | root.children().last() + pomConfig 86 | } 87 | } 88 | } 89 | } 90 | 91 | artifactory { 92 | contextUrl = 'http://oss.jfrog.org' 93 | publish { 94 | repository { 95 | repoKey = 'oss-snapshot-local' 96 | username = bintrayUser 97 | password = bintrayKey 98 | } 99 | defaults { 100 | publications('mavenPublication') 101 | publishArtifacts = true 102 | publishPom = true 103 | properties = [ 104 | 'build.number': buildNumber, 105 | 'build.name': 'diffparser' 106 | ] 107 | } 108 | } 109 | resolve { 110 | repoKey = 'jcenter' 111 | } 112 | clientConfig.info.setBuildNumber(buildNumber) 113 | clientConfig.info.setBuildName('diffparser') 114 | } 115 | 116 | bintray { 117 | user = bintrayUser 118 | key = bintrayKey 119 | publications = ['mavenPublication'] 120 | 121 | pkg { 122 | repo = 'maven-releases' 123 | name = 'diffparser' 124 | userOrg = 'reflectoring' 125 | licenses = ['Apache-2.0'] 126 | vcsUrl = 'https://github.com/thombergs/diffparser' 127 | version { 128 | name = project.version 129 | desc = "build ${buildNumber}" 130 | released = new Date() 131 | gpg { 132 | sign = true 133 | } 134 | } 135 | } 136 | 137 | publish = true 138 | } 139 | 140 | 141 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thombergs/diffparser/699921a49bfe96bac5be4d460edf450741fdb225/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/api/DiffParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.api; 17 | 18 | import io.reflectoring.diffparser.api.model.Diff; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.util.List; 24 | 25 | /** 26 | * Interface to a parser that parses a textual diff between two text files. See the javadoc of the implementation you want to use to see 27 | * what diff format it is expecting as input. 28 | * 29 | * @author Tom Hombergs 30 | */ 31 | @SuppressWarnings("UnusedDeclaration") 32 | public interface DiffParser { 33 | 34 | /** 35 | * Constructs a list of Diffs from a textual InputStream. 36 | * 37 | * @param in the input stream to parse 38 | * @return list of Diff objects parsed from the InputStream. 39 | */ 40 | List parse(InputStream in); 41 | 42 | /** 43 | * Constructs a list of Diffs from a textual byte array. 44 | * 45 | * @param bytes the byte array to parse 46 | * @return list of Diff objects parsed from the byte array. 47 | */ 48 | List parse(byte[] bytes); 49 | 50 | /** 51 | * Constructs a list of Diffs from a textual File 52 | * 53 | * @param file the file to parse 54 | * @return list of Diff objects parsed from the File. 55 | */ 56 | List parse(File file) throws IOException; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/api/UnifiedDiffParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.api; 17 | 18 | import io.reflectoring.diffparser.api.model.Diff; 19 | import io.reflectoring.diffparser.api.model.Hunk; 20 | import io.reflectoring.diffparser.api.model.Line; 21 | import io.reflectoring.diffparser.api.model.Range; 22 | import io.reflectoring.diffparser.unified.ParserState; 23 | import io.reflectoring.diffparser.unified.ResizingParseWindow; 24 | 25 | import java.io.*; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | import java.util.regex.Matcher; 29 | import java.util.regex.Pattern; 30 | 31 | /** 32 | * A parser that parses a unified diff from text into a {@link Diff} data structure. 33 | *

34 | * An example of a unified diff this parser can handle is the following: 35 | *

 36 |  * Modified: trunk/test1.txt
 37 |  * ===================================================================
 38 |  * --- /trunk/test1.txt	2013-10-23 19:41:56 UTC (rev 46)
 39 |  * +++ /trunk/test1.txt	2013-10-23 19:44:39 UTC (rev 47)
 40 |  * @@ -1,4 +1,3 @@
 41 |  * test1
 42 |  * -test1
 43 |  * +test234
 44 |  * -test1
 45 |  * \ No newline at end of file
 46 |  * @@ -5,9 +6,10 @@
 47 |  * -test1
 48 |  * -test1
 49 |  * +test2
 50 |  * +test2
 51 |  * 
52 | * Note that the TAB character and date after the file names are not being parsed but instead cut off. 53 | */ 54 | public class UnifiedDiffParser implements DiffParser { 55 | public static final Pattern LINE_RANGE_PATTERN = Pattern.compile("^.*-([0-9]+)(?:,([0-9]+))? \\+([0-9]+)(?:,([0-9]+))?.*$"); 56 | 57 | @Override 58 | public List parse(InputStream in) { 59 | ResizingParseWindow window = new ResizingParseWindow(in); 60 | ParserState state = ParserState.INITIAL; 61 | List parsedDiffs = new ArrayList<>(); 62 | Diff currentDiff = new Diff(); 63 | String currentLine; 64 | while ((currentLine = window.slideForward()) != null) { 65 | state = state.nextState(window); 66 | switch (state) { 67 | case INITIAL: 68 | // nothing to do 69 | break; 70 | case HEADER: 71 | parseHeader(currentDiff, currentLine); 72 | break; 73 | case FROM_FILE: 74 | parseFromFile(currentDiff, currentLine); 75 | break; 76 | case TO_FILE: 77 | parseToFile(currentDiff, currentLine); 78 | break; 79 | case HUNK_START: 80 | parseHunkStart(currentDiff, currentLine); 81 | break; 82 | case FROM_LINE: 83 | parseFromLine(currentDiff, currentLine); 84 | break; 85 | case TO_LINE: 86 | parseToLine(currentDiff, currentLine); 87 | break; 88 | case NEUTRAL_LINE: 89 | parseNeutralLine(currentDiff, currentLine); 90 | break; 91 | case END: 92 | parsedDiffs.add(currentDiff); 93 | currentDiff = new Diff(); 94 | break; 95 | default: 96 | throw new IllegalStateException(String.format("Illegal parser state '%s", state)); 97 | } 98 | } 99 | 100 | return parsedDiffs; 101 | } 102 | 103 | private void parseNeutralLine(Diff currentDiff, String currentLine) { 104 | Line line = new Line(Line.LineType.NEUTRAL, currentLine); 105 | currentDiff.getLatestHunk().getLines().add(line); 106 | } 107 | 108 | private void parseToLine(Diff currentDiff, String currentLine) { 109 | Line toLine = new Line(Line.LineType.TO, currentLine.substring(1)); 110 | currentDiff.getLatestHunk().getLines().add(toLine); 111 | } 112 | 113 | private void parseFromLine(Diff currentDiff, String currentLine) { 114 | Line fromLine = new Line(Line.LineType.FROM, currentLine.substring(1)); 115 | currentDiff.getLatestHunk().getLines().add(fromLine); 116 | } 117 | 118 | private void parseHunkStart(Diff currentDiff, String currentLine) { 119 | Matcher matcher = LINE_RANGE_PATTERN.matcher(currentLine); 120 | if (matcher.matches()) { 121 | String range1Start = matcher.group(1); 122 | String range1Count = (matcher.group(2) != null) ? matcher.group(2) : "1"; 123 | Range fromRange = new Range(Integer.valueOf(range1Start), Integer.valueOf(range1Count)); 124 | 125 | String range2Start = matcher.group(3); 126 | String range2Count = (matcher.group(4) != null) ? matcher.group(4) : "1"; 127 | Range toRange = new Range(Integer.valueOf(range2Start), Integer.valueOf(range2Count)); 128 | 129 | Hunk hunk = new Hunk(); 130 | hunk.setFromFileRange(fromRange); 131 | hunk.setToFileRange(toRange); 132 | currentDiff.getHunks().add(hunk); 133 | } else { 134 | throw new IllegalStateException(String.format("No line ranges found in the following hunk start line: '%s'. Expected something " + 135 | "like '-1,5 +3,5'.", currentLine)); 136 | } 137 | } 138 | 139 | private void parseToFile(Diff currentDiff, String currentLine) { 140 | currentDiff.setToFileName(cutAfterTab(currentLine.substring(4))); 141 | } 142 | 143 | private void parseFromFile(Diff currentDiff, String currentLine) { 144 | currentDiff.setFromFileName(cutAfterTab(currentLine.substring(4))); 145 | } 146 | 147 | /** 148 | * Cuts a TAB and all following characters from a String. 149 | */ 150 | private String cutAfterTab(String line) { 151 | Pattern p = Pattern.compile("^(.*)\\t.*$"); 152 | Matcher matcher = p.matcher(line); 153 | if (matcher.matches()) { 154 | return matcher.group(1); 155 | } else { 156 | return line; 157 | } 158 | } 159 | 160 | private void parseHeader(Diff currentDiff, String currentLine) { 161 | currentDiff.getHeaderLines().add(currentLine); 162 | } 163 | 164 | 165 | @Override 166 | public List parse(byte[] bytes) { 167 | return parse(new ByteArrayInputStream(bytes)); 168 | } 169 | 170 | @Override 171 | public List parse(File file) throws IOException { 172 | FileInputStream in = new FileInputStream(file); 173 | try{ 174 | return parse(in); 175 | } finally { 176 | in.close(); 177 | } 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/api/model/Diff.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.api.model; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Represents a Diff between two files. 23 | * 24 | * @author Tom Hombergs 25 | */ 26 | @SuppressWarnings("UnusedDeclaration") 27 | public class Diff { 28 | 29 | private String fromFileName; 30 | 31 | private String toFileName; 32 | 33 | private List headerLines = new ArrayList<>(); 34 | 35 | private List hunks = new ArrayList<>(); 36 | 37 | /** 38 | * The header lines of the diff. These lines are purely informational and are not parsed. 39 | * 40 | * @return the list of header lines. 41 | */ 42 | public List getHeaderLines() { 43 | return headerLines; 44 | } 45 | 46 | public void setHeaderLines(List headerLines) { 47 | this.headerLines = headerLines; 48 | } 49 | 50 | /** 51 | * Gets the name of the first file that was compared with this Diff (the file "from" which the changes were made, 52 | * i.e. the "left" file of the diff). 53 | * 54 | * @return the name of the "from"-file. 55 | */ 56 | public String getFromFileName() { 57 | return fromFileName; 58 | } 59 | 60 | /** 61 | * Gets the name of the second file that was compared with this Diff (the file "to" which the changes were made, 62 | * i.e. the "right" file of the diff). 63 | * 64 | * @return the name of the "to"-file. 65 | */ 66 | public String getToFileName() { 67 | return toFileName; 68 | } 69 | 70 | /** 71 | * The list if all {@link Hunk}s which contain all changes that are part of this Diff. 72 | * 73 | * @return list of all Hunks that are part of this Diff. 74 | */ 75 | public List getHunks() { 76 | return hunks; 77 | } 78 | 79 | public void setFromFileName(String fromFileName) { 80 | this.fromFileName = fromFileName; 81 | } 82 | 83 | public void setToFileName(String toFileName) { 84 | this.toFileName = toFileName; 85 | } 86 | 87 | public void setHunks(List hunks) { 88 | this.hunks = hunks; 89 | } 90 | 91 | /** 92 | * Gets the last {@link Hunk} of changes that is part of this Diff. 93 | * 94 | * @return the last {@link Hunk} that has been added to this Diff. 95 | */ 96 | public Hunk getLatestHunk() { 97 | return hunks.get(hunks.size() - 1); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/api/model/Hunk.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.api.model; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Represents a "hunk" of changes made to a file. 23 | *

24 | * A Hunk consists of one or more lines that either exist only in the first file ("from line"), only in the second file ("to line") or in 25 | * both files ("neutral line"). Additionally, it contains information about which excerpts of the compared files are compared in this 26 | * Hunk in the form of line ranges. 27 | * 28 | * @author Tom Hombergs 29 | */ 30 | @SuppressWarnings("UnusedDeclaration") 31 | public class Hunk { 32 | 33 | private Range fromFileRange; 34 | 35 | private Range toFileRange; 36 | 37 | private List lines = new ArrayList<>(); 38 | 39 | /** 40 | * The range of line numbers that this Hunk spans in the first file of the Diff. 41 | * 42 | * @return range of line numbers in the first file (the "from" file). 43 | */ 44 | public Range getFromFileRange() { 45 | return fromFileRange; 46 | } 47 | 48 | /** 49 | * The range of line numbers that this Hunk spans in the second file of the Diff. 50 | * 51 | * @return range of line numbers in the second file (the "to" file). 52 | */ 53 | public Range getToFileRange() { 54 | return toFileRange; 55 | } 56 | 57 | /** 58 | * The lines that are part of this Hunk. 59 | * 60 | * @return lines of this Hunk. 61 | */ 62 | public List getLines() { 63 | return lines; 64 | } 65 | 66 | public void setFromFileRange(Range fromFileRange) { 67 | this.fromFileRange = fromFileRange; 68 | } 69 | 70 | public void setToFileRange(Range toFileRange) { 71 | this.toFileRange = toFileRange; 72 | } 73 | 74 | public void setLines(List lines) { 75 | this.lines = lines; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/api/model/Line.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.api.model; 17 | 18 | /** 19 | * Represents a line of a Diff. A line is either contained in both files ("neutral"), only in the first file ("from"), 20 | * or only in the second file ("to"). 21 | * 22 | * @author Tom Hombergs 23 | */ 24 | @SuppressWarnings("UnusedDeclaration") 25 | public class Line { 26 | 27 | /** 28 | * All possible types a line can have. 29 | */ 30 | public enum LineType { 31 | 32 | /** 33 | * This line is only contained in the first file of the Diff (the "from" file). 34 | */ 35 | FROM, 36 | 37 | /** 38 | * This line is only contained in the second file of the Diff (the "to" file). 39 | */ 40 | TO, 41 | 42 | /** 43 | * This line is contained in both filed of the Diff, and is thus considered "neutral". 44 | */ 45 | NEUTRAL 46 | 47 | } 48 | 49 | private final LineType lineType; 50 | 51 | private final String content; 52 | 53 | public Line(LineType lineType, String content) { 54 | this.lineType = lineType; 55 | this.content = content; 56 | } 57 | 58 | /** 59 | * The type of this line. 60 | * 61 | * @return the type of this line. 62 | */ 63 | public LineType getLineType() { 64 | return lineType; 65 | } 66 | 67 | /** 68 | * The actual content of the line as String. 69 | * 70 | * @return the actual line content. 71 | */ 72 | public String getContent() { 73 | return content; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/api/model/Range.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.api.model; 17 | 18 | /** 19 | * Represents a range of line numbers that spans a window on a text file. 20 | * 21 | * @author Tom Hombergs 22 | */ 23 | public class Range { 24 | 25 | private final int lineStart; 26 | 27 | private final int lineCount; 28 | 29 | public Range(int lineStart, int lineCount) { 30 | this.lineStart = lineStart; 31 | this.lineCount = lineCount; 32 | } 33 | 34 | /** 35 | * The line number at which this range starts (inclusive). 36 | * 37 | * @return the line number at which this range starts. 38 | */ 39 | public int getLineStart() { 40 | return lineStart; 41 | } 42 | 43 | /** 44 | * The count of lines in this range. 45 | * 46 | * @return the count of lines in this range. 47 | */ 48 | public int getLineCount() { 49 | return lineCount; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/unified/ParseWindow.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.unified; 17 | 18 | public interface ParseWindow { 19 | 20 | /** 21 | * Returns the line currently focused by this window. This is actually the 22 | * same line as returned by {@link #slideForward()} but calling 23 | * this method does not slide the window forward a step. 24 | * 25 | * @return the currently focused line. 26 | */ 27 | String getFocusLine(); 28 | 29 | /** 30 | * Returns the number of the current line within the whole document. 31 | * 32 | * @return the line number. 33 | */ 34 | @SuppressWarnings("UnusedDeclaration") 35 | int getFocusLineNumber(); 36 | 37 | /** 38 | * Slides the window forward one line. 39 | * 40 | * @return the next line that is in the focus of this window or null if the 41 | * end of the stream has been reached. 42 | */ 43 | String slideForward(); 44 | 45 | /** 46 | * Looks ahead from the current line and retrieves a line that will be the 47 | * focus line after the window has slided forward. 48 | * 49 | * @param distance the number of lines to look ahead. Must be greater or equal 0. 50 | * 0 returns the focus line. 1 returns the first line after the 51 | * current focus line and so on. Note that all lines up to the 52 | * returned line will be held in memory until the window has 53 | * slided past them, so be careful not to look ahead too far! 54 | * @return the line identified by the distance parameter that lies ahead of 55 | * the focus line. Returns null if the line cannot be read because 56 | * it lies behind the end of the stream. 57 | */ 58 | String getFutureLine(int distance); 59 | 60 | void addLine(int pos, String line); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/unified/ParserState.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.unified; 17 | 18 | import static io.reflectoring.diffparser.api.UnifiedDiffParser.LINE_RANGE_PATTERN; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | /** 24 | * State machine for a parser parsing a unified diff. 25 | * 26 | * @author Tom Hombergs 27 | */ 28 | public enum ParserState { 29 | 30 | /** 31 | * This is the initial state of the parser. 32 | */ 33 | INITIAL { 34 | @Override 35 | public ParserState nextState(ParseWindow window) { 36 | String line = window.getFocusLine(); 37 | if (matchesFromFilePattern(line)) { 38 | logTransition(line, INITIAL, FROM_FILE); 39 | return FROM_FILE; 40 | } else { 41 | logTransition(line, INITIAL, HEADER); 42 | return HEADER; 43 | } 44 | } 45 | }, 46 | 47 | /** 48 | * The parser is in this state if it is currently parsing a header line. 49 | */ 50 | HEADER { 51 | @Override 52 | public ParserState nextState(ParseWindow window) { 53 | String line = window.getFocusLine(); 54 | if (matchesFromFilePattern(line)) { 55 | logTransition(line, HEADER, FROM_FILE); 56 | return FROM_FILE; 57 | } else { 58 | logTransition(line, HEADER, HEADER); 59 | return HEADER; 60 | } 61 | } 62 | }, 63 | 64 | /** 65 | * The parser is in this state if it is currently parsing the line containing the "from" file. 66 | *

67 | * Example line:
68 | * {@code --- /path/to/file.txt} 69 | */ 70 | FROM_FILE { 71 | @Override 72 | public ParserState nextState(ParseWindow window) { 73 | String line = window.getFocusLine(); 74 | if (matchesToFilePattern(line)) { 75 | logTransition(line, FROM_FILE, TO_FILE); 76 | return TO_FILE; 77 | } else { 78 | throw new IllegalStateException("A FROM_FILE line ('---') must be directly followed by a TO_FILE line ('+++')!"); 79 | } 80 | } 81 | }, 82 | 83 | /** 84 | * The parser is in this state if it is currently parsing the line containing the "to" file. 85 | *

86 | * Example line:
87 | * {@code +++ /path/to/file.txt} 88 | */ 89 | TO_FILE { 90 | @Override 91 | public ParserState nextState(ParseWindow window) { 92 | String line = window.getFocusLine(); 93 | if (matchesHunkStartPattern(line)) { 94 | logTransition(line, TO_FILE, HUNK_START); 95 | return HUNK_START; 96 | } else { 97 | throw new IllegalStateException("A TO_FILE line ('+++') must be directly followed by a HUNK_START line ('@@')!"); 98 | } 99 | } 100 | }, 101 | 102 | /** 103 | * The parser is in this state if it is currently parsing a line containing the header of a hunk. 104 | *

105 | * Example line:
106 | * {@code @@ -1,5 +2,6 @@} 107 | */ 108 | HUNK_START { 109 | @Override 110 | public ParserState nextState(ParseWindow window) { 111 | String line = window.getFocusLine(); 112 | if (matchesFromLinePattern(line)) { 113 | logTransition(line, HUNK_START, FROM_LINE); 114 | return FROM_LINE; 115 | } else if (matchesToLinePattern(line)) { 116 | logTransition(line, HUNK_START, TO_LINE); 117 | return TO_LINE; 118 | } else { 119 | logTransition(line, HUNK_START, NEUTRAL_LINE); 120 | return NEUTRAL_LINE; 121 | } 122 | } 123 | }, 124 | 125 | /** 126 | * The parser is in this state if it is currently parsing a line containing a line that is in the first file, 127 | * but not the second (a "from" line). 128 | *

129 | * Example line:
130 | * {@code - only the dash at the start is important} 131 | */ 132 | FROM_LINE { 133 | @Override 134 | public ParserState nextState(ParseWindow window) { 135 | String line = window.getFocusLine(); 136 | if (matchesFromLinePattern(line)) { 137 | logTransition(line, FROM_LINE, FROM_LINE); 138 | return FROM_LINE; 139 | } else if (matchesToLinePattern(line)) { 140 | logTransition(line, FROM_LINE, TO_LINE); 141 | return TO_LINE; 142 | } else if (matchesEndPattern(line, window)) { 143 | logTransition(line, FROM_LINE, END); 144 | return END; 145 | } else if (matchesHunkStartPattern(line)) { 146 | logTransition(line, FROM_LINE, HUNK_START); 147 | return HUNK_START; 148 | } else { 149 | logTransition(line, FROM_LINE, NEUTRAL_LINE); 150 | return NEUTRAL_LINE; 151 | } 152 | } 153 | }, 154 | 155 | /** 156 | * The parser is in this state if it is currently parsing a line containing a line that is in the second file, 157 | * but not the first (a "to" line). 158 | *

159 | * Example line:
160 | * {@code + only the plus at the start is important} 161 | */ 162 | TO_LINE { 163 | @Override 164 | public ParserState nextState(ParseWindow window) { 165 | String line = window.getFocusLine(); 166 | if (matchesFromLinePattern(line)) { 167 | logTransition(line, TO_LINE, FROM_LINE); 168 | return FROM_LINE; 169 | } else if (matchesToLinePattern(line)) { 170 | logTransition(line, TO_LINE, TO_LINE); 171 | return TO_LINE; 172 | } else if (matchesEndPattern(line, window)) { 173 | logTransition(line, TO_LINE, END); 174 | return END; 175 | } else if (matchesHunkStartPattern(line)) { 176 | logTransition(line, TO_LINE, HUNK_START); 177 | return HUNK_START; 178 | } else { 179 | logTransition(line, TO_LINE, NEUTRAL_LINE); 180 | return NEUTRAL_LINE; 181 | } 182 | } 183 | }, 184 | 185 | /** 186 | * The parser is in this state if it is currently parsing a line that is contained in both files (a "neutral" line). This line can 187 | * contain any string. 188 | */ 189 | NEUTRAL_LINE { 190 | @Override 191 | public ParserState nextState(ParseWindow window) { 192 | String line = window.getFocusLine(); 193 | if (matchesFromLinePattern(line)) { 194 | logTransition(line, NEUTRAL_LINE, FROM_LINE); 195 | return FROM_LINE; 196 | } else if (matchesToLinePattern(line)) { 197 | logTransition(line, NEUTRAL_LINE, TO_LINE); 198 | return TO_LINE; 199 | } else if (matchesEndPattern(line, window)) { 200 | logTransition(line, NEUTRAL_LINE, END); 201 | return END; 202 | } else if (matchesHunkStartPattern(line)) { 203 | logTransition(line, NEUTRAL_LINE, HUNK_START); 204 | return HUNK_START; 205 | } else { 206 | logTransition(line, NEUTRAL_LINE, NEUTRAL_LINE); 207 | return NEUTRAL_LINE; 208 | } 209 | } 210 | }, 211 | 212 | /** 213 | * The parser is in this state if it is currently parsing a line that is the delimiter between two Diffs. This line is always a new 214 | * line. 215 | */ 216 | END { 217 | @Override 218 | public ParserState nextState(ParseWindow window) { 219 | String line = window.getFocusLine(); 220 | logTransition(line, END, INITIAL); 221 | return INITIAL; 222 | } 223 | }; 224 | 225 | protected static Logger logger = LoggerFactory.getLogger(ParserState.class); 226 | 227 | /** 228 | * Returns the next state of the state machine depending on the current state and the content of a window of lines around the line 229 | * that is currently being parsed. 230 | * 231 | * @param window the window around the line currently being parsed. 232 | * @return the next state of the state machine. 233 | */ 234 | public abstract ParserState nextState(ParseWindow window); 235 | 236 | protected void logTransition(String currentLine, ParserState fromState, ParserState toState) { 237 | logger.debug(String.format("%12s -> %12s: %s", fromState, toState, currentLine)); 238 | } 239 | 240 | protected boolean matchesFromFilePattern(String line) { 241 | return line.startsWith("---"); 242 | } 243 | 244 | protected boolean matchesToFilePattern(String line) { 245 | return line.startsWith("+++"); 246 | } 247 | 248 | protected boolean matchesFromLinePattern(String line) { 249 | return line.startsWith("-"); 250 | } 251 | 252 | protected boolean matchesToLinePattern(String line) { 253 | return line.startsWith("+"); 254 | } 255 | 256 | protected boolean matchesHunkStartPattern(String line) { 257 | return LINE_RANGE_PATTERN.matcher(line).matches(); 258 | } 259 | 260 | protected boolean matchesEndPattern(String line, ParseWindow window) { 261 | if ("".equals(line.trim())) { 262 | // We have a newline which might be the delimiter between two diffs. It may just be an empty line in the current diff or it 263 | // may be the delimiter to the next diff. This has to be disambiguated... 264 | int i = 1; 265 | String futureLine; 266 | while ((futureLine = window.getFutureLine(i)) != null) { 267 | if (matchesFromFilePattern(futureLine)) { 268 | // We found the start of a new diff without another newline in between. That makes the current line the delimiter 269 | // between this diff and the next. 270 | return true; 271 | } else if ("".equals(futureLine.trim())) { 272 | // We found another newline after the current newline without a start of a new diff in between. That makes the 273 | // current line just a newline within the current diff. 274 | return false; 275 | } else { 276 | i++; 277 | } 278 | } 279 | // We reached the end of the stream. 280 | return true; 281 | } else { 282 | // some diff tools like "svn diff" do not put an empty line between two diffs 283 | // we add that empty line and call the method again 284 | String nextFromFileLine = window.getFutureLine(3); 285 | if(nextFromFileLine != null && matchesFromFilePattern(nextFromFileLine)){ 286 | window.addLine(1, ""); 287 | return matchesEndPattern(line, window); 288 | }else{ 289 | return false; 290 | } 291 | } 292 | } 293 | 294 | 295 | } 296 | -------------------------------------------------------------------------------- /src/main/java/io/reflectoring/diffparser/unified/ResizingParseWindow.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 Tom Hombergs (tom.hombergs@gmail.com | http://wickedsource.org) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.reflectoring.diffparser.unified; 17 | 18 | import java.io.*; 19 | import java.util.ArrayList; 20 | import java.util.LinkedList; 21 | import java.util.List; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * A {@link ResizingParseWindow} slides through the lines of a input stream and 27 | * offers methods to get the currently focused line as well as upcoming lines. 28 | * It is backed by an automatically resizing {@link LinkedList} 29 | * 30 | * @author Tom Hombergs 31 | */ 32 | @SuppressWarnings("UnusedDeclaration") 33 | public class ResizingParseWindow implements ParseWindow { 34 | 35 | private BufferedReader reader; 36 | 37 | private LinkedList lineQueue = new LinkedList<>(); 38 | 39 | private int lineNumber = 0; 40 | 41 | private List ignorePatterns = new ArrayList<>(); 42 | 43 | private boolean isEndOfStream = false; 44 | 45 | public ResizingParseWindow(InputStream in) { 46 | Reader unbufferedReader = new InputStreamReader(in); 47 | this.reader = new BufferedReader(unbufferedReader); 48 | } 49 | 50 | public void addIgnorePattern(String ignorePattern) { 51 | this.ignorePatterns.add(Pattern.compile(ignorePattern)); 52 | } 53 | 54 | @Override 55 | public String getFutureLine(int distance) { 56 | try { 57 | resizeWindowIfNecessary(distance + 1); 58 | return lineQueue.get(distance); 59 | } catch (IndexOutOfBoundsException e) { 60 | return null; 61 | } 62 | } 63 | 64 | @Override 65 | public void addLine(int pos, String line) { 66 | lineQueue.add(pos, line); 67 | } 68 | 69 | /** 70 | * Resizes the sliding window to the given size, if necessary. 71 | * 72 | * @param newSize the new size of the window (i.e. the number of lines in the 73 | * window). 74 | */ 75 | private void resizeWindowIfNecessary(int newSize) { 76 | try { 77 | int numberOfLinesToLoad = newSize - this.lineQueue.size(); 78 | for (int i = 0; i < numberOfLinesToLoad; i++) { 79 | String nextLine = getNextLine(); 80 | if (nextLine != null) { 81 | lineQueue.addLast(nextLine); 82 | } else { 83 | throw new IndexOutOfBoundsException("End of stream has been reached!"); 84 | } 85 | } 86 | } catch (IOException e) { 87 | throw new RuntimeException(e); 88 | } 89 | } 90 | 91 | @Override 92 | public String slideForward() { 93 | try { 94 | lineQueue.pollFirst(); 95 | lineNumber++; 96 | if (lineQueue.isEmpty()) { 97 | String nextLine = getNextLine(); 98 | if (nextLine != null) { 99 | lineQueue.addLast(nextLine); 100 | } 101 | return nextLine; 102 | } else { 103 | return lineQueue.peekFirst(); 104 | } 105 | } catch (IOException e) { 106 | throw new RuntimeException(e); 107 | } 108 | } 109 | 110 | private String getNextLine() throws IOException { 111 | String nextLine = reader.readLine(); 112 | while (matchesIgnorePattern(nextLine)) { 113 | nextLine = reader.readLine(); 114 | } 115 | 116 | return getNextLineOrVirtualBlankLineAtEndOfStream(nextLine); 117 | } 118 | 119 | /** 120 | * Guarantees that a virtual blank line is injected at the end of the input 121 | * stream to ensure the parser attempts to transition to the {@code END} 122 | * state, if necessary, when the end of stream is reached. 123 | */ 124 | private String getNextLineOrVirtualBlankLineAtEndOfStream(String nextLine) { 125 | if ((nextLine == null) && !isEndOfStream) { 126 | isEndOfStream = true; 127 | return ""; 128 | } 129 | 130 | return nextLine; 131 | } 132 | 133 | private boolean matchesIgnorePattern(String line) { 134 | if (line == null) { 135 | return false; 136 | } else { 137 | for (Pattern pattern : ignorePatterns) { 138 | Matcher matcher = pattern.matcher(line); 139 | if (matcher.matches()) { 140 | return true; 141 | } 142 | } 143 | return false; 144 | } 145 | } 146 | 147 | @Override 148 | public String getFocusLine() { 149 | return lineQueue.element(); 150 | } 151 | 152 | @Override 153 | public int getFocusLineNumber() { 154 | return lineNumber; 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/test/java/io/reflectoring/diffparser/unified/GitDiffTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Crosskey Banking Solutions. All rights reserved. 3 | */ 4 | package io.reflectoring.diffparser.unified; 5 | 6 | import io.reflectoring.diffparser.api.DiffParser; 7 | import io.reflectoring.diffparser.api.UnifiedDiffParser; 8 | import io.reflectoring.diffparser.api.model.Diff; 9 | import io.reflectoring.diffparser.api.model.Hunk; 10 | import io.reflectoring.diffparser.api.model.Line; 11 | import java.io.InputStream; 12 | import java.util.List; 13 | 14 | import org.testng.annotations.Test; 15 | 16 | import static junit.framework.Assert.assertEquals; 17 | import static junit.framework.Assert.assertNotNull; 18 | 19 | public class GitDiffTest { 20 | 21 | @Test 22 | public void testParse() { 23 | // given 24 | DiffParser parser = new UnifiedDiffParser(); 25 | InputStream in = getClass().getResourceAsStream("git.diff"); 26 | 27 | // when 28 | List diffs = parser.parse(in); 29 | 30 | // then 31 | assertNotNull(diffs); 32 | assertEquals(3, diffs.size()); 33 | 34 | Diff diff1 = diffs.get(0); 35 | assertEquals("a/diffparser/pom.xml", diff1.getFromFileName()); 36 | assertEquals("b/diffparser/pom.xml", diff1.getToFileName()); 37 | assertEquals(2, diff1.getHunks().size()); 38 | 39 | List headerLines = diff1.getHeaderLines(); 40 | assertEquals(2, headerLines.size()); 41 | 42 | Hunk hunk1 = diff1.getHunks().get(0); 43 | assertEquals(6, hunk1.getFromFileRange().getLineStart()); 44 | assertEquals(7, hunk1.getFromFileRange().getLineCount()); 45 | assertEquals(6, hunk1.getToFileRange().getLineStart()); 46 | assertEquals(7, hunk1.getToFileRange().getLineCount()); 47 | 48 | List lines = hunk1.getLines(); 49 | assertEquals(8, lines.size()); 50 | assertEquals(Line.LineType.FROM, lines.get(3).getLineType()); 51 | assertEquals(Line.LineType.TO, lines.get(4).getLineType()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/io/reflectoring/diffparser/unified/SvnDiffTest.java: -------------------------------------------------------------------------------- 1 | package io.reflectoring.diffparser.unified; 2 | 3 | import io.reflectoring.diffparser.api.DiffParser; 4 | import io.reflectoring.diffparser.api.UnifiedDiffParser; 5 | import junit.framework.Assert; 6 | import org.testng.annotations.Test; 7 | import io.reflectoring.diffparser.api.model.Diff; 8 | import io.reflectoring.diffparser.api.model.Hunk; 9 | import io.reflectoring.diffparser.api.model.Line; 10 | 11 | import java.io.InputStream; 12 | import java.util.List; 13 | 14 | /** 15 | * Tests the DiffParser with a diff created by the "svn diff" command. 16 | */ 17 | public class SvnDiffTest { 18 | 19 | @Test 20 | public void testParse() throws Exception { 21 | // given 22 | DiffParser parser = new UnifiedDiffParser(); 23 | InputStream in = getClass().getResourceAsStream("svn.diff"); 24 | 25 | // when 26 | List diffs = parser.parse(in); 27 | 28 | // then 29 | Assert.assertNotNull(diffs); 30 | Assert.assertEquals(2, diffs.size()); 31 | 32 | Diff diff1 = diffs.get(0); 33 | Assert.assertEquals("UnifiedDiffParser.java", diff1.getFromFileName()); 34 | Assert.assertEquals("UnifiedDiffParser.java", diff1.getToFileName()); 35 | Assert.assertEquals(1, diff1.getHunks().size()); 36 | 37 | List headerLines = diff1.getHeaderLines(); 38 | Assert.assertEquals(2, headerLines.size()); 39 | 40 | Hunk hunk1 = diff1.getHunks().get(0); 41 | Assert.assertEquals(73, hunk1.getFromFileRange().getLineStart()); 42 | Assert.assertEquals(13, hunk1.getFromFileRange().getLineCount()); 43 | Assert.assertEquals(73, hunk1.getToFileRange().getLineStart()); 44 | Assert.assertEquals(13, hunk1.getToFileRange().getLineCount()); 45 | 46 | List lines = hunk1.getLines(); 47 | Assert.assertEquals(16, lines.size()); 48 | Assert.assertEquals(Line.LineType.TO, lines.get(3).getLineType()); 49 | Assert.assertEquals(Line.LineType.FROM, lines.get(7).getLineType()); 50 | Assert.assertEquals(Line.LineType.TO, lines.get(8).getLineType()); 51 | 52 | } 53 | 54 | @Test 55 | public void testParse_WhenHunkRangeLineCountNotSpecified_ShouldSetHunkRangeLineCountToOne() throws Exception { 56 | // given 57 | DiffParser parser = new UnifiedDiffParser(); 58 | String in = "" 59 | + "--- from 2015-12-21 17:53:29.082877088 -0500\n" 60 | + "+++ to 2015-12-21 08:41:52.663714666 -0500\n" 61 | + "@@ -10 +10 @@\n" 62 | + "-from\n" 63 | + "+to\n" 64 | + "\n"; 65 | 66 | // when 67 | List diffs = parser.parse(in.getBytes()); 68 | 69 | // then 70 | Assert.assertNotNull(diffs); 71 | Assert.assertEquals(1, diffs.size()); 72 | 73 | Diff diff1 = diffs.get(0); 74 | Assert.assertEquals(1, diff1.getHunks().size()); 75 | 76 | Hunk hunk1 = diff1.getHunks().get(0); 77 | Assert.assertEquals(1, hunk1.getFromFileRange().getLineCount()); 78 | Assert.assertEquals(1, hunk1.getToFileRange().getLineCount()); 79 | } 80 | 81 | @Test 82 | public void testParse_WhenInputDoesNotEndWithEmptyLine_ShouldTransitionToEndState() throws Exception { 83 | // given 84 | DiffParser parser = new UnifiedDiffParser(); 85 | String in = "" 86 | + "--- from 2015-12-21 17:53:29.082877088 -0500\n" 87 | + "+++ to 2015-12-21 08:41:52.663714666 -0500\n" 88 | + "@@ -10,1 +10,1 @@\n" 89 | + "-from\n" 90 | + "+to\n"; 91 | 92 | // when 93 | List diffs = parser.parse(in.getBytes()); 94 | 95 | // then 96 | Assert.assertNotNull(diffs); 97 | Assert.assertEquals(1, diffs.size()); 98 | 99 | Diff diff1 = diffs.get(0); 100 | Assert.assertEquals(1, diff1.getHunks().size()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/io/reflectoring/diffparser/unified/TortoiseDiffTest.java: -------------------------------------------------------------------------------- 1 | package io.reflectoring.diffparser.unified; 2 | 3 | import io.reflectoring.diffparser.api.DiffParser; 4 | import io.reflectoring.diffparser.api.UnifiedDiffParser; 5 | import io.reflectoring.diffparser.api.model.Hunk; 6 | import junit.framework.Assert; 7 | import org.testng.annotations.Test; 8 | import io.reflectoring.diffparser.api.model.Diff; 9 | import io.reflectoring.diffparser.api.model.Line; 10 | 11 | import java.io.InputStream; 12 | import java.util.List; 13 | 14 | /** 15 | * Tests the diffparser with a diff created by Tortoise SVN. 16 | */ 17 | public class TortoiseDiffTest { 18 | 19 | @Test 20 | public void testParse() throws Exception { 21 | // given 22 | DiffParser parser = new UnifiedDiffParser(); 23 | InputStream in = getClass().getResourceAsStream("tortoise.diff"); 24 | 25 | // when 26 | List diffs = parser.parse(in); 27 | 28 | // then 29 | Assert.assertNotNull(diffs); 30 | Assert.assertEquals(2, diffs.size()); 31 | 32 | Diff diff1 = diffs.get(0); 33 | Assert.assertEquals("/trunk/test1 - Kopie (2).txt", diff1.getFromFileName()); 34 | Assert.assertEquals("/trunk/test1 - Kopie (2).txt", diff1.getToFileName()); 35 | Assert.assertEquals(2, diff1.getHunks().size()); 36 | 37 | List headerLines = diff1.getHeaderLines(); 38 | Assert.assertEquals(2, headerLines.size()); 39 | 40 | Hunk hunk1 = diff1.getHunks().get(0); 41 | Assert.assertEquals(1, hunk1.getFromFileRange().getLineStart()); 42 | Assert.assertEquals(4, hunk1.getFromFileRange().getLineCount()); 43 | Assert.assertEquals(1, hunk1.getToFileRange().getLineStart()); 44 | Assert.assertEquals(3, hunk1.getToFileRange().getLineCount()); 45 | 46 | List lines = hunk1.getLines(); 47 | Assert.assertEquals(6, lines.size()); 48 | Assert.assertEquals(Line.LineType.NEUTRAL, lines.get(0).getLineType()); 49 | Assert.assertEquals(Line.LineType.FROM, lines.get(1).getLineType()); 50 | Assert.assertEquals(Line.LineType.TO, lines.get(2).getLineType()); 51 | Assert.assertEquals(Line.LineType.NEUTRAL, lines.get(3).getLineType()); 52 | Assert.assertEquals(Line.LineType.FROM, lines.get(4).getLineType()); 53 | Assert.assertEquals(Line.LineType.NEUTRAL, lines.get(5).getLineType()); 54 | 55 | } 56 | 57 | @Test 58 | public void testParse_WhenHunkRangeLineCountNotSpecified_ShouldSetHunkRangeLineCountToOne() throws Exception { 59 | // given 60 | DiffParser parser = new UnifiedDiffParser(); 61 | String in = "" 62 | + "--- from 2015-12-21 17:53:29.082877088 -0500\n" 63 | + "+++ to 2015-12-21 08:41:52.663714666 -0500\n" 64 | + "@@ -10 +10 @@\n" 65 | + "-from\n" 66 | + "+to\n" 67 | + "\n"; 68 | 69 | // when 70 | List diffs = parser.parse(in.getBytes()); 71 | 72 | // then 73 | Assert.assertNotNull(diffs); 74 | Assert.assertEquals(1, diffs.size()); 75 | 76 | Diff diff1 = diffs.get(0); 77 | Assert.assertEquals(1, diff1.getHunks().size()); 78 | 79 | Hunk hunk1 = diff1.getHunks().get(0); 80 | Assert.assertEquals(1, hunk1.getFromFileRange().getLineCount()); 81 | Assert.assertEquals(1, hunk1.getToFileRange().getLineCount()); 82 | } 83 | 84 | @Test 85 | public void testParse_WhenInputDoesNotEndWithEmptyLine_ShouldTransitionToEndState() throws Exception { 86 | // given 87 | DiffParser parser = new UnifiedDiffParser(); 88 | String in = "" 89 | + "--- from 2015-12-21 17:53:29.082877088 -0500\n" 90 | + "+++ to 2015-12-21 08:41:52.663714666 -0500\n" 91 | + "@@ -10,1 +10,1 @@\n" 92 | + "-from\n" 93 | + "+to\n"; 94 | 95 | // when 96 | List diffs = parser.parse(in.getBytes()); 97 | 98 | // then 99 | Assert.assertNotNull(diffs); 100 | Assert.assertEquals(1, diffs.size()); 101 | 102 | Diff diff1 = diffs.get(0); 103 | Assert.assertEquals(1, diff1.getHunks().size()); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/resources/io/reflectoring/diffparser/unified/svn.diff: -------------------------------------------------------------------------------- 1 | Index: UnifiedDiffParser.java 2 | =================================================================== 3 | --- UnifiedDiffParser.java (revision 3) 4 | +++ UnifiedDiffParser.java (working copy) 5 | @@ -73,13 +73,13 @@ 6 | parseFromFile(currentDiff, currentLine); 7 | break; 8 | case TO_FILE: 9 | + new line 10 | parseToFile(currentDiff, currentLine); 11 | break; 12 | case HUNK_START: 13 | - parseHunkStart(currentDiff, currentLine); 14 | + changedLine(currentDiff, currentLine); 15 | break; 16 | - case FROM_LINE: 17 | - parseFromLine(currentDiff, currentLine); 18 | + case FROM_LINE: 19 | break; 20 | case TO_LINE: 21 | parseToLine(currentDiff, currentLine); 22 | Index: UnifiedDiffParserTest.java 23 | =================================================================== 24 | --- UnifiedDiffParserTest.java (revision 3) 25 | +++ UnifiedDiffParserTest.java (working copy) 26 | @@ -17,8 +17,7 @@ 27 | public void testParse() throws Exception { 28 | // given 29 | DiffParser parser = new UnifiedDiffParser(); 30 | - InputStream in = getClass().getResourceAsStream("svnlog.diff"); 31 | - 32 | + 33 | // when 34 | List diffs = parser.parse(in); 35 | 36 | @@ -34,7 +33,8 @@ 37 | List headerLines = diff1.getHeaderLines(); 38 | Assert.assertEquals(2, headerLines.size()); 39 | 40 | - Hunk hunk1 = diff1.getHunks().get(0); 41 | + new line 42 | + new line 43 | Assert.assertEquals(1, hunk1.getFromFileRange().getLineStart()); 44 | Assert.assertEquals(4, hunk1.getFromFileRange().getLineCount()); 45 | Assert.assertEquals(1, hunk1.getToFileRange().getLineStart()); 46 | @@ -64,8 +64,8 @@ 47 | + "\n"; 48 | 49 | // when 50 | - List diffs = parser.parse(in.getBytes()); 51 | - 52 | + List diffs = change in line 53 | + 54 | // then 55 | Assert.assertNotNull(diffs); 56 | Assert.assertEquals(1, diffs.size()); 57 | -------------------------------------------------------------------------------- /src/test/resources/io/reflectoring/diffparser/unified/tortoise.diff: -------------------------------------------------------------------------------- 1 | Modified: trunk/test1 - Kopie (2).txt 2 | =================================================================== 3 | --- /trunk/test1 - Kopie (2).txt 2013-10-23 19:41:56 UTC (rev 46) 4 | +++ /trunk/test1 - Kopie (2).txt 2013-10-23 19:44:39 UTC (rev 47) 5 | @@ -1,4 +1,3 @@ 6 | test1 7 | -test1 8 | +test234 9 | 10 | -test1 11 | \ No newline at end of file 12 | @@ -5,9 +6,10 @@ 13 | -test1 14 | -test1 15 | +aösdhasd 16 | +asdasd 17 | 18 | Modified: trunk/test1.txt 19 | =================================================================== 20 | --- /trunk/test1.txt 2013-10-23 19:41:56 UTC (rev 46) 21 | +++ /trunk/test1.txt 2013-10-23 19:44:39 UTC (rev 47) 22 | @@ -1,4 +1,5 @@ 23 | -test1 24 | -test1 25 | +aösdhasd 26 | +asdasd 27 | 28 | -test1 29 | \ No newline at end of file 30 | +sadsad 31 | +asdasd 32 | \ No newline at end of file -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/org/wickedsource/diffparser/unified/git.diff: -------------------------------------------------------------------------------- 1 | diff --git a/diffparser/pom.xml b/diffparser/pom.xml 2 | index 5809534..4f4147a 100644 3 | --- a/diffparser/pom.xml 4 | +++ b/diffparser/pom.xml 5 | @@ -6,7 +6,7 @@ 6 | 7 | org.wickedsource 8 | diffparser 9 | - 1.1-SNAPSHOT 10 | + 1.0 11 | jar 12 | diffparser 13 | Parse textual diffs with Java. 14 | @@ -101,19 +101,6 @@ 15 | 16 | 17 | 18 | - 19 | - org.apache.maven.plugins 20 | - maven-gpg-plugin 21 | - 22 | - 23 | - sign-artifacts 24 | - verify 25 | - 26 | - sign 27 | - 28 | - 29 | - 30 | - 31 | 32 | 33 | 34 | diff --git a/diffparser/src/main/java/org/wickedsource/diffparser/api/UnifiedDiffParser.java b/diffparser/src/main/java/org/wickedsource/diffparser/api/UnifiedDiffParser.java 35 | index 0b53136..4dcdc64 100644 36 | --- a/diffparser/src/main/java/org/wickedsource/diffparser/api/UnifiedDiffParser.java 37 | +++ b/diffparser/src/main/java/org/wickedsource/diffparser/api/UnifiedDiffParser.java 38 | @@ -79,7 +79,7 @@ public class UnifiedDiffParser implements DiffParser { 39 | parseHunkStart(currentDiff, currentLine); 40 | break; 41 | case FROM_LINE: 42 | - parseFromLine(currentDiff, currentLine); 43 | + parseFromLime(currentDiff, currentLine); 44 | break; 45 | case TO_LINE: 46 | parseToLine(currentDiff, currentLine); 47 | @@ -109,7 +109,7 @@ public class UnifiedDiffParser implements DiffParser { 48 | currentDiff.getLatestHunk().getLines().add(toLine); 49 | } 50 | 51 | - private void parseFromLine(Diff currentDiff, String currentLine) { 52 | + private void parseFromLime(Diff currentDiff, String currentLine) { 53 | Line fromLine = new Line(Line.LineType.FROM, currentLine.substring(1)); 54 | currentDiff.getLatestHunk().getLines().add(fromLine); 55 | } 56 | diff --git a/diffparser/src/main/java/org/wickedsource/diffparser/unified/ResizingParseWindow.java b/diffparser/src/main/java/org/wickedsource/diffparser/unified/ResizingParseWindow.java 57 | index d9f7e97..15c23a7 100644 58 | --- a/diffparser/src/main/java/org/wickedsource/diffparser/unified/ResizingParseWindow.java 59 | +++ b/diffparser/src/main/java/org/wickedsource/diffparser/unified/ResizingParseWindow.java 60 | @@ -71,6 +71,7 @@ public class ResizingParseWindow implements ParseWindow { 61 | for (int i = 0; i < numberOfLinesToLoad; i++) { 62 | String nextLine = getNextLine(); 63 | if (nextLine != null) { 64 | + nextLine = nextLine.trim(); 65 | lineQueue.addLast(nextLine); 66 | } else { 67 | throw new IndexOutOfBoundsException("End of stream has been reached!"); 68 | @@ -89,6 +90,7 @@ public class ResizingParseWindow implements ParseWindow { 69 | if (lineQueue.isEmpty()) { 70 | String nextLine = getNextLine(); 71 | if (nextLine != null) { 72 | + nextLine = nextLine.trim(); 73 | lineQueue.addLast(nextLine); 74 | } 75 | return nextLine; 76 | --------------------------------------------------------------------------------