├── .gitignore ├── README.md ├── build.gradle ├── docs ├── INSTALL.md ├── LICENSE.txt ├── README.md ├── REQUIREMENTS.md ├── STATUS.md └── imgs │ ├── build_output.png │ ├── dependency_resolution.png │ ├── gradle_setting.png │ └── toy_example.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── RefactoringMiner-20200801.jar ├── classindex-3.8.jar ├── client-2.1.3-SNAPSHOT.jar ├── client.diff-2.1.3-SNAPSHOT.jar ├── commons-codec-1.10.jar ├── commons-io-2.0.1.jar ├── commons-lang3-3.1.jar ├── commons-logging-1.2.jar ├── core-2.1.3-SNAPSHOT.jar ├── gen.javaparser-2.1.3-SNAPSHOT.jar ├── gen.jdt-2.1.3-SNAPSHOT.jar ├── guava-27.0-jre.jar ├── javaparser-symbol-solver-model-3.13.6.jar ├── json-20190722-sources.jar ├── json-20190722.jar ├── org.eclipse.osgi-3.15.0.jar ├── ph-commons-9.3.4.jar └── simmetrics-core-3.2.3.jar ├── log4j.properties ├── settings.gradle └── src ├── MsgTemplate.json ├── main └── java │ └── com │ └── github │ └── smartcommit │ ├── client │ ├── CLI.java │ ├── Config.java │ ├── Main.java │ └── SmartCommit.java │ ├── core │ ├── GraphBuilder.java │ ├── GroupGenerator.java │ ├── GroupGeneratorOld.java │ ├── RepoAnalyzer.java │ └── visitor │ │ ├── IdentifierVisitor.java │ │ ├── MemberVisitor.java │ │ ├── MyNodeFinder.java │ │ └── SimpleVisitor.java │ ├── evaluation │ ├── DataMiner.java │ └── Evaluation.java │ ├── io │ ├── DataCollector.java │ ├── DiffGraphExporter.java │ ├── DiffTypeProvider.java │ ├── GraphExporter.java │ └── TypeProvider.java │ ├── model │ ├── Action.java │ ├── DiffFile.java │ ├── DiffHunk.java │ ├── EntityPool.java │ ├── Group.java │ ├── Hunk.java │ ├── constant │ │ ├── ChangeType.java │ │ ├── ContentType.java │ │ ├── FileStatus.java │ │ ├── FileType.java │ │ ├── GroupLabel.java │ │ ├── Operation.java │ │ └── Version.java │ ├── diffgraph │ │ ├── DiffEdge.java │ │ ├── DiffEdgeType.java │ │ └── DiffNode.java │ ├── entity │ │ ├── AnnotationInfo.java │ │ ├── AnnotationMemberInfo.java │ │ ├── ClassInfo.java │ │ ├── DeclarationInfo.java │ │ ├── EnumConstantInfo.java │ │ ├── EnumInfo.java │ │ ├── FieldInfo.java │ │ ├── HunkInfo.java │ │ ├── InitializerInfo.java │ │ ├── InterfaceInfo.java │ │ └── MethodInfo.java │ └── graph │ │ ├── Edge.java │ │ ├── EdgeType.java │ │ ├── Node.java │ │ └── NodeType.java │ └── util │ ├── Distance.java │ ├── GitService.java │ ├── GitServiceCGit.java │ ├── GitServiceJGit.java │ ├── JDTParser.java │ ├── JDTService.java │ ├── NameResolver.java │ ├── Utils.java │ └── diffparser │ ├── api │ ├── DiffParser.java │ ├── UnifiedDiffParser.java │ └── model │ │ ├── Diff.java │ │ ├── Hunk.java │ │ ├── Line.java │ │ └── Range.java │ └── unified │ ├── ParseWindow.java │ ├── ParserState.java │ └── ResizingParseWindow.java └── test └── java └── com └── github └── smartcommit ├── MetricsTest.java ├── TestContentType.java ├── TestDistance.java └── TestReformatting.java /.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ ### 2 | .idea/ 3 | *.iml 4 | *.ipr 5 | logs/ 6 | 7 | ### Gradle ### 8 | .gradle/ 9 | #gradle/ 10 | 11 | ### Eclipse ### 12 | .settings 13 | .classpath 14 | .project 15 | 16 | # Compiled class file 17 | *.class 18 | out/ 19 | build/ 20 | 21 | # Log file 22 | *.log 23 | 24 | # BlueJ files 25 | *.ctxt 26 | 27 | # Mobile Tools for Java (J2ME) 28 | .mtj.tmp/ 29 | 30 | # Package Files # 31 | # *.jar 32 | *.war 33 | *.nar 34 | *.ear 35 | *.zip 36 | *.tar.gz 37 | *.rar 38 | 39 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 40 | hs_err_pid* 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 | 5 |

SmartCommit

6 | 7 |
8 | 9 | #### SmartCommit aims at making "code commit" an elegant and decent daily work for developers. 10 | 11 | As advocated by many communities (e.g., [Git official doc], [Angular contribution instructions]) and companies (e.g., [Google's engineering practices]), developers are encouraged to submit cohesive and self-contained commits, accompanying with clear and informative commit messages. 12 | 13 | SmartCommit is the assistant for you to follow this best practice. 14 | 15 | Specifically, it helps you to: 16 | 17 | - Organize your local changes into groups, each of which is expected to focus on one specific task. 18 | - Review and stage fine-grained code changes within an intuitive GUI, in the forms of code hunks or files. 19 | - Commit and push multiple commits with one single click, saving the effort to type `git-status`, `git-diff`, `git-add`, `git-commit` and `git-push` commands. 20 | 21 | [Git official doc]: https://git-scm.com/docs/gitworkflows#_separate_changes 22 | [Angular contribution instructions]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md 23 | [Google's engineering practices]: https://github.com/google/eng-practices/blob/master/review/developer/small-cls.md 24 | 25 | > Note: This repo is the change decomposition&suggestion algorithm of SmartCommit, some additional features (like intent classification and message suggestion) are excluded in the public version. 26 | > Check the [SmartCommit] repo for the demo GUI client. 27 | 28 | [SmartCommit]: https://github.com/Symbolk/SmartCommit 29 | 30 | ## As a User 31 | 32 | ## Requirements 33 | 34 | - macOS/Windows/Linux 35 | - Java JRE 8+ 36 | - Git ^2.20.0 37 | 38 | ## Usage 39 | 40 | ### 1. JAR Usage 41 | 42 | Download the latest jar from [releases], or build one with `gradle fatJar -x test`, and run the following command: 43 | 44 | [releases]: https://github.com/Symbolk/SmartCommitCore/releases 45 | 46 | ```sh 47 | java -jar SmartCommit-xxx.jar [OPTIONS] 48 | ``` 49 | 50 | ### Options 51 | 52 | ```sh 53 | Usage: SmartCommit [options] 54 | Options: 55 | -r, --repo 56 | Absolute root path of the target Git repository. 57 | Default: 58 | -w, --working 59 | Analyze the current working tree (default). 60 | Default: true 61 | -c, --commit 62 | Analyze a specific commit by providing its ID. 63 | Default: 64 | -o, --output 65 | Specify the path to output the result. 66 | Default: /Users/symbolk/.smartcommit/repos 67 | -ref, --detect-refactoring 68 | Whether to enable refactoring detection, true/false. 69 | Default: true 70 | -gr, --granularity 71 | Set the atomic unit/granularity of change: {hunk: 0 (default), member: 72 | 1, class: 2, package: 3}. 73 | Default: 0 74 | -ms, --min-similarity 75 | Set the minimal similarity between change, [0.0, 1.0]. 76 | Default: 0.8 77 | -nj, --process-non-java 78 | Whether to further process non-java changes, true/false. 79 | Default: true 80 | -wt, --weight-threshold 81 | Set the threshold for partitioning (if not specified or 0.0, use the 82 | max-gap splitter), [0.0, 1.0]. 83 | Default: 0.0 84 | ``` 85 | 86 | ### 2. API Usage 87 | 88 | The entry class SmartCommit provides the following APIs to use programmatically: 89 | 90 | #### API for initialization: 91 | 92 | ```java 93 | /** 94 | * Initial setup for analysis 95 | * 96 | * @param repoName repo name 97 | * @param repoPath absolute local repo path 98 | * @param tempDir temporary directory path for intermediate and final result 99 | */ 100 | SmartCommit(String repoName, String repoPath, String tempDir) 101 | ``` 102 | 103 | #### API for analyzing changes: 104 | 105 | ```java 106 | 107 | /** 108 | * Analyze the current working directory of the repository 109 | * 110 | * @return suggested groups 111 | * @throws Exception 112 | */ 113 | Map analyzeWorkingTree() 114 | 115 | /** 116 | * Analyze a specific commit of the repository for decomposition 117 | * 118 | * @param commitID the target commit hash id 119 | * @return suggested groups 120 | * @throws Exception 121 | */ 122 | Map analyzeCommit(String commitID) 123 | ``` 124 | 125 | #### API for exporting the result: 126 | 127 | ```java 128 | 129 | /** 130 | * Save meta information of each group, including diff hunk ids, commit msgs, etc. 131 | * 132 | * @param generatedGroups generated groups 133 | * @param outputDir output directory path 134 | */ 135 | void exportGroupResults(Map generatedGroups, String outputDir) 136 | 137 | /** 138 | * Generate and save the detailed content of diff hunks for each group 139 | * 140 | * @param results generated groups 141 | * @param outputDir output directory path 142 | */ 143 | void exportGroupDetails(Map results, String outputDir) 144 | ``` 145 | 146 | ### Options & Arguments: 147 | 148 | ```java 149 | // whether to enable refactoring detection 150 | void setDetectRefactorings(boolean detectRefactorings) 151 | 152 | // whether to group changes in non-java textual files by type 153 | void setProcessNonJavaChanges(boolean processNonJavaChanges) 154 | 155 | // override the threshold for edge weight filtering (default: 0.0, use the dynamic threshold) 156 | void setWeightThreshold(Double weightThreshold) 157 | 158 | // override the minimum similarity considered (default: 0.8, high-similarity) 159 | void setMinSimilarity(double minSimilarity) 160 | 161 | // override the maximum distance between diff hunks (default: 0, hunk-level) 162 | void setMaxDistance(int maxDistance) 163 | ``` 164 | 165 | ## As a Developer 166 | 167 | ### Requirements 168 | 169 | - maxOS/Windows/Linux 170 | - Java JDK 8+ 171 | - Git ^2.20.0 172 | - Gradle ^6.2.1 173 | - IntelliJ IDEA 2020 (with Gradle integration) 174 | 175 | ### Environment Setup 176 | 177 | 1. Open the cloned repository as a project with IntelliJ IDEA; 178 | 179 | 2. Resolve dependencies by clicking the refresh button in the Gradle tab, or use `gradle build -x test`; 180 | 181 | ### Test 182 | 183 | Run `CLI.main()` to see CLI options, or modify `Config.java` and run `Main.main()` for API usage. 184 | 185 | ### Build 186 | 187 | Run the following command under the root of the cloned repository to build an executable jar from source with all dependencies packaged: 188 | 189 | ```sh 190 | gradle fatJar 191 | ``` 192 | 193 | Packaged jar file will be generated in `build\libs`, with the name `SmartCommit-xxx.jar`. 194 | 195 | ### Project Structure 196 | ``` 197 | SmartCommit 198 | ├─client // interface 199 | │ ├─SmartCommit // entry class 200 | ├─core // core algorithms 201 | │ ├─visitor // tree visitors 202 | │ ├─RepoAnalyzer // analyzing the repository to collect change info 203 | │ ├─GraphBuilder // building entity reference graph from source code 204 | │ └─GroupGenerator // building and generate groups from diff hunk graph 205 | ├─model // data structure 206 | │ ├─constant // enum constants 207 | │ ├─ChangeType // types of textual change actions 208 | │ ├─ContentType // types of change content 209 | │ ├─FileStatus // status of changed files 210 | │ ├─FileType // types of changed files 211 | │ ├─GroupLabel // intent label of groups 212 | │ ├─Operation // types of semantic change operations 213 | │ └─Version // old and new version tags 214 | │ ├─diffgraph // diff hunk graph definition 215 | │ ├─DiffNode // node definition 216 | │ ├─DiffEdge // edge definition 217 | │ └─DiffEdgeType // edge types 218 | │ ├─entity // program entities 219 | │ ├─graph // entity reference graph 220 | │ ├─Node // node definition 221 | │ ├─Edge // edge definition 222 | │ ├─NodeType // node types 223 | │ └─EdgeType // edge types 224 | │ ├─Action // semantic change action 225 | │ ├─Hunk // single-side hunk model 226 | │ ├─DiffFile // changed file model 227 | │ ├─DiffHunk // diff hunk model 228 | │ ├─EntityPool // pool of all involved entities 229 | │ └─Group // generated group model 230 | ├─io // input and output 231 | │ ├─DataCollector // collect data from repository 232 | │ ├─TypeProvider // label provider for visualizing entity reference graph 233 | │ ├─DiffTypeProvider// label provider for visualizing of diff hunk graph 234 | │ └─GraphExporter // graph visualization in dot format 235 | └─util // general utils 236 | ├─diffparser // parsing and splitting diff hunks from GNU diff 237 | ├─GitService // wrapping git functions 238 | ├─JDTService // delegating jdt parser 239 | └─Utils // other utils 240 | 241 | ``` 242 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'idea' 4 | } 5 | 6 | group 'SmartCommitCore' 7 | version '1.0' 8 | 9 | sourceCompatibility = 1.8 10 | 11 | repositories { 12 | // mavenCentral() 13 | maven { url 'https://plugins.gradle.org/m2/' } 14 | maven { url 'http://maven.aliyun.com/nexus/content/repositories/google' } 15 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } 16 | maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' } 17 | } 18 | 19 | //gradle.buildFinished { buildResult -> 20 | // println "BUILD FINISHED" 21 | // println "build failure - " + buildResult.failure 22 | //} 23 | 24 | tasks.withType(JavaCompile) { 25 | options.encoding = "UTF-8" 26 | options.fork = true 27 | options.forkOptions.jvmArgs += ["-Duser.language=en"] 28 | } 29 | 30 | jar { 31 | manifest { 32 | attributes( 33 | 'Main-Class': 'com.github.smartcommit.client.CLI' 34 | ) 35 | } 36 | } 37 | 38 | task fatJar(type: Jar) { 39 | manifest.from jar.manifest 40 | archiveClassifier = 'all' 41 | from { 42 | configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } 43 | configurations.compileClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) } 44 | } { 45 | exclude "META-INF/*.SF" 46 | exclude "META-INF/*.DSA" 47 | exclude "META-INF/*.RSA" 48 | } 49 | with jar 50 | } 51 | 52 | artifacts { 53 | archives fatJar 54 | } 55 | 56 | test { 57 | useJUnitPlatform() 58 | testLogging { 59 | events "passed", "skipped", "failed" 60 | } 61 | } 62 | 63 | configurations.all { 64 | } 65 | 66 | dependencies { 67 | implementation fileTree(dir: 'lib', include: ['*.jar']) 68 | implementation 'com.beust:jcommander:1.78' 69 | implementation 'org.eclipse.jdt:org.eclipse.jdt.core:3.23.0' 70 | implementation 'org.jgrapht:jgrapht-core:1.3.0' 71 | implementation 'org.jgrapht:jgrapht-io:1.3.0' 72 | implementation 'info.debatty:java-string-similarity:2.0.0' 73 | 74 | implementation 'org.eclipse.jgit:org.eclipse.jgit:5.10.0.202012080955-r' 75 | implementation 'org.mongodb:mongo-java-driver:3.12.8' 76 | implementation 'com.google.code.gson:gson:2.8.6' 77 | implementation 'com.ibm.icu:icu4j:68.2' 78 | 79 | implementation 'org.apache.logging.log4j:log4j-core:2.17.0' 80 | implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3' 81 | implementation 'com.github.haifengl:smile-kotlin:2.6.0' 82 | 83 | testImplementation 'org.assertj:assertj-core:3.18.1' 84 | testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0' 85 | } 86 | -------------------------------------------------------------------------------- /docs/INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | 1. Clone the SmartCommitCore project from GitHub: 4 | 5 | ````` 6 | git clone https://github.com/Symbolk/SmartCommitCore 7 | ````` 8 | 9 | 2. Configure the environment as described in [REQUIREMENTS]; 10 | 11 | [REQUIREMENTS]: /docs/REQUIREMENTS.md 12 | 13 | 3. Open SmartCommitCore in IntelliJ IDEA, set the Gradle version to use in `Preferences->Build,Execution,Deployment->Build Tools->Gradle`: 14 | 15 | ![image-20210602165205875](/docs/imgs/gradle_setting.png?raw=true) 16 | 17 | 4. Resolve dependencies by clicking the `Reload All Gradle Projects` on the Gradle tool window: 18 | 19 | ![image-20210602165205875](/docs/imgs/dependency_resolution.png?raw=true) 20 | 21 | 5. After the dependency resolution finishes, build the project with `Build->Build Project` of IDEA, expected output: 22 | 23 | ![image-20210602113556766](/docs/imgs/build_output.png?raw=true) 24 | 25 | 6. Right click on `src/main/java/com/github/smartcommit/client/CLI.java` and click `Run 'CLI.main()'`to verify the correct setup, expected output: 26 | 27 | ```sh 28 | Please at least specify the Git repository to analyze with -r. 29 | Usage: SmartCommit [options] 30 | Options: 31 | -r, --repo 32 | Absolute root path of the target Git repository. 33 | Default: 34 | -w, --working 35 | Analyze the current working tree (default). 36 | Default: true 37 | -c, --commit 38 | Analyze a specific commit by providing its ID. 39 | Default: 40 | -o, -output 41 | Specify the path to output the result. 42 | Default: /Users/USERNAME/.smartcommit/repos 43 | -ref, --detect-refactoring 44 | Whether to enable refactoring detection. 45 | Default: true 46 | -md, --max-distance 47 | Set the maximum distance (default: 0). 48 | Default: 0 49 | -ms, --min-similarity 50 | Set the minimum similarity (default: 0.8). 51 | Default: 0.8 52 | -nj, --process-non-java 53 | Whether to further process non-java changes. 54 | Default: true 55 | -wt, --weight-threshold 56 | Set the threshold for weight filtering (default: 0.6). 57 | Default: 0.6 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /docs/LICENSE.txt: -------------------------------------------------------------------------------- 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, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/REQUIREMENTS.md: -------------------------------------------------------------------------------- 1 | ## Required Technology Skills 2 | 3 | - Basic use experience of macOS (e.g., run commands in bash/shell) and IntelliJ IDEA/Pycharm (e.g., import projects, resolve dependencies, and run programs) 4 | 5 | - Basic Java programming experience to run the artifact (e.g., install dependencies with Gradle) 6 | 7 | - Basic Python programming experience to reproduce the evaluation (e.g., install dependencies with Gradle and Pip) 8 | 9 | - Basic use experience of MongoDB (e.g., start the database, run commands and import data) 10 | 11 | 12 | 13 | ## Hardware Requirements 14 | 15 | Disk Space: 10G+ 16 | 17 | RAM: 8G+ 18 | 19 | 20 | 21 | ## Software Requirements 22 | 23 | - OS: macOS ^10.15.7 24 | 25 | #### To run the artifact: 26 | 27 | - Java: JDK ^8 28 | - Gradle: ^6.8 29 | - Git: Git 2.29.2 30 | - IntelliJ IDEA 2020 (with Gradle integration) 31 | 32 | #### To replicate the evaluation: 33 | 34 | - MongoDB: Community Edition ^4.4.3 35 | 36 | #### To visualize the results: 37 | 38 | - Python ^3.7.0 39 | - PyCharm 2020 40 | -------------------------------------------------------------------------------- /docs/STATUS.md: -------------------------------------------------------------------------------- 1 | ## Target Badge 2 | 3 | Artifacts Evaluated - Reusable 4 | 5 | 6 | 7 | ## Supporting Reasons 8 | 9 | We have the following reasons to support the badge: 10 | 11 | - The project is established with a well-designed architecture, and is developed trying to follow the best practice of cohesive commits by dog-fooding itself. 12 | - The code is carefully documented to be self-explained, with friendly API that allows it to be used as the backend of custom frontends (we implemented a cross-platform desktop electron app and a plugin of IntelliJ IDEA). 13 | - The tool provides a series of configurable options for extending and customizing (see Appendix below). 14 | - The industrial version of the tool has stood the test of a long-term (>1 year) and large-scale (>100 engineers on >10 industrial projects) practical use. 15 | 16 | Therefore, we believe that the artifact can not only be used by developers to improve their *commit style*, but can also be reused by researchers to support the further research by modifying and extending the software. 17 | 18 | 19 | 20 | 21 | 22 | ## Appendix: API, Configurations, and Architecture 23 | 24 | ### API Usage 25 | 26 | The entry class SmartCommit provides the following APIs to use programmatically: 27 | 28 | #### API for initialization: 29 | 30 | ```java 31 | /** 32 | * Initial setup for analysis 33 | * 34 | * @param repoName repo name 35 | * @param repoPath absolute local repo path 36 | * @param tempDir temporary directory path for intermediate and final result 37 | */ 38 | SmartCommit(String repoName, String repoPath, String tempDir) 39 | ``` 40 | 41 | #### API for analyzing changes: 42 | 43 | ```java 44 | /** 45 | * Analyze the current working directory of the repository 46 | * 47 | * @return suggested groups 48 | * @throws Exception 49 | */ 50 | Map analyzeWorkingTree() 51 | 52 | /** 53 | * Analyze a specific commit of the repository for decomposition 54 | * 55 | * @param commitID the target commit hash id 56 | * @return suggested groups 57 | * @throws Exception 58 | */ 59 | Map analyzeCommit(String commitID) 60 | ``` 61 | 62 | #### API for exporting the result: 63 | 64 | ```java 65 | /** 66 | * Save meta information of each group, including diff hunk ids, commit msgs, etc. 67 | * 68 | * @param generatedGroups generated groups 69 | * @param outputDir output directory path 70 | */ 71 | void exportGroupResults(Map generatedGroups, String outputDir) 72 | 73 | /** 74 | * Generate and save the detailed content of diff hunks for each group 75 | * 76 | * @param results generated groups 77 | * @param outputDir output directory path 78 | */ 79 | void exportGroupDetails(Map results, String outputDir) 80 | ``` 81 | 82 | ### Options & Arguments: 83 | 84 | ```java 85 | // whether to enable refactoring detection 86 | void setDetectRefactorings(boolean detectRefactorings) 87 | 88 | // whether to group changes in non-java textual files by type 89 | void setProcessNonJavaChanges(boolean processNonJavaChanges) 90 | 91 | // override the threshold for edge weight filtering (default: 0.6) 92 | void setWeightThreshold(Double weightThreshold) 93 | 94 | // override the minimum similarity considered (default: 0.8) 95 | void setMinSimilarity(double minSimilarity) 96 | 97 | // override the maximum distance between diff hunks (default: 0) 98 | void setMaxDistance(int maxDistance) 99 | ``` 100 | 101 | ### Project Structure 102 | ``` 103 | SmartCommit 104 | ├─client // interface 105 | │ ├─SmartCommit // entry class 106 | ├─core // core algorithms 107 | │ ├─visitor // tree visitors 108 | │ ├─RepoAnalyzer // analyzing the repository to collect change info 109 | │ ├─GraphBuilder // building entity reference graph from source code 110 | │ └─GroupGenerator // building and generate groups from diff hunk graph 111 | ├─model // data structure 112 | │ ├─constant // enum constants 113 | │ ├─ChangeType // types of textual change actions 114 | │ ├─ContentType // types of change content 115 | │ ├─FileStatus // status of changed files 116 | │ ├─FileType // types of changed files 117 | │ ├─GroupLabel // intent label of groups 118 | │ ├─Operation // types of semantic change operations 119 | │ └─Version // old and new version tags 120 | │ ├─diffgraph // diff hunk graph definition 121 | │ ├─DiffNode // node definition 122 | │ ├─DiffEdge // edge definition 123 | │ └─DiffEdgeType // edge types 124 | │ ├─entity // program entities 125 | │ ├─graph // entity reference graph 126 | │ ├─Node // node definition 127 | │ ├─Edge // edge definition 128 | │ ├─NodeType // node types 129 | │ └─EdgeType // edge types 130 | │ ├─Action // semantic change action 131 | │ ├─Hunk // single-side hunk model 132 | │ ├─DiffFile // changed file model 133 | │ ├─DiffHunk // diff hunk model 134 | │ ├─EntityPool // pool of all involved entities 135 | │ └─Group // generated group model 136 | ├─io // input and output 137 | │ ├─DataCollector // collect data from repository 138 | │ ├─TypeProvider // label provider for visualizing entity reference graph 139 | │ ├─DiffTypeProvider// label provider for visualizing of diff hunk graph 140 | │ └─GraphExporter // graph visualization in dot format 141 | └─util // general utils 142 | ├─diffparser // parsing and splitting diff hunks from GNU diff 143 | ├─GitService // wrapping git functions 144 | ├─JDTService // delegating jdt parser 145 | └─Utils // other utils 146 | ``` 147 | -------------------------------------------------------------------------------- /docs/imgs/build_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/docs/imgs/build_output.png -------------------------------------------------------------------------------- /docs/imgs/dependency_resolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/docs/imgs/dependency_resolution.png -------------------------------------------------------------------------------- /docs/imgs/gradle_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/docs/imgs/gradle_setting.png -------------------------------------------------------------------------------- /docs/imgs/toy_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/docs/imgs/toy_example.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Feb 29 10:32:57 CST 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /lib/RefactoringMiner-20200801.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/RefactoringMiner-20200801.jar -------------------------------------------------------------------------------- /lib/classindex-3.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/classindex-3.8.jar -------------------------------------------------------------------------------- /lib/client-2.1.3-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/client-2.1.3-SNAPSHOT.jar -------------------------------------------------------------------------------- /lib/client.diff-2.1.3-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/client.diff-2.1.3-SNAPSHOT.jar -------------------------------------------------------------------------------- /lib/commons-codec-1.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/commons-codec-1.10.jar -------------------------------------------------------------------------------- /lib/commons-io-2.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/commons-io-2.0.1.jar -------------------------------------------------------------------------------- /lib/commons-lang3-3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/commons-lang3-3.1.jar -------------------------------------------------------------------------------- /lib/commons-logging-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/commons-logging-1.2.jar -------------------------------------------------------------------------------- /lib/core-2.1.3-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/core-2.1.3-SNAPSHOT.jar -------------------------------------------------------------------------------- /lib/gen.javaparser-2.1.3-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/gen.javaparser-2.1.3-SNAPSHOT.jar -------------------------------------------------------------------------------- /lib/gen.jdt-2.1.3-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/gen.jdt-2.1.3-SNAPSHOT.jar -------------------------------------------------------------------------------- /lib/guava-27.0-jre.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/guava-27.0-jre.jar -------------------------------------------------------------------------------- /lib/javaparser-symbol-solver-model-3.13.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/javaparser-symbol-solver-model-3.13.6.jar -------------------------------------------------------------------------------- /lib/json-20190722-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/json-20190722-sources.jar -------------------------------------------------------------------------------- /lib/json-20190722.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/json-20190722.jar -------------------------------------------------------------------------------- /lib/org.eclipse.osgi-3.15.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/org.eclipse.osgi-3.15.0.jar -------------------------------------------------------------------------------- /lib/ph-commons-9.3.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/ph-commons-9.3.4.jar -------------------------------------------------------------------------------- /lib/simmetrics-core-3.2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Symbolk/SmartCommitCore/44b4f582ddd96c0de43bbfe9bce3fec13d9404cc/lib/simmetrics-core-3.2.3.jar -------------------------------------------------------------------------------- /log4j.properties: -------------------------------------------------------------------------------- 1 | ### log4j settings ### 2 | log4j.rootLogger=INFO,stdout,D,E 3 | ### INFO --> Console ### 4 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 5 | log4j.appender.stdout.Target=System.out 6 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.stdout.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n 8 | ### DEBUG --> logs/error.log ### 9 | log4j.appender.D=org.apache.log4j.DailyRollingFileAppender 10 | log4j.appender.D.File=${logs.dir}/logs/debug.log 11 | log4j.appender.D.DatePattern='.'yyyy-MM-dd 12 | log4j.appender.D.Append=true 13 | log4j.appender.D.Threshold=DEBUG 14 | log4j.appender.D.layout=org.apache.log4j.PatternLayout 15 | log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n 16 | ### ERROR --> logs/error.log ### 17 | log4j.appender.E=org.apache.log4j.DailyRollingFileAppender 18 | log4j.appender.E.File=${logs.dir}/logs/error.log 19 | log4j.appender.E.DatePattern='.'yyyy-MM-dd 20 | log4j.appender.E.Append=true 21 | log4j.appender.E.Threshold=ERROR 22 | log4j.appender.E.layout=org.apache.log4j.PatternLayout 23 | log4j.appender.E.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n 24 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'smartcommitcore' 2 | 3 | -------------------------------------------------------------------------------- /src/MsgTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add" : ["Add Method ... in Class", "Add Field ... to Method ..."], 3 | "Create" : ["Create a new Object ...","Create Class ... for ..."], 4 | "Make" : ["Make Method ... more efficient", "Make Class ... more robust"], 5 | "Implement" : ["Implement Interface ...","Implement Method ... for class ..."], 6 | "Update" : ["Update Method ...", "Update Field ... in Class ..."], 7 | "Use" : ["Use Field ... in Method ...", "Use Class ... for ..."], 8 | "Set" : ["Set Field ... in Class ..."], 9 | "Handle" : ["Handle Error in Method ...", "Handle Exception ... in Method ..."], 10 | "Improve" : ["Handle Error in Method ..."], 11 | "Optimize" : ["Optimize Function ..."], 12 | "Upgrade" : ["Upgrade Package ... to Version ..."], 13 | "Remove" : ["Remove Method ... ", "Remove Code in Class... "], 14 | "Refactor" : ["Refactor Solution ... for ..."], 15 | "Replace" : ["Replace Field ... in Method ..."], 16 | "Move" : ["Move Method ...to Class ...", "Move Field ... from Class ... to Class ...", "Move Class ... to ..."], 17 | "Change" : ["Change Field Type ... in Method ...", "Change Method Signature ... to ... in Class ..."], 18 | "Rename" : ["Rename Field ... in Method ...", "Rename Method ... to ... in class ...", "Rename Class ... to ..."], 19 | "Document" : ["Document ... in File ..."], 20 | "Reformat" : ["Reformat code in File ..."], 21 | "Fix" : ["Fix Error ... in File ...", "Fix ... in Class ..."], 22 | "Revert" : ["Revert ... "], 23 | "Test" : ["Test Method ...", "Test Method ... for ... in Class ..."] 24 | } -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/client/CLI.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.client; 2 | 3 | import com.beust.jcommander.JCommander; 4 | import com.beust.jcommander.Parameter; 5 | import com.beust.jcommander.ParameterException; 6 | import com.github.smartcommit.model.Group; 7 | import org.apache.log4j.BasicConfigurator; 8 | import org.apache.log4j.Level; 9 | import org.eclipse.jgit.api.Git; 10 | import org.eclipse.jgit.lib.Repository; 11 | import org.eclipse.jgit.lib.RepositoryCache; 12 | import org.eclipse.jgit.util.FS; 13 | 14 | import java.io.File; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.util.Map; 18 | 19 | public class CLI { 20 | // command line options 21 | @Parameter( 22 | names = {"-r", "--repo"}, 23 | arity = 1, 24 | order = 0, 25 | required = true, 26 | description = "Absolute root path of the target Git repository.") 27 | String repoPath = ""; 28 | 29 | @Parameter( 30 | names = {"-w", "--working"}, 31 | arity = 0, 32 | order = 1, 33 | description = "Analyze the current working tree (default).") 34 | Boolean analyzeWorkingTree = true; 35 | 36 | @Parameter( 37 | names = {"-c", "--commit"}, 38 | arity = 1, 39 | order = 2, 40 | description = "Analyze a specific commit by providing its ID.") 41 | String commitID = ""; 42 | 43 | // output path 44 | @Parameter( 45 | names = {"-o", "--output"}, 46 | arity = 1, 47 | order = 3, 48 | description = "Specify the path to output the result.") 49 | String outputPath = 50 | System.getProperty("user.home") + File.separator + ".smartcommit" + File.separator + "repos"; 51 | 52 | // options&args 53 | @Parameter( 54 | names = {"-gr", "--granularity"}, 55 | arity = 1, 56 | description = 57 | "Set the atomic unit/granularity of change: {hunk: 0 (default), member: 1, class: 2, package: 3}.") 58 | Integer granularity = 0; 59 | 60 | @Parameter( 61 | names = {"-ref", "--detect-refactoring"}, 62 | arity = 1, 63 | description = "Whether to enable refactoring detection, true/false.") 64 | Boolean detectRef = true; 65 | 66 | @Parameter( 67 | names = {"-nj", "--process-non-java"}, 68 | arity = 1, 69 | description = "Whether to further process non-java changes, true/false.") 70 | Boolean processNonJava = true; 71 | 72 | @Parameter( 73 | names = {"-wt", "--weight-threshold"}, 74 | arity = 1, 75 | description = 76 | "Set the threshold for partitioning (if not specified or 0.0, use the max-gap splitter), [0.0, 1.0].") 77 | Double weightThreshold = 0D; 78 | 79 | @Parameter( 80 | names = {"-ms", "--min-similarity"}, 81 | arity = 1, 82 | description = "Set the minimal similarity between change, [0.0, 1.0].") 83 | Double minSimilarity = 0.8D; 84 | 85 | public static void main(String[] args) { 86 | // config the logger 87 | // PropertyConfigurator.configure("log4j.properties"); 88 | // use basic configuration when packaging 89 | BasicConfigurator.configure(); 90 | org.apache.log4j.Logger.getRootLogger().setLevel(Level.ERROR); 91 | 92 | try { 93 | CLI cli = new CLI(); 94 | cli.run(args); 95 | } catch (Exception e) { 96 | e.printStackTrace(); 97 | } 98 | } 99 | 100 | /** Run merging according to given options */ 101 | private void run(String[] args) { 102 | JCommander commandLineOptions = new JCommander(this); 103 | try { 104 | commandLineOptions.parse(args); 105 | checkArguments(this); 106 | 107 | String repoName = getRepoName(repoPath); 108 | outputPath = outputPath + File.separator + repoName; 109 | 110 | SmartCommit smartCommit = 111 | new SmartCommit(generateRepoID(repoName), repoName, repoPath, outputPath); 112 | 113 | // apply options 114 | smartCommit.setDetectRefactorings(detectRef); 115 | smartCommit.setProcessNonJavaChanges(processNonJava); 116 | smartCommit.setWeightThreshold(weightThreshold); 117 | smartCommit.setMinSimilarity(minSimilarity); 118 | smartCommit.setMaxDistance(granularity); // use the distance on the tree to limit granularity 119 | 120 | Map groups; 121 | if (analyzeWorkingTree) { 122 | groups = smartCommit.analyzeWorkingTree(); 123 | } else { 124 | groups = smartCommit.analyzeCommit(commitID); 125 | } 126 | if (groups != null && !groups.isEmpty()) { 127 | System.out.println("End analysis, results saved under: " + outputPath); 128 | } else { 129 | System.out.println("End analysis, but found no Changes."); 130 | } 131 | } catch (ParameterException pe) { 132 | System.err.println(pe.getMessage()); 133 | commandLineOptions.setProgramName("SmartCommit"); 134 | commandLineOptions.usage(); 135 | } 136 | } 137 | 138 | private String generateRepoID(String repoName) { 139 | return String.valueOf(repoName.hashCode()); 140 | } 141 | 142 | private String getRepoName(String repoPath) { 143 | String repoName = ""; 144 | Path path = Paths.get(repoPath); 145 | try (Git git = Git.open(path.toFile())) { 146 | Repository repository = git.getRepository(); 147 | // getDirectory() returns the .git file 148 | repoName = repository.getDirectory().getParentFile().getName(); 149 | String branch = repository.getBranch(); 150 | System.out.println("Begin analysis, [repo] " + repoName + " [branch] " + branch); 151 | } catch (Exception e) { 152 | e.printStackTrace(); 153 | } 154 | return repoName; 155 | } 156 | 157 | /** Check if args are valid */ 158 | private void checkArguments(CLI cli) { 159 | if (cli.repoPath.isEmpty()) { 160 | throw new ParameterException( 161 | "Please at least specify the Git repository to analyze with -r."); 162 | } else { 163 | File d = new File(cli.repoPath); 164 | if (!d.exists()) { 165 | throw new ParameterException(cli.repoPath + " does not exist!"); 166 | } 167 | if (!d.isDirectory()) { 168 | throw new ParameterException(cli.repoPath + " is not a directory!"); 169 | } 170 | if (!checkRepoValid(cli.repoPath)) { 171 | throw new ParameterException(cli.repoPath + " is not a valid Git repository!"); 172 | } 173 | if (!cli.commitID.isEmpty()) { 174 | cli.analyzeWorkingTree = false; 175 | } 176 | } 177 | } 178 | 179 | private boolean checkRepoValid(String repoPath) { 180 | return RepositoryCache.FileKey.isGitRepository(new File(repoPath, ".git"), FS.DETECTED); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/client/Config.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.client; 2 | 3 | import java.io.File; 4 | 5 | /** Store the constants as the config */ 6 | public final class Config { 7 | // at commit 8 | public static final String REPO_NAME = "SmartCommitCore"; 9 | public static final String REPO_PATH = System.getProperty("user.dir"); 10 | 11 | // in working tree 12 | // public static final String REPO_NAME = "SmartCommitCore"; 13 | // public static final String REPO_PATH = "~/coding/dev" + File.separator + REPO_NAME; 14 | 15 | // arguments 16 | public static final Double WEIGHT_THRESHOLD = 0D; 17 | public static final Double MIN_SIMILARITY = 0.8D; 18 | // {hunk: 0 (default), member: 1, class: 2, package: 3} 19 | public static final Integer MAX_DISTANCE = 2; 20 | public static final String REPO_ID = String.valueOf(REPO_NAME.hashCode()); 21 | public static final String TEMP_DIR = 22 | System.getProperty("user.home") 23 | + File.separator 24 | + "smartcommit" 25 | + File.separator 26 | + "temp" 27 | + File.separator 28 | + REPO_NAME; 29 | public static final String JRE_PATH = 30 | System.getProperty("java.home") + File.separator + "lib/rt.jar"; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/client/Main.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.client; 2 | 3 | import com.github.smartcommit.model.Group; 4 | import org.apache.log4j.BasicConfigurator; 5 | import org.apache.log4j.Level; 6 | 7 | import java.util.Map; 8 | 9 | public class Main { 10 | public static void main(String[] args) { 11 | // use basic configuration when packaging 12 | BasicConfigurator.configure(); 13 | org.apache.log4j.Logger.getRootLogger().setLevel(Level.INFO); 14 | 15 | String COMMIT_ID = "fced40b"; 16 | try { 17 | SmartCommit smartCommit = 18 | new SmartCommit(Config.REPO_ID, Config.REPO_NAME, Config.REPO_PATH, Config.TEMP_DIR); 19 | smartCommit.setDetectRefactorings(true); 20 | smartCommit.setProcessNonJavaChanges(false); 21 | smartCommit.setWeightThreshold(Config.WEIGHT_THRESHOLD); 22 | smartCommit.setMinSimilarity(Config.MIN_SIMILARITY); 23 | smartCommit.setMaxDistance(Config.MAX_DISTANCE); 24 | // Map groups = smartCommit.analyzeWorkingTree(); 25 | Map groups = smartCommit.analyzeCommit(COMMIT_ID); 26 | if (groups != null && !groups.isEmpty()) { 27 | for (Map.Entry entry : groups.entrySet()) { 28 | Group group = entry.getValue(); 29 | System.out.println(entry.getKey()); 30 | System.out.println(group.toString()); 31 | } 32 | 33 | } else { 34 | System.out.println("There is no changes."); 35 | } 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/core/RepoAnalyzer.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.core; 2 | 3 | import com.github.smartcommit.model.DiffFile; 4 | import com.github.smartcommit.model.DiffHunk; 5 | import com.github.smartcommit.model.constant.FileStatus; 6 | import com.github.smartcommit.util.GitService; 7 | import com.github.smartcommit.util.GitServiceCGit; 8 | import com.github.smartcommit.util.Utils; 9 | import org.apache.log4j.Logger; 10 | 11 | import java.util.*; 12 | 13 | // import com.github.gumtreediff.gen.jdt.JdtTreeGenerator; 14 | 15 | public class RepoAnalyzer { 16 | 17 | private static final Logger logger = Logger.getLogger(RepoAnalyzer.class); 18 | 19 | private String repoID; 20 | private String repoName; 21 | private String repoPath; 22 | private List diffFiles; 23 | private List diffHunks; 24 | private Map idToDiffFileMap; 25 | private Map idToDiffHunkMap; 26 | 27 | public RepoAnalyzer(String repoID, String repoName, String repoPath) { 28 | this.repoID = repoID; 29 | this.repoName = repoName; 30 | this.repoPath = repoPath; 31 | this.diffFiles = new ArrayList<>(); 32 | this.diffHunks = new ArrayList<>(); 33 | this.idToDiffFileMap = new HashMap<>(); 34 | this.idToDiffHunkMap = new HashMap<>(); 35 | } 36 | 37 | public String getRepoPath() { 38 | return repoPath; 39 | } 40 | 41 | public List getDiffFiles() { 42 | return diffFiles; 43 | } 44 | 45 | public List getDiffHunks() { 46 | return diffHunks; 47 | } 48 | 49 | /** Analyze the current working tree to cache temp data */ 50 | public List analyzeWorkingTree() { 51 | // analyze the diff files and hunks 52 | GitService gitService = new GitServiceCGit(); 53 | ArrayList diffFiles = gitService.getChangedFilesInWorkingTree(this.repoPath); 54 | if (!diffFiles.isEmpty()) { 55 | gitService.getDiffHunksInWorkingTree(this.repoPath, diffFiles); 56 | this.diffFiles = diffFiles; 57 | this.idToDiffFileMap = generateIDToDiffFileMap(); 58 | } 59 | return diffFiles; 60 | } 61 | 62 | /** 63 | * Analyze one specific commit to cache temp data 64 | * 65 | * @param commitID 66 | */ 67 | public List analyzeCommit(String commitID) { 68 | // analyze the diff files and hunks 69 | GitService gitService = new GitServiceCGit(); 70 | ArrayList diffFiles = gitService.getChangedFilesAtCommit(this.repoPath, commitID); 71 | if (!diffFiles.isEmpty()) { 72 | gitService.getDiffHunksAtCommit(this.repoPath, commitID, diffFiles); 73 | this.diffFiles = diffFiles; 74 | this.idToDiffFileMap = generateIDToDiffFileMap(); 75 | } 76 | return diffFiles; 77 | } 78 | 79 | /** 80 | * Generate fileID:diffFile map (for commit stage) 81 | * 82 | * @return 83 | */ 84 | private Map generateIDToDiffFileMap() { 85 | Map idToDiffFileMap = new HashMap<>(); 86 | // map for all diff hunks inside the repo 87 | for (DiffFile diffFile : diffFiles) { 88 | String fileID = Utils.generateUUID(); 89 | idToDiffFileMap.put(fileID, diffFile); 90 | diffFile.setRepoID(repoID); 91 | diffFile.setRepoName(repoName); 92 | diffFile.setFileID(fileID); 93 | // map for diff hunks inside a file 94 | Map diffHunksMap = new HashMap<>(); 95 | for (DiffHunk diffHunk : diffFile.getDiffHunks()) { 96 | String diffHunkID = Utils.generateUUID(); 97 | // for entire file change as a whole diff hunk, fileID == diffHunkID 98 | if (diffFile.getStatus().equals(FileStatus.UNTRACKED) 99 | || diffFile.getStatus().equals(FileStatus.ADDED) 100 | || diffFile.getStatus().equals(FileStatus.DELETED)) { 101 | diffHunkID = fileID; 102 | } 103 | diffHunk.setRepoID(repoID); 104 | diffHunk.setRepoName(repoName); 105 | diffHunk.setFileID(fileID); 106 | diffHunk.setDiffHunkID(diffHunkID); 107 | this.diffHunks.add(diffHunk); 108 | diffHunksMap.put(diffHunkID, diffHunk); 109 | this.idToDiffHunkMap.put(diffHunkID, diffHunk); 110 | } 111 | diffFile.setDiffHunksMap(diffHunksMap); 112 | } 113 | return idToDiffFileMap; 114 | } 115 | 116 | public String getRepoName() { 117 | return repoName; 118 | } 119 | 120 | public Map getIdToDiffFileMap() { 121 | return idToDiffFileMap; 122 | } 123 | 124 | public Map getIdToDiffHunkMap() { 125 | return idToDiffHunkMap; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/core/visitor/IdentifierVisitor.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.core.visitor; 2 | 3 | import org.eclipse.jdt.core.dom.*; 4 | import org.eclipse.jdt.core.util.IConstantValueAttribute; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | 10 | public class IdentifierVisitor extends ASTVisitor { 11 | private List invokedMethods; 12 | private List declaredMethods; 13 | private List declaredVars; 14 | private List instantiatedClasses; 15 | private List declaredFields; 16 | private List accessedFields; 17 | 18 | public IdentifierVisitor() { 19 | this.invokedMethods = new ArrayList<>(); 20 | this.declaredMethods = new ArrayList<>(); 21 | this.declaredVars = new ArrayList<>(); 22 | this.instantiatedClasses = new ArrayList<>(); 23 | this.declaredFields = new ArrayList<>(); 24 | this.accessedFields = new ArrayList<>(); 25 | } 26 | 27 | @Override 28 | public boolean visit(MethodInvocation invocation) { 29 | IMethodBinding binding = invocation.resolveMethodBinding(); 30 | if (binding != null) { 31 | ITypeBinding typeBinding = binding.getDeclaringClass(); 32 | if (typeBinding.isFromSource()) { 33 | this.invokedMethods.add(typeBinding.getQualifiedName() + ":" + binding.toString()); 34 | } 35 | } else { 36 | this.invokedMethods.add(invocation.getExpression().toString()+"."+invocation.getName().toString()); 37 | } 38 | return true; 39 | } 40 | 41 | @Override 42 | public boolean visit(MethodDeclaration declaration) { 43 | IMethodBinding binding = declaration.resolveBinding(); 44 | if (binding != null) { 45 | this.declaredMethods.add( 46 | binding.getDeclaringClass().getQualifiedName() + ":" + binding.toString()); 47 | } 48 | return true; 49 | } 50 | 51 | @Override 52 | public boolean visit(ClassInstanceCreation creation) { 53 | IMethodBinding binding = creation.resolveConstructorBinding(); 54 | if (binding != null) { 55 | ITypeBinding typeBinding = binding.getDeclaringClass(); 56 | if (typeBinding.isFromSource()) { 57 | this.instantiatedClasses.add(typeBinding.getQualifiedName() + ":" + binding.toString()); 58 | } 59 | } 60 | return true; 61 | } 62 | 63 | private List processList(List list) { 64 | List results = new ArrayList<>(); 65 | for (Iterator iter = list.iterator(); iter.hasNext(); ) { 66 | VariableDeclarationFragment fragment = (VariableDeclarationFragment) iter.next(); 67 | IVariableBinding binding = fragment.resolveBinding(); 68 | if (binding != null && binding.getDeclaringClass() != null) { 69 | results.add( 70 | binding.getDeclaringClass().getQualifiedName() 71 | + ":" 72 | + binding.getDeclaringMethod().getName() 73 | + ":" 74 | + binding.toString()); 75 | } 76 | } 77 | return results; 78 | } 79 | 80 | @Override 81 | public boolean visit(VariableDeclarationExpression declaration) { 82 | this.declaredVars.addAll(processList(declaration.fragments())); 83 | return true; 84 | } 85 | 86 | // @Override 87 | // public boolean visit(FieldDeclaration declaration) { 88 | // this.declaredFields.addAll(processList(declaration.fragments())); 89 | // return true; 90 | // } 91 | 92 | @Override 93 | public boolean visit(FieldAccess access) { 94 | IVariableBinding binding = access.resolveFieldBinding(); 95 | if (binding != null) { 96 | ITypeBinding typeBinding = binding.getDeclaringClass(); 97 | if (typeBinding.isFromSource()) { 98 | this.accessedFields.add( 99 | typeBinding.getDeclaringClass().getQualifiedName() + ":" + binding.toString()); 100 | } 101 | } 102 | return true; 103 | } 104 | 105 | public List getInvokedMethods() { 106 | return invokedMethods; 107 | } 108 | 109 | public List getDeclaredMethods() { 110 | return declaredMethods; 111 | } 112 | 113 | public List getDeclaredVars() { 114 | return declaredVars; 115 | } 116 | 117 | public List getInstantiatedClasses() { 118 | return instantiatedClasses; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/core/visitor/MyNodeFinder.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.core.visitor; 2 | 3 | import org.eclipse.jdt.core.*; 4 | import org.eclipse.jdt.core.compiler.IScanner; 5 | import org.eclipse.jdt.core.compiler.ITerminalSymbols; 6 | import org.eclipse.jdt.core.compiler.InvalidInputException; 7 | import org.eclipse.jdt.core.dom.ASTNode; 8 | import org.eclipse.jdt.core.dom.ASTVisitor; 9 | import org.eclipse.jdt.core.dom.SimpleType; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * For a given selection range, finds the covered node and the covering node. 16 | * 17 | * @since 3.5 18 | */ 19 | public final class MyNodeFinder { 20 | /** This class defines the actual visitor that finds the node. */ 21 | private static class MyNodeFinderVisitor extends ASTVisitor { 22 | private int fStart; 23 | private int fEnd; 24 | private ASTNode fCoveringNode; 25 | private ASTNode fCoveredNode; 26 | private List fCoveredNodes; 27 | 28 | MyNodeFinderVisitor(int offset, int length) { 29 | super(true); // include Javadoc tags 30 | this.fStart = offset; 31 | this.fEnd = offset + length; 32 | this.fCoveredNodes = new ArrayList<>(); 33 | } 34 | 35 | @Override 36 | public boolean preVisit2(ASTNode node) { 37 | int nodeStart = node.getStartPosition(); 38 | int nodeEnd = nodeStart + node.getLength(); 39 | if (nodeEnd < this.fStart || this.fEnd < nodeStart) { 40 | return false; 41 | } 42 | if (nodeStart <= this.fStart && this.fEnd <= nodeEnd) { 43 | this.fCoveringNode = node; 44 | } 45 | if (this.fStart <= nodeStart && nodeEnd <= this.fEnd) { 46 | if (this.fCoveringNode == node) { // nodeStart == fStart && nodeEnd == fEnd 47 | this.fCoveredNode = node; 48 | return true; // look further for node with same length as parent 49 | } else if (this.fCoveredNode == null) { // no better found 50 | this.fCoveredNode = node; 51 | } 52 | this.fCoveredNodes.add(node); 53 | return false; 54 | } 55 | return true; 56 | } 57 | /** 58 | * Returns the covered node. If more than one nodes are covered by the selection, the returned 59 | * node is first covered node found in a top-down traversal of the AST 60 | * 61 | * @return ASTNode 62 | */ 63 | public ASTNode getCoveredNode() { 64 | return this.fCoveredNode; 65 | } 66 | 67 | /** 68 | * Returns the covering node. If more than one nodes are covering the selection, the returned 69 | * node is last covering node found in a top-down traversal of the AST 70 | * 71 | * @return ASTNode 72 | */ 73 | public ASTNode getCoveringNode() { 74 | return this.fCoveringNode; 75 | } 76 | 77 | public List getCoveredNodes() { 78 | return this.fCoveredNodes; 79 | } 80 | } 81 | /** 82 | * Maps a selection to an ASTNode, where the selection is defined using a start and a length. The 83 | * result node is determined as follows: 84 | * 85 | *
    86 | *
  • First, tries to find a node whose range is the exactly the given selection. If multiple 87 | * matching nodes are found, the innermost is returned. 88 | *
  • If no such node exists, then the last node in a preorder traversal of the AST is 89 | * returned, where the node range fully contains the selection. If the length is zero, then 90 | * ties between adjacent nodes are broken by choosing the right side. 91 | *
92 | * 93 | * @param root the root node from which the search starts 94 | * @param start the start of the selection 95 | * @param length the length of the selection 96 | * @return the innermost node that exactly matches the selection, or the first node that contains 97 | * the selection 98 | */ 99 | public static ASTNode perform(ASTNode root, int start, int length) { 100 | org.eclipse.jdt.core.dom.NodeFinder finder = 101 | new org.eclipse.jdt.core.dom.NodeFinder(root, start, length); 102 | ASTNode result = finder.getCoveredNode(); 103 | if (result == null || result.getStartPosition() != start || result.getLength() != length) { 104 | return finder.getCoveringNode(); 105 | } 106 | return result; 107 | } 108 | 109 | /** 110 | * Maps a selection to an ASTNode, where the selection is defined using a source range. Calls 111 | * perform(root, range.getOffset(), range.getLength()). 112 | * 113 | * @param root the root node from which the search starts 114 | * @param range the selection range 115 | * @return the innermost node that exactly matches the selection, or the first node that contains 116 | * the selection 117 | * @see #perform(ASTNode, int, int) 118 | */ 119 | public static ASTNode perform(ASTNode root, ISourceRange range) { 120 | return perform(root, range.getOffset(), range.getLength()); 121 | } 122 | 123 | /** 124 | * Maps a selection to an ASTNode, where the selection is given by a start and a length. The 125 | * result node is determined as follows: 126 | * 127 | *
    128 | *
  • If {@link #getCoveredNode()} doesn't find a node, returns null. 129 | *
  • Otherwise, iff the selection only contains the covered node and optionally some 130 | * whitespace or comments on either side of the node, returns the node. 131 | *
  • Otherwise, returns the {@link #getCoveringNode() covering} node. 132 | *
133 | * 134 | * @param root the root node from which the search starts 135 | * @param start the start of the selection 136 | * @param length the length of the selection 137 | * @param source the source of the compilation unit 138 | * @return the result node 139 | * @throws JavaModelException if an error occurs in the Java model 140 | */ 141 | public static ASTNode perform(ASTNode root, int start, int length, ITypeRoot source) 142 | throws JavaModelException { 143 | org.eclipse.jdt.core.dom.NodeFinder finder = 144 | new org.eclipse.jdt.core.dom.NodeFinder(root, start, length); 145 | ASTNode result = finder.getCoveredNode(); 146 | if (result == null) return null; 147 | int nodeStart = result.getStartPosition(); 148 | if (start <= nodeStart && ((nodeStart + result.getLength()) <= (start + length))) { 149 | IBuffer buffer = source.getBuffer(); 150 | if (buffer != null) { 151 | IScanner scanner = ToolFactory.createScanner(false, false, false, false); 152 | try { 153 | scanner.setSource(buffer.getText(start, length).toCharArray()); 154 | int token = scanner.getNextToken(); 155 | if (token != ITerminalSymbols.TokenNameEOF) { 156 | int tStart = scanner.getCurrentTokenStartPosition(); 157 | if (tStart == result.getStartPosition() - start) { 158 | scanner.resetTo(tStart + result.getLength(), length - 1); 159 | token = scanner.getNextToken(); 160 | if (token == ITerminalSymbols.TokenNameEOF) return result; 161 | } 162 | } 163 | } catch (InvalidInputException e) { 164 | // ignore 165 | } catch (IndexOutOfBoundsException e) { 166 | // https://bugs.eclipse.org/bugs/show_bug.cgi?id=305001 167 | return null; 168 | } 169 | } 170 | } 171 | return finder.getCoveringNode(); 172 | } 173 | 174 | private ASTNode fCoveringNode; 175 | private ASTNode fCoveredNode; 176 | private List fCoveredNodes; 177 | 178 | /** 179 | * Instantiate a new node finder using the given root node, the given start and the given length. 180 | * 181 | * @param root the given root node 182 | * @param start the given start 183 | * @param length the given length 184 | */ 185 | public MyNodeFinder(ASTNode root, int start, int length) { 186 | MyNodeFinderVisitor nodeFinderVisitor = new MyNodeFinderVisitor(start, length); 187 | root.accept(nodeFinderVisitor); 188 | this.fCoveredNode = nodeFinderVisitor.getCoveredNode(); 189 | this.fCoveringNode = nodeFinderVisitor.getCoveringNode(); 190 | this.fCoveredNodes = nodeFinderVisitor.getCoveredNodes(); 191 | } 192 | /** 193 | * If the AST contains nodes whose range is equal to the selection, returns the innermost of those 194 | * nodes. Otherwise, returns the first node in a preorder traversal of the AST, where the complete 195 | * node range is covered by the selection. 196 | * 197 | *

Example: For a {@link SimpleType} whose name is a {@link } and a selection that equals both 198 | * nodes' range, the covered node is the SimpleName. But if the selection is expanded 199 | * to include a whitespace before or after the SimpleType, then the covered node is 200 | * the SimpleType. 201 | * 202 | * @return the covered node, or null if the selection is empty or too short to cover 203 | * an entire node 204 | */ 205 | public ASTNode getCoveredNode() { 206 | return this.fCoveredNode; 207 | } 208 | 209 | /** 210 | * Returns the innermost node that fully contains the selection. A node also contains the 211 | * zero-length selection on either end. 212 | * 213 | *

If more than one node covers the selection, the returned node is the last covering node 214 | * found in a preorder traversal of the AST. This implies that for a zero-length selection between 215 | * two adjacent sibling nodes, the node on the right is returned. 216 | * 217 | * @return the covering node 218 | */ 219 | public ASTNode getCoveringNode() { 220 | return this.fCoveringNode; 221 | } 222 | 223 | public List getCoveredNodes() { 224 | return this.fCoveredNodes; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/core/visitor/SimpleVisitor.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.core.visitor; 2 | 3 | import org.eclipse.jdt.core.dom.ASTVisitor; 4 | import org.eclipse.jdt.core.dom.ITypeBinding; 5 | import org.eclipse.jdt.core.dom.SimpleName; 6 | import org.eclipse.jdt.core.dom.SimpleType; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class SimpleVisitor extends ASTVisitor { 12 | 13 | private List simpleTypes; 14 | private List simpleNames; 15 | 16 | public SimpleVisitor() { 17 | this.simpleTypes = new ArrayList<>(); 18 | this.simpleNames = new ArrayList<>(); 19 | } 20 | 21 | @Override 22 | public boolean visit(SimpleType node) { 23 | // System.out.println("------Type--------"); 24 | this.simpleTypes.add(node.getName().toString()); 25 | ITypeBinding binding = node.resolveBinding(); 26 | if (binding != null) { 27 | if (binding.isFromSource()) { 28 | System.out.println(binding.getQualifiedName()); 29 | } 30 | } 31 | return true; 32 | } 33 | 34 | @Override 35 | public boolean visit(SimpleName node) { 36 | // System.out.println("------Name--------"); 37 | this.simpleNames.add(node.getIdentifier()); 38 | // IBinding binding = node.resolveBinding(); 39 | ITypeBinding binding = node.resolveTypeBinding(); 40 | if (binding != null) { 41 | if (binding.isFromSource()) { 42 | System.out.println(binding.getQualifiedName()); 43 | } 44 | } 45 | return true; 46 | } 47 | 48 | public List getSimpleTypes() { 49 | return simpleTypes; 50 | } 51 | 52 | public List getSimpleNames() { 53 | return simpleNames; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/evaluation/DataMiner.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.evaluation; 2 | 3 | import com.github.smartcommit.util.Utils; 4 | import com.mongodb.BasicDBObject; 5 | import com.mongodb.MongoClient; 6 | import com.mongodb.MongoClientURI; 7 | import com.mongodb.client.MongoCollection; 8 | import com.mongodb.client.MongoCursor; 9 | import com.mongodb.client.MongoDatabase; 10 | import org.apache.log4j.BasicConfigurator; 11 | import org.apache.log4j.Level; 12 | import org.bson.Document; 13 | import org.eclipse.jgit.api.Git; 14 | import org.eclipse.jgit.lib.Repository; 15 | import org.eclipse.jgit.revwalk.RevCommit; 16 | import org.eclipse.jgit.revwalk.RevWalk; 17 | import org.refactoringminer.api.GitService; 18 | import org.refactoringminer.util.GitServiceImpl; 19 | 20 | import java.io.BufferedWriter; 21 | import java.io.File; 22 | import java.io.FileWriter; 23 | import java.io.IOException; 24 | import java.nio.file.Path; 25 | import java.nio.file.Paths; 26 | import java.text.BreakIterator; 27 | import java.util.*; 28 | import java.util.regex.Matcher; 29 | import java.util.regex.Pattern; 30 | 31 | /** Mine atomic commits and also composite commits from specified repos */ 32 | public class DataMiner { 33 | // home dir of the local machine 34 | private static final String homeDir = System.getProperty("user.home") + File.separator; 35 | // root dir of all data and results 36 | private static final String dataDir = homeDir + "/smartcommit/"; 37 | private static final String mongoDBUrl = "mongodb://localhost:27017"; 38 | 39 | public static void main(String[] args) { 40 | BasicConfigurator.configure(); 41 | org.apache.log4j.Logger.getRootLogger().setLevel(Level.WARN); 42 | 43 | String repoDir = dataDir + "repos/"; 44 | 45 | String repoName = "rocketmq"; 46 | String repoPath = repoDir + repoName; 47 | // number of examined commits 48 | int numCommits = 0; 49 | // !merge && fix/close/resolve/issue && #issueid/number 50 | // composite: (# > 1 || #predicates > 1 || and/also/plus/too/other || bullet list)) 51 | 52 | GitService gitService = new GitServiceImpl(); 53 | List atomicCommits = new ArrayList<>(); 54 | List compositeCommits = new ArrayList<>(); 55 | Pattern issuePattern = 56 | Pattern.compile("#[0-9]+?\\s+"); // Other special format: jruby-XXX xstr-XXX storm-XXX 57 | Pattern bulletPattern = Pattern.compile("\\*|-\\s+"); 58 | try (Repository repository = gitService.openRepository(repoPath)) { 59 | // iterate commits from master:HEAD 60 | try (RevWalk walk = gitService.createAllRevsWalk(repository, repository.getBranch())) { 61 | System.out.println("Mining repo: " + repoName + " on branch: " + repository.getBranch()); 62 | for (RevCommit commit : walk) { 63 | numCommits += 1; 64 | // no merge commits 65 | if (commit.getParentCount() == 1) { 66 | String msg = commit.getFullMessage().toLowerCase(); 67 | // focused on issues 68 | if (anyMatch(msg, new String[] {"issue", "#", "fix", "close", "resolve", "solve"})) { 69 | 70 | // extract issue ids 71 | Set issueIDs = new HashSet<>(); 72 | Matcher issueMatcher = issuePattern.matcher(msg); 73 | while (issueMatcher.find()) { 74 | issueIDs.add(issueMatcher.group()); 75 | } 76 | 77 | Matcher bulletMatcher = bulletPattern.matcher(msg); 78 | int bulletNum = 0; 79 | while (bulletMatcher.find()) { 80 | bulletNum++; 81 | } 82 | 83 | if (bulletNum > 1 84 | || containsMultipleVerbs(commit.getFullMessage()) 85 | || issueIDs.size() > 1) { 86 | compositeCommits.add(commit); 87 | System.out.println("[C]" + commit.getName()); 88 | } else { 89 | atomicCommits.add(commit); 90 | System.out.println("[A]" + commit.getName()); 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | System.out.println("[Total]: " + numCommits); 98 | System.out.println( 99 | "[Atomic]: " 100 | + atomicCommits.size() 101 | + "(" 102 | + Utils.formatDouble((double) atomicCommits.size() * 100 / numCommits) 103 | + "%)"); 104 | // save results into mongodb 105 | saveSamplesInDB(repoName, "atomic", atomicCommits); 106 | // write results into csv file 107 | // saveSamplesInCSV(atomicCommits, resultsDir + repoName + "_atomic.csv"); 108 | } catch (Exception e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | 113 | private static boolean containsMultipleVerbs(String msg) { 114 | List sentences = new ArrayList<>(); 115 | BreakIterator iterator = BreakIterator.getSentenceInstance(Locale.US); 116 | iterator.setText(msg); 117 | int start = iterator.first(); 118 | for (int end = iterator.next(); end != BreakIterator.DONE; start = end, end = iterator.next()) { 119 | sentences.add(msg.substring(start, end).toLowerCase()); 120 | } 121 | String[] verbs = 122 | new String[] { 123 | "add", 124 | "fix", 125 | "change", 126 | "modif", 127 | "remove", 128 | "delete", 129 | "refactor", 130 | "format", 131 | "rename", 132 | "reformat", 133 | "patch", 134 | "clean" 135 | }; 136 | // String[] words = msg.split("\\s+"); 137 | int num = 0; 138 | for (String sentence : sentences) { 139 | if (anyMatch(sentence, verbs)) { 140 | num += 1; 141 | if (num > 1) { 142 | return true; 143 | } 144 | } 145 | } 146 | return false; 147 | } 148 | 149 | private static boolean anyMatch(String str, String[] keywords) { 150 | return Arrays.stream(keywords).parallel().anyMatch(str::contains); 151 | } 152 | 153 | /** 154 | * Save samples in mongodb 155 | * 156 | * @param repoName 157 | * @param dbName 158 | * @param commits 159 | */ 160 | private static void saveSamplesInDB(String repoName, String dbName, List commits) { 161 | MongoClientURI connectionString = new MongoClientURI(mongoDBUrl); 162 | MongoClient mongoClient = new MongoClient(connectionString); 163 | MongoDatabase db = mongoClient.getDatabase(dbName); 164 | MongoCollection col = db.getCollection(repoName); 165 | // !!! drop the last testing results 166 | col.drop(); 167 | 168 | for (RevCommit commit : commits) { 169 | Document commitDoc = new Document("repo_name", repoName); 170 | commitDoc 171 | .append("commit_id", commit.getName()) 172 | .append("commit_time", commit.getAuthorIdent().getWhen()) 173 | .append("committer_name", commit.getAuthorIdent().getName()) 174 | .append("committer_email", commit.getAuthorIdent().getEmailAddress()) 175 | .append("commit_msg", commit.getFullMessage()); 176 | 177 | col.insertOne(commitDoc); 178 | } 179 | mongoClient.close(); 180 | } 181 | 182 | /** 183 | * Save samples in csv files 184 | * 185 | * @param commits 186 | * @param resultFilePath 187 | * @throws IOException 188 | */ 189 | private static void saveSamplesInCSV(List commits, String resultFilePath) 190 | throws IOException { 191 | File file = new File(resultFilePath); 192 | 193 | if (!file.exists() && !file.isDirectory()) { 194 | // file.mkdirs(); 195 | file.createNewFile(); 196 | } 197 | 198 | try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) { 199 | for (RevCommit commit : commits) { 200 | bw.write(commit.getName() + "~~" + commit.getFullMessage().trim().replaceAll("\n", "")); 201 | bw.newLine(); 202 | bw.flush(); 203 | } 204 | } 205 | } 206 | /** 207 | * Yet another way to list all commits 208 | * 209 | * @param repoPath 210 | */ 211 | private void listCommits(String repoPath) { 212 | Path path = Paths.get(repoPath); 213 | try (Git git = Git.open(path.toFile())) { 214 | Iterable commits = git.log().all().call(); 215 | Repository repository = git.getRepository(); 216 | String branch = repository.getBranch(); 217 | System.out.println(branch); 218 | for (Iterator iter = commits.iterator(); iter.hasNext(); ) { 219 | RevCommit commit = iter.next(); 220 | System.out.println(commit.getAuthorIdent()); 221 | System.out.println(commit.getFullMessage()); 222 | } 223 | } catch (Exception e) { 224 | e.printStackTrace(); 225 | } 226 | } 227 | 228 | private static void getAllEmails(String repoName) { 229 | System.out.println("Repo: " + repoName); 230 | MongoClientURI server = new MongoClientURI(mongoDBUrl); 231 | MongoClient serverClient = new MongoClient(server); 232 | 233 | try { 234 | MongoDatabase db = serverClient.getDatabase("smartcommit"); 235 | MongoCollection col = db.getCollection("contacts"); 236 | BasicDBObject condition = 237 | new BasicDBObject( 238 | "repos", new BasicDBObject("$elemMatch", new BasicDBObject("repo", repoName))); 239 | // Bson condition = Filters.elemMatch("repos", Fil); 240 | 241 | try (MongoCursor cursor = col.find(condition).iterator()) { 242 | while (cursor.hasNext()) { 243 | Document doc = cursor.next(); 244 | System.out.println((doc.get("email").toString())); 245 | } 246 | } 247 | 248 | serverClient.close(); 249 | } catch (Exception e) { 250 | e.printStackTrace(); 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/io/DiffGraphExporter.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.io; 2 | 3 | import com.github.smartcommit.model.diffgraph.DiffEdge; 4 | import com.github.smartcommit.model.diffgraph.DiffNode; 5 | import org.jgrapht.Graph; 6 | import org.jgrapht.io.ComponentAttributeProvider; 7 | import org.jgrapht.io.ComponentNameProvider; 8 | import org.jgrapht.io.DOTExporter; 9 | import org.jgrapht.io.ExportException; 10 | 11 | import java.io.StringWriter; 12 | import java.io.Writer; 13 | 14 | public class DiffGraphExporter { 15 | public static String exportAsDotWithType(Graph graph) { 16 | try { 17 | // use helper classes to define how vertices should be rendered, 18 | // adhering to the DOT language restrictions 19 | ComponentNameProvider vertexIdProvider = diffNode -> diffNode.getId().toString(); 20 | ComponentNameProvider vertexLabelProvider = diffNode -> diffNode.getIndex(); 21 | ComponentAttributeProvider vertexAttributeProvider = new DiffTypeProvider(); 22 | 23 | ComponentNameProvider edgeLabelProvider = 24 | edge -> edge.getType().asString() + "(" + edge.getWeight().toString() + ")"; 25 | ComponentAttributeProvider edgeAttributeProvider = new DiffTypeProvider(); 26 | org.jgrapht.io.GraphExporter exporter = 27 | new DOTExporter<>( 28 | vertexIdProvider, 29 | vertexLabelProvider, 30 | edgeLabelProvider, 31 | vertexAttributeProvider, 32 | edgeAttributeProvider); 33 | Writer writer = new StringWriter(); 34 | exporter.exportGraph(graph, writer); 35 | return writer.toString(); 36 | 37 | } catch (ExportException e) { 38 | e.printStackTrace(); 39 | return ""; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/io/DiffTypeProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.io; 2 | 3 | import com.github.smartcommit.model.diffgraph.DiffEdge; 4 | import com.github.smartcommit.model.diffgraph.DiffNode; 5 | import org.jgrapht.io.Attribute; 6 | import org.jgrapht.io.AttributeType; 7 | import org.jgrapht.io.ComponentAttributeProvider; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class DiffTypeProvider implements ComponentAttributeProvider { 13 | @Override 14 | public Map getComponentAttributes(Object component) { 15 | if (component instanceof DiffNode) { 16 | DiffNode node = (DiffNode) component; 17 | Map map = new HashMap<>(); 18 | map.put("color", new DiffNodeColorAttribute(node)); 19 | map.put("shape", new DiffNodeShapeAttribute(node)); 20 | return map; 21 | } 22 | if (component instanceof DiffEdge) { 23 | DiffEdge edge = (DiffEdge) component; 24 | Map map = new HashMap<>(); 25 | map.put("type", new DiffEdgeTypeAttribute(edge)); 26 | map.put("color", new DiffEdgeColorAttribute(edge)); 27 | map.put("style", new DiffEdgeStyleAttribute(edge)); 28 | return map; 29 | } 30 | return null; 31 | } 32 | } 33 | 34 | class DiffNodeShapeAttribute implements Attribute { 35 | private DiffNode node; 36 | 37 | public DiffNodeShapeAttribute(DiffNode node) { 38 | this.node = node; 39 | } 40 | 41 | @Override 42 | public String getValue() { 43 | return "record"; 44 | } 45 | 46 | @Override 47 | public AttributeType getType() { 48 | return AttributeType.STRING; 49 | } 50 | } 51 | 52 | class DiffNodeColorAttribute implements Attribute { 53 | private DiffNode node; 54 | 55 | public DiffNodeColorAttribute(DiffNode node) { 56 | this.node = node; 57 | } 58 | 59 | @Override 60 | public String getValue() { 61 | return "blue"; 62 | } 63 | 64 | @Override 65 | public AttributeType getType() { 66 | return AttributeType.STRING; 67 | } 68 | } 69 | 70 | class DiffEdgeTypeAttribute implements Attribute { 71 | private DiffEdge edge; 72 | 73 | public DiffEdgeTypeAttribute(DiffEdge edge) { 74 | this.edge = edge; 75 | } 76 | 77 | @Override 78 | public String getValue() { 79 | return edge.getType().asString(); 80 | } 81 | 82 | @Override 83 | public AttributeType getType() { 84 | return AttributeType.STRING; 85 | } 86 | } 87 | 88 | class DiffEdgeColorAttribute implements Attribute { 89 | private DiffEdge edge; 90 | 91 | public DiffEdgeColorAttribute(DiffEdge edge) { 92 | this.edge = edge; 93 | } 94 | 95 | @Override 96 | public String getValue() { 97 | return edge.getType().isConstraint() ? "red" : "black"; 98 | } 99 | 100 | @Override 101 | public AttributeType getType() { 102 | return AttributeType.STRING; 103 | } 104 | } 105 | 106 | class DiffEdgeStyleAttribute implements Attribute { 107 | private DiffEdge edge; 108 | 109 | public DiffEdgeStyleAttribute(DiffEdge edge) { 110 | this.edge = edge; 111 | } 112 | 113 | @Override 114 | public String getValue() { 115 | return edge.getType().isConstraint() ? "solid" : "dashed"; 116 | } 117 | 118 | @Override 119 | public AttributeType getType() { 120 | return AttributeType.STRING; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/io/GraphExporter.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.io; 2 | 3 | import com.github.smartcommit.model.graph.Edge; 4 | import com.github.smartcommit.model.graph.Node; 5 | import com.github.smartcommit.util.Utils; 6 | import org.jgrapht.Graph; 7 | import org.jgrapht.io.ComponentAttributeProvider; 8 | import org.jgrapht.io.ComponentNameProvider; 9 | import org.jgrapht.io.DOTExporter; 10 | import org.jgrapht.io.ExportException; 11 | 12 | import java.io.StringWriter; 13 | import java.io.Writer; 14 | 15 | public class GraphExporter { 16 | /** Export a graph into DOT format. */ 17 | public static String exportAsDot(Graph graph) { 18 | try { 19 | // use helper classes to define how vertices should be rendered, 20 | // adhering to the DOT language restrictions 21 | ComponentNameProvider vertexIdProvider = node -> node.getId().toString(); 22 | ComponentNameProvider vertexLabelProvider = node -> node.getIdentifier(); 23 | ComponentNameProvider edgeLabelProvider = edge -> edge.getType().asString(); 24 | org.jgrapht.io.GraphExporter exporter = 25 | new DOTExporter<>(vertexIdProvider, vertexLabelProvider, edgeLabelProvider); 26 | Writer writer = new StringWriter(); 27 | exporter.exportGraph(graph, writer); 28 | return writer.toString(); 29 | 30 | } catch (ExportException e) { 31 | e.printStackTrace(); 32 | return ""; 33 | } 34 | } 35 | 36 | public static String exportAsDotWithType(Graph graph) { 37 | try { 38 | // use helper classes to define how vertices should be rendered, 39 | // adhering to the DOT language restrictions 40 | ComponentNameProvider vertexIdProvider = node -> node.getId().toString(); 41 | ComponentNameProvider vertexLabelProvider = 42 | node -> 43 | node.isInDiffHunk 44 | ? node.getIdentifier() + "(" + node.diffHunkIndex + ")" 45 | : node.getIdentifier(); 46 | ComponentAttributeProvider vertexAttributeProvider = new TypeProvider(); 47 | 48 | ComponentNameProvider edgeLabelProvider = 49 | edge -> edge.getType().asString() + "(" + edge.getWeight() + ")"; 50 | ComponentAttributeProvider edgeAttributeProvider = new TypeProvider(); 51 | org.jgrapht.io.GraphExporter exporter = 52 | new DOTExporter<>( 53 | vertexIdProvider, 54 | vertexLabelProvider, 55 | edgeLabelProvider, 56 | vertexAttributeProvider, 57 | edgeAttributeProvider); 58 | Writer writer = new StringWriter(); 59 | exporter.exportGraph(graph, writer); 60 | return writer.toString(); 61 | 62 | } catch (ExportException e) { 63 | e.printStackTrace(); 64 | return ""; 65 | } 66 | } 67 | 68 | /** 69 | * Print the graph to console for debugging 70 | * 71 | * @param graph 72 | */ 73 | public static void printAsDot(Graph graph) { 74 | System.out.println(exportAsDotWithType(graph)); 75 | } 76 | 77 | /** 78 | * Save the exported dot to file 79 | * 80 | * @param graph 81 | */ 82 | public static void saveAsDot(Graph graph, String filePath) { 83 | Utils.writeStringToFile(exportAsDotWithType(graph), filePath); 84 | } 85 | 86 | public static void printVertexAndEdge(Graph graph) { 87 | for (Node node : graph.vertexSet()) { 88 | System.out.println(node); 89 | } 90 | System.out.println("------------------------------"); 91 | for (Edge edge : graph.edgeSet()) { 92 | Node source = graph.getEdgeSource(edge); 93 | Node target = graph.getEdgeTarget(edge); 94 | System.out.println( 95 | source.getIdentifier() + " " + edge.getType().asString() + " " + target.getIdentifier()); 96 | } 97 | System.out.println("------------------------------"); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/io/TypeProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.io; 2 | 3 | import com.github.smartcommit.model.graph.Edge; 4 | import com.github.smartcommit.model.graph.Node; 5 | import org.jgrapht.io.Attribute; 6 | import org.jgrapht.io.AttributeType; 7 | import org.jgrapht.io.ComponentAttributeProvider; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class TypeProvider implements ComponentAttributeProvider { 13 | @Override 14 | public Map getComponentAttributes(Object component) { 15 | if (component instanceof Node) { 16 | Node node = (Node) component; 17 | Map map = new HashMap<>(); 18 | map.put("type", new NodeTypeAttribute(node)); 19 | map.put("color", new NodeColorAttribute(node)); 20 | map.put("shape", new NodeShapeAttribute(node)); 21 | return map; 22 | } 23 | if (component instanceof Edge) { 24 | Edge edge = (Edge) component; 25 | Map map = new HashMap<>(); 26 | map.put("type", new EdgeTypeAttribute(edge)); 27 | map.put("color", new EdgeColorAttribute(edge)); 28 | map.put("style", new EdgeStyleAttribute(edge)); 29 | return map; 30 | } 31 | return null; 32 | } 33 | } 34 | 35 | class NodeShapeAttribute implements Attribute { 36 | private Node node; 37 | 38 | public NodeShapeAttribute(Node node) { 39 | this.node = node; 40 | } 41 | 42 | @Override 43 | public String getValue() { 44 | switch (node.getType()) { 45 | case PACKAGE: 46 | return "folder"; 47 | case CLASS: 48 | return "component"; 49 | case INTERFACE: 50 | return "polygon"; 51 | case ENUM: 52 | return "septagon"; 53 | case ANNOTATION: 54 | return "cds"; 55 | case METHOD: 56 | return "ellipse"; 57 | case FIELD: 58 | return "box"; 59 | case HUNK: 60 | return "diamond"; 61 | default: 62 | return ""; 63 | } 64 | } 65 | 66 | @Override 67 | public AttributeType getType() { 68 | return AttributeType.STRING; 69 | } 70 | } 71 | 72 | class NodeTypeAttribute implements Attribute { 73 | private Node node; 74 | 75 | public NodeTypeAttribute(Node node) { 76 | this.node = node; 77 | } 78 | 79 | @Override 80 | public String getValue() { 81 | return node.getType().asString(); 82 | } 83 | 84 | @Override 85 | public AttributeType getType() { 86 | return AttributeType.STRING; 87 | } 88 | } 89 | 90 | class NodeColorAttribute implements Attribute { 91 | private Node node; 92 | 93 | public NodeColorAttribute(Node node) { 94 | this.node = node; 95 | } 96 | 97 | @Override 98 | public String getValue() { 99 | return node.isInDiffHunk ? "red" : "black"; 100 | } 101 | 102 | @Override 103 | public AttributeType getType() { 104 | return AttributeType.STRING; 105 | } 106 | } 107 | 108 | class EdgeTypeAttribute implements Attribute { 109 | private Edge edge; 110 | 111 | public EdgeTypeAttribute(Edge edge) { 112 | this.edge = edge; 113 | } 114 | 115 | @Override 116 | public String getValue() { 117 | return edge.getType().asString(); 118 | } 119 | 120 | @Override 121 | public AttributeType getType() { 122 | return AttributeType.STRING; 123 | } 124 | } 125 | 126 | class EdgeColorAttribute implements Attribute { 127 | private Edge edge; 128 | 129 | public EdgeColorAttribute(Edge edge) { 130 | this.edge = edge; 131 | } 132 | 133 | @Override 134 | public String getValue() { 135 | return edge.getType().isStructural() ? "black" : "blue"; 136 | } 137 | 138 | @Override 139 | public AttributeType getType() { 140 | return AttributeType.STRING; 141 | } 142 | } 143 | 144 | class EdgeStyleAttribute implements Attribute { 145 | private Edge edge; 146 | 147 | public EdgeStyleAttribute(Edge edge) { 148 | this.edge = edge; 149 | } 150 | 151 | @Override 152 | public String getValue() { 153 | return edge.getType().isStructural() ? "solid" : "dashed"; 154 | } 155 | 156 | @Override 157 | public AttributeType getType() { 158 | return AttributeType.STRING; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/Action.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model; 2 | 3 | import com.github.smartcommit.model.constant.Operation; 4 | 5 | /** One of the semantic change actions of the diff hunk */ 6 | public class Action { 7 | private Operation operation; 8 | private String typeFrom = ""; 9 | private String labelFrom = ""; 10 | 11 | // if modify the type or label 12 | private String typeTo = ""; 13 | private String labelTo = ""; 14 | 15 | public Action(Operation operation, String typeFrom, String labelFrom) { 16 | this.operation = operation; 17 | this.typeFrom = typeFrom; 18 | this.labelFrom = labelFrom.trim(); 19 | this.typeTo = ""; 20 | this.labelTo = ""; 21 | } 22 | 23 | public Action( 24 | Operation operation, String typeFrom, String labelFrom, String typeTo, String labelTo) { 25 | this.operation = operation == null ? Operation.UKN : operation; 26 | this.typeFrom = typeFrom == null ? "" : typeFrom; 27 | this.labelFrom = labelFrom == null ? "" : labelFrom.trim(); 28 | this.typeTo = typeTo == null ? "" : typeTo; 29 | this.labelTo = labelTo == null ? "" : labelTo.trim(); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | StringBuilder builder = new StringBuilder(); 35 | builder.append(operation); 36 | builder 37 | .append(typeFrom.isEmpty() ? "" : " " + typeFrom) 38 | .append(labelFrom.isEmpty() ? "" : " \"" + labelFrom + "\""); 39 | if (!typeFrom.equals(typeTo)) { 40 | builder.append(typeTo.isEmpty() ? "" : " To " + typeTo); 41 | if (!labelFrom.equals(labelTo)) { 42 | builder.append(labelTo.isEmpty() ? "" : ": \"" + labelTo + "\""); 43 | } 44 | } else { 45 | if (!labelFrom.equals(labelTo)) { 46 | builder.append(labelTo.isEmpty() ? "" : " To: \"" + labelTo + "\""); 47 | } 48 | } 49 | 50 | builder.append("."); 51 | 52 | return builder.toString(); 53 | } 54 | 55 | @Override 56 | public boolean equals(Object obj) { 57 | if (obj == this) { 58 | return true; 59 | } 60 | 61 | if (!(obj instanceof Action)) { 62 | return false; 63 | } 64 | 65 | Action a = (Action) obj; 66 | return a.operation.equals(this.operation) 67 | && a.typeFrom.equals(this.typeFrom) 68 | && a.typeTo.equals(this.typeTo) 69 | && a.labelFrom.equals(this.labelFrom) 70 | && a.labelTo.equals(this.labelTo); 71 | } 72 | 73 | public int getOperationIndex() { 74 | return operation.index; 75 | } 76 | 77 | public Operation getOperation() { 78 | return operation; 79 | } 80 | 81 | public String getTypeFrom() { 82 | return typeFrom; 83 | } 84 | 85 | public String getLabelFrom() { 86 | return labelFrom; 87 | } 88 | 89 | public String getTypeTo() { 90 | return typeTo; 91 | } 92 | 93 | public String getLabelTo() { 94 | return labelTo; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/DiffFile.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model; 2 | 3 | import com.github.smartcommit.model.constant.FileStatus; 4 | import com.github.smartcommit.model.constant.FileType; 5 | import com.github.smartcommit.model.constant.Version; 6 | 7 | import java.nio.charset.Charset; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class DiffFile { 14 | private String repoID; 15 | private String repoName; 16 | private String fileID; 17 | private Charset charset; 18 | private Integer index; // the index of the diff file in the current repo, start from 0 19 | private FileStatus status; 20 | private FileType fileType; 21 | private String baseRelativePath; 22 | private String currentRelativePath; 23 | private String baseContent; 24 | private String currentContent; 25 | private String description; 26 | private Map diffHunksMap; 27 | private transient List diffHunks; 28 | // lines from the raw output of git-diff (for patch generation) 29 | private List rawHeaders = new ArrayList<>(); 30 | 31 | public DiffFile( 32 | Integer index, 33 | FileStatus status, 34 | FileType fileType, 35 | Charset charset, 36 | String baseRelativePath, 37 | String currentRelativePath, 38 | String baseContent, 39 | String currentContent) { 40 | this.index = index; 41 | this.status = status; 42 | this.fileType = fileType; 43 | this.charset = charset; 44 | this.baseRelativePath = baseRelativePath; 45 | this.currentRelativePath = currentRelativePath; 46 | this.baseContent = baseContent; 47 | this.currentContent = currentContent; 48 | this.description = status.label; 49 | this.diffHunks = new ArrayList<>(); 50 | this.diffHunksMap = new HashMap<>(); 51 | } 52 | 53 | /** Constructor to clone object for json serialization */ 54 | public DiffFile( 55 | String repoID, 56 | String repoName, 57 | String fileID, 58 | Integer index, 59 | FileStatus status, 60 | FileType fileType, 61 | String baseRelativePath, 62 | String currentRelativePath, 63 | String baseContent, 64 | String currentContent, 65 | Map diffHunksMap) { 66 | this.repoID = repoID; 67 | this.repoName = repoName; 68 | this.fileID = fileID; 69 | this.index = index; 70 | this.status = status; 71 | this.description = status.label; 72 | this.fileType = fileType; 73 | this.baseRelativePath = baseRelativePath; 74 | this.currentRelativePath = currentRelativePath; 75 | this.baseContent = baseContent; 76 | this.currentContent = currentContent; 77 | this.diffHunksMap = diffHunksMap; 78 | } 79 | 80 | public String getRepoID() { 81 | return repoID; 82 | } 83 | 84 | public String getRepoName() { 85 | return repoName; 86 | } 87 | 88 | public String getFileID() { 89 | return fileID; 90 | } 91 | 92 | public Charset getCharset() { 93 | return charset; 94 | } 95 | 96 | public void setRepoID(String repoID) { 97 | this.repoID = repoID; 98 | } 99 | 100 | public void setRepoName(String repoName) { 101 | this.repoName = repoName; 102 | } 103 | 104 | public void setFileID(String fileID) { 105 | this.fileID = fileID; 106 | } 107 | 108 | public List getRawHeaders() { 109 | return rawHeaders; 110 | } 111 | 112 | public void setRawHeaders(List rawHeaders) { 113 | this.rawHeaders = rawHeaders; 114 | } 115 | 116 | public FileStatus getStatus() { 117 | return status; 118 | } 119 | 120 | public FileType getFileType() { 121 | return fileType; 122 | } 123 | 124 | public String getBaseRelativePath() { 125 | return baseRelativePath; 126 | } 127 | 128 | public String getCurrentRelativePath() { 129 | return currentRelativePath; 130 | } 131 | 132 | public String getRelativePathOf(Version version) { 133 | if (version.equals(Version.BASE)) { 134 | return getBaseRelativePath(); 135 | } else if (version.equals(Version.CURRENT)) { 136 | return getCurrentRelativePath(); 137 | } 138 | return ""; 139 | } 140 | 141 | public String getBaseContent() { 142 | return baseContent; 143 | } 144 | 145 | public String getCurrentContent() { 146 | return currentContent; 147 | } 148 | 149 | public void setIndex(Integer index) { 150 | this.index = index; 151 | } 152 | 153 | public Integer getIndex() { 154 | return index; 155 | } 156 | 157 | public List getDiffHunks() { 158 | return diffHunks; 159 | } 160 | 161 | public Map getDiffHunksMap() { 162 | return diffHunksMap; 163 | } 164 | 165 | public void setDiffHunksMap(Map diffHunksMap) { 166 | this.diffHunksMap = diffHunksMap; 167 | } 168 | 169 | public void setDiffHunks(List diffHunks) { 170 | this.diffHunks = diffHunks; 171 | } 172 | 173 | /** 174 | * Clone the object for json serialization 175 | * 176 | * @return 177 | */ 178 | public DiffFile shallowClone() { 179 | DiffFile diffFile = 180 | new DiffFile( 181 | repoID, 182 | repoName, 183 | fileID, 184 | index, 185 | status, 186 | fileType, 187 | baseRelativePath, 188 | currentRelativePath, 189 | "", 190 | "", 191 | diffHunksMap); 192 | diffFile.setRawHeaders(this.rawHeaders); 193 | return diffFile; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/DiffHunk.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model; 2 | 3 | import com.github.smartcommit.model.constant.ChangeType; 4 | import com.github.smartcommit.model.constant.ContentType; 5 | import com.github.smartcommit.model.constant.FileType; 6 | import com.github.smartcommit.model.constant.Version; 7 | import org.apache.commons.lang3.tuple.Pair; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class DiffHunk { 13 | private String repoID; 14 | private String repoName; 15 | private String fileID; 16 | private String diffHunkID; 17 | private String commitID; 18 | 19 | private Integer fileIndex; // the index of the diff file 20 | private Integer index; // the index of the diff hunk in the current file diff, start from 0 21 | private Hunk baseHunk; 22 | private Hunk currentHunk; 23 | private FileType fileType; 24 | private ChangeType changeType; 25 | private transient List astActions = new ArrayList<>(); 26 | private transient List refActions = new ArrayList<>(); 27 | private String description = ""; 28 | 29 | // lines from the raw output of git-diff (for patch generation) 30 | private List rawDiffs = new ArrayList<>(); 31 | 32 | public DiffHunk( 33 | Integer index, FileType fileType, ChangeType changeType, Hunk baseHunk, Hunk currentHunk) { 34 | this.index = index; 35 | this.fileType = fileType; 36 | this.baseHunk = baseHunk; 37 | this.currentHunk = currentHunk; 38 | this.changeType = changeType; 39 | } 40 | 41 | public DiffHunk( 42 | Integer index, 43 | FileType fileType, 44 | ChangeType changeType, 45 | Hunk baseHunk, 46 | Hunk currentHunk, 47 | String description) { 48 | this.index = index; 49 | this.fileType = fileType; 50 | this.baseHunk = baseHunk; 51 | this.currentHunk = currentHunk; 52 | this.changeType = changeType; 53 | this.description = description; 54 | } 55 | 56 | public Integer getIndex() { 57 | return index; 58 | } 59 | 60 | public Hunk getBaseHunk() { 61 | return baseHunk; 62 | } 63 | 64 | public Hunk getCurrentHunk() { 65 | return currentHunk; 66 | } 67 | 68 | public String getRepoID() { 69 | return repoID; 70 | } 71 | 72 | public String getFileID() { 73 | return fileID; 74 | } 75 | 76 | public void setFileID(String fileID) { 77 | this.fileID = fileID; 78 | } 79 | 80 | public void setRepoID(String repoID) { 81 | this.repoID = repoID; 82 | } 83 | 84 | public String getRepoName() { 85 | return repoName; 86 | } 87 | 88 | public void setRepoName(String repoName) { 89 | this.repoName = repoName; 90 | } 91 | 92 | public String getDiffHunkID() { 93 | return diffHunkID; 94 | } 95 | 96 | public void setDiffHunkID(String diffHunkID) { 97 | this.diffHunkID = diffHunkID; 98 | } 99 | 100 | public String getCommitID() { 101 | return commitID; 102 | } 103 | 104 | public void setCommitID(String commitID) { 105 | this.commitID = commitID; 106 | } 107 | 108 | public Integer getBaseStartLine() { 109 | return baseHunk.getStartLine(); 110 | } 111 | 112 | public Integer getBaseEndLine() { 113 | return baseHunk.getEndLine(); 114 | } 115 | 116 | public Integer getCurrentStartLine() { 117 | return currentHunk.getStartLine(); 118 | } 119 | 120 | public Integer getCurrentEndLine() { 121 | return currentHunk.getEndLine(); 122 | } 123 | 124 | public Pair getCodeRangeOf(Version version) { 125 | if (version.equals(Version.BASE)) { 126 | return Pair.of(getBaseStartLine(), getBaseEndLine()); 127 | } else if (version.equals(Version.CURRENT)) { 128 | return Pair.of(getCurrentStartLine(), getCurrentEndLine()); 129 | } 130 | return Pair.of(-1, -1); 131 | } 132 | 133 | public Integer getFileIndex() { 134 | return fileIndex; 135 | } 136 | 137 | public void setFileIndex(Integer fileIndex) { 138 | this.fileIndex = fileIndex; 139 | } 140 | 141 | public String getUniqueIndex() { 142 | return fileIndex + ":" + index; 143 | } 144 | 145 | public FileType getFileType() { 146 | return fileType; 147 | } 148 | 149 | public ChangeType getChangeType() { 150 | return changeType; 151 | } 152 | 153 | public List getRawDiffs() { 154 | return rawDiffs; 155 | } 156 | 157 | public void setRawDiffs(List rawDiffs) { 158 | this.rawDiffs = rawDiffs; 159 | } 160 | 161 | public List getAstActions() { 162 | return astActions; 163 | } 164 | 165 | public List getRefActions() { 166 | return refActions; 167 | } 168 | 169 | public void addASTAction(Action action) { 170 | if (!astActions.contains(action)) { 171 | astActions.add(action); 172 | } 173 | } 174 | 175 | public void setAstActions(List astActions) { 176 | this.astActions = astActions; 177 | } 178 | 179 | public void addRefAction(Action action) { 180 | if (!refActions.contains(action)) { 181 | refActions.add(action); 182 | } 183 | } 184 | 185 | public void setRefActions(List refActions) { 186 | this.refActions = refActions; 187 | } 188 | 189 | public String getUUID() { 190 | return fileID + ":" + diffHunkID; 191 | } 192 | 193 | public boolean containsCode() { 194 | return baseHunk.getContentType().equals(ContentType.CODE) 195 | || baseHunk.getContentType().equals(ContentType.IMPORT) 196 | || currentHunk.getContentType().equals(ContentType.CODE) 197 | || currentHunk.getContentType().equals(ContentType.IMPORT); 198 | } 199 | 200 | /** 201 | * Generate a string description from the actions 202 | * 203 | * @return 204 | */ 205 | public void generateDescription() { 206 | StringBuilder builder = new StringBuilder(); 207 | for (Action action : astActions) { 208 | builder.append(action.toString()).append(System.lineSeparator()); 209 | } 210 | for (Action action : refActions) { 211 | builder.append(action.toString()).append(System.lineSeparator()); 212 | } 213 | description = builder.toString(); 214 | } 215 | 216 | public String getDescription() { 217 | if (description.isEmpty()) { 218 | generateDescription(); 219 | } 220 | return description; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/EntityPool.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model; 2 | 3 | import com.github.smartcommit.model.entity.*; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class EntityPool { 9 | private String srcDir; 10 | public Map classInfoMap; 11 | public Map interfaceInfoMap; 12 | public Map enumInfoMap; 13 | public Map enumConstantInfoMap; 14 | public Map annotationInfoMap; 15 | public Map methodInfoMap; 16 | public Map fieldInfoMap; 17 | public Map initBlockInfoMap; // initializer blocks 18 | public Map hunkInfoMap; 19 | // fileIndex : importedType : hunkInfo 20 | public Map> importInfoMap; 21 | 22 | public EntityPool(String srcDir) { 23 | this.srcDir = srcDir; 24 | classInfoMap = new HashMap<>(); 25 | interfaceInfoMap = new HashMap<>(); 26 | enumInfoMap = new HashMap<>(); 27 | enumConstantInfoMap = new HashMap<>(); 28 | annotationInfoMap = new HashMap<>(); 29 | methodInfoMap = new HashMap<>(); 30 | fieldInfoMap = new HashMap<>(); 31 | initBlockInfoMap = new HashMap<>(); 32 | hunkInfoMap = new HashMap<>(); 33 | importInfoMap = new HashMap<>(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/Group.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model; 2 | 3 | import com.github.smartcommit.model.constant.GroupLabel; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | /** The output result, one group for one commit */ 11 | public class Group { 12 | private String repoID; 13 | private String repoName; 14 | private String groupID; 15 | // fileIndex:diffHunkIndex 16 | private List diffHunkIndices; 17 | // fileID:diffHunkID 18 | // if fileID==diffHunkID, status is UNTRACKED, the whole file is a diff hunk 19 | private List diffHunkIDs; 20 | 21 | // system recommendation 22 | private GroupLabel intentLabel; 23 | private List recommendedCommitMsgs = new ArrayList<>(); 24 | // user choice 25 | private String commitID = ""; 26 | private String commitMsg = ""; 27 | 28 | // record link categories for interpretability 29 | // transient? 30 | private Set linkCategories = new HashSet<>(); 31 | 32 | public Group( 33 | String repoID, String repoName, String groupID, List diffHunkIDs, GroupLabel label) { 34 | this.repoID = repoID; 35 | this.repoName = repoName; 36 | this.groupID = groupID; 37 | this.diffHunkIndices = new ArrayList<>(); 38 | this.diffHunkIDs = diffHunkIDs; 39 | this.intentLabel = label; 40 | } 41 | 42 | public Group( 43 | String repoID, 44 | String repoName, 45 | String groupID, 46 | List diffHunkIndices, 47 | List diffHunkIDs, 48 | GroupLabel label) { 49 | this.repoID = repoID; 50 | this.repoName = repoName; 51 | this.groupID = groupID; 52 | this.diffHunkIndices = diffHunkIndices; 53 | this.diffHunkIDs = diffHunkIDs; 54 | this.intentLabel = label; 55 | } 56 | 57 | public String getGroupID() { 58 | return groupID; 59 | } 60 | 61 | public List getDiffHunkIDs() { 62 | return diffHunkIDs; 63 | } 64 | 65 | public List getDiffHunkIndices() { 66 | return diffHunkIndices; 67 | } 68 | 69 | public GroupLabel getIntentLabel() { 70 | return intentLabel; 71 | } 72 | 73 | public List getRecommendedCommitMsgs() { 74 | return recommendedCommitMsgs; 75 | } 76 | 77 | public void setRecommendedCommitMsgs(List recommendedCommitMsgs) { 78 | this.recommendedCommitMsgs = recommendedCommitMsgs; 79 | } 80 | 81 | public void setIntentLabel(GroupLabel intentLabel) { 82 | this.intentLabel = intentLabel; 83 | } 84 | 85 | public String getCommitID() { 86 | return commitID; 87 | } 88 | 89 | public void setCommitID(String commitID) { 90 | this.commitID = commitID; 91 | } 92 | 93 | public String getCommitMsg() { 94 | return commitMsg; 95 | } 96 | 97 | public void setCommitMsg(String commitMsg) { 98 | this.commitMsg = commitMsg; 99 | } 100 | 101 | public void addByID(String diffID) { 102 | if (diffHunkIDs.contains(diffID)) { 103 | return; 104 | } else { 105 | diffHunkIDs.add(diffID); 106 | } 107 | } 108 | 109 | public void addByIndex(String diffIndex) { 110 | if (diffHunkIndices.contains(diffIndex)) { 111 | return; 112 | } else { 113 | diffHunkIndices.add(diffIndex); 114 | } 115 | } 116 | 117 | public Set getLinkCategories() { 118 | return linkCategories; 119 | } 120 | 121 | public void addLinkCategories(Set categories) { 122 | this.linkCategories.addAll(categories); 123 | } 124 | 125 | public void setDiffHunkIDs(List diffHunkIDs) { 126 | this.diffHunkIDs = diffHunkIDs; 127 | } 128 | 129 | @Override 130 | public String toString() { 131 | StringBuilder builder = new StringBuilder(); 132 | // builder.append(intentLabel).append("\n"); 133 | // builder.append(commitMsg).append("\n"); 134 | builder.append("Changes: {"); 135 | builder.append(String.join(", ", diffHunkIndices)); 136 | builder.append("}\n"); 137 | return builder.toString(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/Hunk.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model; 2 | 3 | import com.github.smartcommit.model.constant.ContentType; 4 | import com.github.smartcommit.model.constant.Version; 5 | import com.google.common.collect.Iterables; 6 | import org.eclipse.jdt.core.dom.ASTNode; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class Hunk { 12 | private String relativeFilePath; 13 | private Integer startLine; 14 | private Integer endLine; 15 | private Version version; 16 | private ContentType contentType; 17 | private List codeSnippet; 18 | private transient List coveredNodes; 19 | 20 | public Hunk( 21 | Version version, 22 | String relativeFilePath, 23 | Integer startLine, 24 | Integer endLine, 25 | ContentType contentType, 26 | List codeSnippet) { 27 | this.version = version; 28 | this.relativeFilePath = relativeFilePath; 29 | this.startLine = startLine; 30 | this.endLine = endLine; 31 | this.contentType = contentType; 32 | this.codeSnippet = codeSnippet; 33 | this.coveredNodes = new ArrayList<>(); 34 | } 35 | 36 | public Version getVersion() { 37 | return version; 38 | } 39 | 40 | public String getRelativeFilePath() { 41 | return relativeFilePath; 42 | } 43 | 44 | public Integer getStartLine() { 45 | return startLine; 46 | } 47 | 48 | public Integer getEndLine() { 49 | return endLine; 50 | } 51 | 52 | public List getCodeSnippet() { 53 | return codeSnippet; 54 | } 55 | 56 | public ContentType getContentType() { 57 | return contentType; 58 | } 59 | 60 | public List getCoveredNodes() { 61 | return coveredNodes; 62 | } 63 | 64 | public void setCoveredNodes(List coveredNodes) { 65 | this.coveredNodes = coveredNodes; 66 | } 67 | 68 | /** 69 | * Get the length of the last line in the code snippets 70 | * 71 | * @return 72 | */ 73 | public int getLastLineLength() { 74 | String lastLineRaw = Iterables.getLast(codeSnippet, null); 75 | if (lastLineRaw == null) { 76 | return 0; 77 | } else { 78 | return lastLineRaw.length(); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/constant/ChangeType.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.constant; 2 | 3 | /** 4 | * Change type of a DiffHunk 5 | */ 6 | public enum ChangeType { 7 | MODIFIED("M", "Modify"), 8 | ADDED("A", "Add"), 9 | DELETED("D", "Delete"); 10 | 11 | public String symbol; 12 | public String label; 13 | 14 | ChangeType(String symbol, String label) { 15 | this.symbol = symbol; 16 | this.label = label; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/constant/ContentType.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.constant; 2 | 3 | /** Type of the content in hunk */ 4 | public enum ContentType { 5 | IMPORT("ImportStatement"), // pure imports 6 | COMMENT("Comment"), // pure comment 7 | CODE("Code"), // actual code (or mixed) 8 | BLANKLINE("BlankLine"), // blank lines 9 | EMPTY("Empty"), // added/deleted 10 | BINARY("Binary"); // binary content 11 | 12 | public String label; 13 | 14 | ContentType(String label) { 15 | this.label = label; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/constant/FileStatus.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.constant; 2 | 3 | /** 4 | * Status of a DiffFile 5 | */ 6 | public enum FileStatus { 7 | // XY: two-letter status code, where X shows the index status, Y shows the working tree status 8 | UNMODIFIED(" ", "unmodified"), // usually won't appear 9 | MODIFIED("M", "modified"), 10 | ADDED("A", "added"), 11 | DELETED("D", "deleted"), 12 | RENAMED("R", "renamed"), // RXXX (like R096: Renamed with 96% similarity) 13 | COPIED("C", "copied"), // CXXX (like C075: Copied with 75% similarity) 14 | UNMERGED("U", "unmerged"), 15 | UNTRACKED("??", "untracked"), 16 | IGNORED("!!", "ignored"); // Ignored files are not listed, unless --ignored option is in effect, in which case XY are !!. 17 | 18 | public String symbol; 19 | public String label; 20 | 21 | FileStatus(String symbol, String label) { 22 | this.symbol = symbol; 23 | this.label = label; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/constant/FileType.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.constant; 2 | 3 | /** The type of the diff File */ 4 | public enum FileType { 5 | JAVA(".java", "Java"), 6 | KT(".kt", "Kotlin"), 7 | KTS(".kts", "Kotlin-Script"), 8 | JSON(".json", "Json"), 9 | JS(".javascript", "JavaScript"), 10 | PY(".py", "Python"), 11 | CPP(".cpp", "C++"), 12 | HPP(".hpp", "C++ Header"), 13 | C(".c", "C"), 14 | H(".h", "C Header"), 15 | MD(".md", "Markdown"), 16 | TXT(".txt", "Text"), 17 | HTML(".html", "HTML"), 18 | XML(".xml", "XML"), 19 | YML(".yml", "YAML"), 20 | GRADLE(".gradle", "Gradle"), 21 | GROOVY(".groovy", "Groovy"), 22 | PROP(".properties", "Properties"), 23 | 24 | BIN(".", "Binary"), // binary file 25 | OTHER(".*", "Other"); // other plain text file 26 | 27 | public String extension; 28 | public String label; 29 | 30 | FileType(String extension, String label) { 31 | this.extension = extension; 32 | this.label = label; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/constant/GroupLabel.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.constant; 2 | 3 | public enum GroupLabel { 4 | FEATURE("Add or modify feature"), // new feature 5 | REFACTOR("Refactor code structure"), // refactoring 6 | FIX("Fix bug"), // fix bugs 7 | OPT("Optimize code"), // optimization for existing functions 8 | 9 | REFORMAT("Reformat code"), // blank/special character changes 10 | DOC("Update document"), 11 | CONFIG("Change config file"), 12 | RESOURCE("Change resource file"), 13 | SIMILAR("Apply some similar changes"), // systematic changes 14 | CLEAR("Clear unused code"), // clear dead code or comment 15 | 16 | TEST("Modify test cases or tested methods"), 17 | NONJAVA("Modify non-java file"), 18 | OTHER("Other changes"); // trivial changes 19 | 20 | public String label; 21 | 22 | GroupLabel(String label) { 23 | this.label = label; 24 | } 25 | 26 | public String getLabel() { 27 | return label; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/constant/Operation.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.constant; 2 | 3 | /** Action operation on AST or Refactoring */ 4 | public enum Operation { 5 | // AST operations 6 | ADD("Add", 1), 7 | DEL("Delete", 2), 8 | UPD("Update", 3), 9 | MOV("Move", 4), 10 | 11 | // Refactoring operations 12 | // ADD("Add", 1), 13 | CHANGE("Change", 5), 14 | CONVERT("Convert", 6), 15 | EXTRACT("Extract", 7), 16 | EXTRACT_AND_MOVE("Extract And Move", 8), 17 | INLINE("Inline", 9), 18 | INTRODUCE("Introduce", 10), 19 | MERGE("Merge", 11), 20 | MODIFY("Modify", 12), 21 | MOVE("Move", 13), 22 | MOVE_AND_INLINE("Move And Inline", 14), 23 | MOVE_AND_RENAME("Move And Rename", 15), 24 | PARAMETERIZE("Parameterize", 16), 25 | PULL_UP("Pull Up", 17), 26 | PULL_DOWN("Pull Down", 18), 27 | REPLACE("Replace", 19), 28 | REORDER("Reorder", 20), 29 | RENAME("Rename", 21), 30 | REMOVE("Remove", 22), 31 | SPILT("Split", 23), 32 | 33 | 34 | UKN("Unknown", 24); 35 | 36 | public String label; 37 | public int index; 38 | 39 | Operation(String label, int index) { 40 | this.label = label; 41 | this.index = index; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return label; 47 | } 48 | 49 | public int getIndex() { 50 | return index; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/constant/Version.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.constant; 2 | 3 | public enum Version { 4 | BASE(0, "base"), 5 | CURRENT(1, "current"); 6 | 7 | private int index; 8 | private String label; 9 | 10 | Version(int index, String label) { 11 | this.index = index; 12 | this.label = label; 13 | } 14 | 15 | public String asString() { 16 | return label; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/diffgraph/DiffEdge.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.diffgraph; 2 | 3 | public class DiffEdge { 4 | private Integer id; 5 | private DiffEdgeType type; 6 | private Double weight; 7 | 8 | public DiffEdge(Integer id, DiffEdgeType type, Double weight) { 9 | this.id = id; 10 | this.type = type; 11 | this.weight = weight; 12 | } 13 | 14 | public Integer getId() { 15 | return id; 16 | } 17 | 18 | public DiffEdgeType getType() { 19 | return type; 20 | } 21 | 22 | public Double getWeight() { 23 | return weight; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/diffgraph/DiffEdgeType.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.diffgraph; 2 | 3 | public enum DiffEdgeType { 4 | /** hard * */ 5 | DEPEND(true, "dependency", 0), 6 | /** hard * */ 7 | 8 | /** soft * */ 9 | SIMILAR(false, "similar", 1), 10 | CLOSE(false, "close", 1), 11 | /** soft * */ 12 | 13 | /** pattern * */ 14 | REFACTOR(true, "refactor", 2), 15 | MOVING(true, "moving", 2), 16 | /** pattern * */ 17 | 18 | /** logical * */ 19 | REFORMAT(true, "reformat", 3), 20 | TEST(false, "test", 3), 21 | /** logical * */ 22 | 23 | DOC(false, "doc", 4), 24 | CONFIG(false, "config", 4), 25 | RESOURCE(false, "resource", 4), 26 | NONJAVA(false, "non-java", 4), 27 | OTHERS(false, "others", 4); 28 | 29 | Boolean fixed; 30 | String label; 31 | Integer category; // category of the link, mainly for ablation study 32 | 33 | DiffEdgeType(Boolean fixed, String label) { 34 | this.fixed = fixed; 35 | this.label = label; 36 | } 37 | 38 | DiffEdgeType(Boolean fixed, String label, Integer category) { 39 | this.fixed = fixed; 40 | this.label = label; 41 | this.category = category; 42 | } 43 | 44 | public String asString() { 45 | return this.label; 46 | } 47 | 48 | public Boolean isConstraint() { 49 | return this.fixed; 50 | } 51 | 52 | public Integer getCategory() { 53 | return this.category; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/diffgraph/DiffNode.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.diffgraph; 2 | 3 | import com.github.smartcommit.util.Utils; 4 | import org.apache.commons.lang3.tuple.Pair; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** Nodes in the DiffViewGraph, which stand for one diff hunk */ 10 | public class DiffNode { 11 | private Integer id; 12 | private String index; // unique identifier (fileIndex:diffHunkIndex) 13 | private Integer fileIndex; // index of the changed file 14 | private Integer diffHunkIndex; // index of the diff hunk 15 | private String uuid; // fileID:diffHunkID 16 | 17 | // parent info (node id in the graph) to estimate the distance 18 | // in the order of: hunkNodeID, memberNodeID, classNodeID, packageNodeID 19 | private Map baseHierarchy; 20 | private Map currentHierarchy; 21 | 22 | public DiffNode(Integer id, String index, String uuid) { 23 | this.id = id; 24 | this.index = index; 25 | this.uuid = uuid; 26 | Pair indices = Utils.parseIndices(index); 27 | this.fileIndex = indices.getLeft(); 28 | this.diffHunkIndex = indices.getRight(); 29 | this.baseHierarchy = new HashMap<>(); 30 | this.currentHierarchy = new HashMap<>(); 31 | } 32 | 33 | public Integer getId() { 34 | return id; 35 | } 36 | 37 | public String getIndex() { 38 | return index; 39 | } 40 | 41 | public Map getBaseHierarchy() { 42 | return baseHierarchy; 43 | } 44 | 45 | public void setBaseHierarchy(Map baseHierarchy) { 46 | this.baseHierarchy = baseHierarchy; 47 | } 48 | 49 | public Map getCurrentHierarchy() { 50 | return currentHierarchy; 51 | } 52 | 53 | public void setCurrentHierarchy(Map currentHierarchy) { 54 | this.currentHierarchy = currentHierarchy; 55 | } 56 | 57 | public Integer getFileIndex() { 58 | return fileIndex; 59 | } 60 | 61 | public Integer getDiffHunkIndex() { 62 | return diffHunkIndex; 63 | } 64 | 65 | public String getUUID() { 66 | return uuid; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/AnnotationInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | public class AnnotationInfo extends DeclarationInfo{ 4 | public String name; 5 | public String fullName; 6 | public String comment = ""; 7 | public String content = ""; 8 | 9 | public String uniqueName() { 10 | return fullName; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/AnnotationMemberInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | import com.github.smartcommit.model.graph.Node; 4 | 5 | public class AnnotationMemberInfo { 6 | public String name; 7 | public String belongTo; 8 | public String type; 9 | public String defaultValue; 10 | 11 | public Node node; 12 | 13 | public String uniqueName() { 14 | return belongTo + ":" + name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/ClassInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class ClassInfo extends DeclarationInfo { 7 | 8 | public String name; 9 | // public String belongTo; 10 | public String fullName; 11 | public String visibility = "package"; 12 | public boolean isAbstract = false; 13 | public boolean isFinal = false; 14 | public boolean isAnonymous = false; 15 | public String superClassType; 16 | public List superInterfaceTypeList = new ArrayList<>(); 17 | public String comment = ""; 18 | public String content = ""; 19 | 20 | public String uniqueName() { 21 | // return belongTo + "." + name; 22 | return fullName; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/DeclarationInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | import com.github.smartcommit.model.graph.Node; 4 | import org.eclipse.jdt.core.dom.IMethodBinding; 5 | 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | /** 10 | * Information collected in declarations (e.g. type, field, method/constructor, annotation member, 11 | * enum constant) 12 | */ 13 | public class DeclarationInfo { 14 | // which file the entity belongs to 15 | public Integer fileIndex; 16 | // corresponding node in the graph 17 | public Node node; 18 | 19 | // def internal 20 | public Set typeDefs = new HashSet<>(); // AbstractType, including Type, Enum, Annotation 21 | public Set fieldDefs = new HashSet<>(); 22 | public Set methodDefs = new HashSet<>(); 23 | 24 | // use internal 25 | public Set typeUses = new HashSet<>(); // AbstractType, including Type, Enum, Annotation 26 | public Set methodCalls = new HashSet<>(); 27 | public Set fieldUses = new HashSet<>(); 28 | public Set paraUses = new HashSet<>(); 29 | public Set localVarUses = new HashSet<>(); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/EnumConstantInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | import com.github.smartcommit.model.graph.Node; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class EnumConstantInfo { 9 | public String name; 10 | public String belongTo; 11 | public List arguments = new ArrayList<>(); 12 | public String comment = ""; 13 | 14 | // corresponding node in the graph 15 | public Node node; 16 | 17 | public String uniqueName() { 18 | return belongTo + ":" + name; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/EnumInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | public class EnumInfo extends DeclarationInfo{ 4 | public String name; 5 | public String fullName; 6 | public String visibility = "package"; 7 | public String comment = ""; 8 | public String content = ""; 9 | 10 | public String uniqueName() { 11 | return fullName; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/FieldInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | import java.util.Set; 4 | 5 | public class FieldInfo extends DeclarationInfo { 6 | 7 | public String name; 8 | public String belongTo; 9 | public String typeString; 10 | public Set types; 11 | public String visibility; 12 | public boolean isStatic; 13 | public boolean isFinal; 14 | public String comment = ""; 15 | 16 | public String uniqueName() { 17 | return belongTo + ":" + name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/HunkInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | import org.eclipse.jdt.core.dom.ASTNode; 4 | 5 | import java.util.LinkedHashSet; 6 | import java.util.Set; 7 | 8 | public class HunkInfo extends DeclarationInfo { 9 | public String identifier = "-1:-1"; 10 | public Integer fileIndex = -1; 11 | public Integer hunkIndex = -1; 12 | public Set coveredNodes = new LinkedHashSet<>(); 13 | 14 | public HunkInfo(Integer fileIndex, Integer hunkIndex) { 15 | this.fileIndex = fileIndex; 16 | this.hunkIndex = hunkIndex; 17 | this.identifier = fileIndex + ":" + hunkIndex; 18 | } 19 | 20 | public HunkInfo(String identifier) { 21 | this.identifier = identifier; 22 | String[] indices = identifier.split(":"); 23 | if (indices.length == 2) { 24 | this.fileIndex = Integer.valueOf(indices[0]); 25 | this.hunkIndex = Integer.valueOf(indices[1]); 26 | } 27 | } 28 | 29 | public String uniqueName() { 30 | return fileIndex + ":" + hunkIndex; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/InitializerInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | /** More like a method with name "static" */ 4 | public class InitializerInfo extends DeclarationInfo { 5 | public boolean isStatic = false; 6 | public String comment = ""; 7 | public String body = ""; 8 | public String belongTo = ""; 9 | 10 | public String uniqueName() { 11 | return belongTo + ":INIT"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/InterfaceInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class InterfaceInfo extends DeclarationInfo{ 7 | public String name; 8 | public String fullName; 9 | public String visibility; 10 | public List superInterfaceTypeList = new ArrayList<>(); 11 | public String comment = ""; 12 | public String content = ""; 13 | 14 | public String uniqueName() { 15 | return fullName; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/entity/MethodInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.entity; 2 | 3 | import org.eclipse.jdt.core.dom.IMethodBinding; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | public class MethodInfo extends DeclarationInfo { 9 | public String visibility; 10 | public boolean isConstructor; 11 | public boolean isAbstract; 12 | public boolean isFinal; 13 | public boolean isStatic; 14 | public boolean isSynchronized; 15 | 16 | public String name; 17 | public String belongTo; 18 | public String returnString; 19 | public Set returnTypes = new HashSet<>(); 20 | 21 | public String content = ""; 22 | public String comment = ""; 23 | public String paramString; 24 | public Set paramTypes = new HashSet<>(); 25 | public Set exceptionThrows = new HashSet<>(); 26 | 27 | public IMethodBinding methodBinding; 28 | 29 | public String uniqueName() { 30 | return belongTo + ":" + name + "(" + paramString + ")"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/graph/Edge.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.graph; 2 | 3 | public class Edge { 4 | private Integer id; 5 | private EdgeType type; 6 | private Integer weight; 7 | 8 | public Edge(Integer id, EdgeType type) { 9 | this.id = id; 10 | this.type = type; 11 | this.weight = 1; 12 | } 13 | 14 | public Integer getId() { 15 | return id; 16 | } 17 | 18 | public Integer getWeight() { 19 | return weight; 20 | } 21 | 22 | /** Increase the weight by one */ 23 | public Integer increaseWeight() { 24 | this.weight += 1; 25 | return this.weight; 26 | } 27 | 28 | public void setWeight(Integer weight) { 29 | this.weight = weight; 30 | } 31 | 32 | public EdgeType getType() { 33 | return type; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/graph/EdgeType.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.graph; 2 | 3 | public enum EdgeType { 4 | /** file&folder level edges * */ 5 | CONTAIN(true, "contain"), // physical relation 6 | IMPORT(false, "import"), 7 | EXTEND(false, "extend"), 8 | IMPLEMENT(false, "implement"), 9 | /** inside-file edges * */ 10 | // define field/terminal/constructor/inner type/constant 11 | DEFINE(true, "define"), 12 | /** across-node edges * */ 13 | // inter-field/terminal edges 14 | ACCESS(false, "access_field"), 15 | // READ("reads field"), 16 | // WRITE("writes field"), 17 | // call method 18 | CALL(false, "call_method"), 19 | // declare/initialize object 20 | // DECLARE(false, "declare_object"), 21 | PARAM(false, "parameter_type"), 22 | TYPE(false, "field_type"), 23 | INITIALIZE(false, "initialize"), 24 | RETURN(false, "return_type"); 25 | 26 | Boolean isStructural; 27 | String label; 28 | 29 | EdgeType(Boolean isStructural, String label) { 30 | this.isStructural = isStructural; 31 | this.label = label; 32 | } 33 | 34 | public String asString() { 35 | return this.label; 36 | } 37 | 38 | public Boolean isStructural() { 39 | return this.isStructural; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/graph/Node.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.graph; 2 | 3 | public class Node { 4 | private Integer id; 5 | private NodeType type; 6 | private String identifier; 7 | private String qualifiedName; 8 | 9 | public Boolean isInDiffHunk; 10 | // following fields are only valid is isInDiffHunk is true 11 | // diffHunkIndex = fileIndex:hunkIndex 12 | public String diffHunkIndex; 13 | 14 | public Node(Integer id, NodeType type, String identifier, String qualifiedName) { 15 | this.id = id; 16 | this.type = type; 17 | this.identifier = identifier; 18 | this.qualifiedName = qualifiedName; 19 | this.isInDiffHunk = false; 20 | this.diffHunkIndex = ""; 21 | } 22 | 23 | public Integer getId() { 24 | return id; 25 | } 26 | 27 | public NodeType getType() { 28 | return type; 29 | } 30 | 31 | public String getIdentifier() { 32 | return identifier; 33 | } 34 | 35 | public String getQualifiedName() { 36 | return qualifiedName; 37 | } 38 | 39 | public String getDiffHunkIndex() { 40 | return diffHunkIndex; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/model/graph/NodeType.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.model.graph; 2 | 3 | /** Node declarations */ 4 | public enum NodeType { 5 | PROJECT("project"), // final root of all nodes 6 | PACKAGE("package"), 7 | COMPILATION_UNIT("compilation_unit"), // logical node to represent file 8 | 9 | // nonterminal (children classes of AbstractTypeDeclaration) 10 | CLASS("class"), 11 | ANONY_CLASS("anonymous class"), 12 | // INNER_CLASS("class"), 13 | INTERFACE("interface"), 14 | 15 | ENUM("enum"), 16 | ANNOTATION("@interface"), // annotation type declaration 17 | 18 | // terminal 19 | // CONSTRUCTOR("constructor"), 20 | FIELD("field"), 21 | METHOD("method"), 22 | ENUM_CONSTANT("enum_constant"), 23 | INITIALIZER_BLOCK("initializer_block"), 24 | ANNOTATION_MEMBER("annotation_member"), 25 | 26 | HUNK("hunk"); 27 | 28 | String label; 29 | 30 | NodeType(String label) { 31 | this.label = label; 32 | } 33 | 34 | public String asString() { 35 | return this.label; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/util/Distance.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.util; 2 | 3 | public class Distance { 4 | public static void main(String[] args) { 5 | String s1 = "13|45"; 6 | String s3 = "134|5"; 7 | String s2 = "135|4"; 8 | System.out.println(DLDistance(s1, s2)); 9 | System.out.println(getSimilarity(s1, s2)); 10 | System.out.println(LDistance("first second third", "second")); 11 | } 12 | 13 | private static int DLDistance(String s1, String s2) { 14 | int m = (s1 == null) ? 0 : s1.length(); 15 | int n = (s2 == null) ? 0 : s2.length(); 16 | if (m == 0) { 17 | return n; 18 | } 19 | if (n == 0) { 20 | return m; 21 | } 22 | int[] p = new int[n + 1]; 23 | int[] p1 = new int[n + 1]; 24 | int[] t = new int[n + 1]; 25 | for (int i = 0; i < p.length; i++) { 26 | p[i] = i; 27 | } 28 | int d = 0; 29 | int cost = 0; 30 | char s1_c, s2_c; 31 | for (int i = 0; i < m; i++) { 32 | t[0] = i + 1; 33 | s1_c = s1.charAt(i); 34 | for (int j = 1; j < p.length; j++) { 35 | s2_c = s2.charAt(j - 1); 36 | cost = (s1_c == s2_c) ? 0 : 1; 37 | d = Math.min(Math.min(t[j - 1], p[j]) + 1, p[j - 1] + cost); 38 | if (i > 0 && j > 1 && s1_c == s2.charAt(j - 2) && s1.charAt(i - 1) == s2_c) { 39 | d = Math.min(d, p1[j - 2] + cost); 40 | } 41 | t[j] = d; 42 | } 43 | p1 = p; 44 | p = t; 45 | t = new int[n + 1]; 46 | } 47 | return d; 48 | } 49 | 50 | public static float getSimilarity(String s1, String s2) { 51 | if (s1 == null || s2 == null) { 52 | if (s1 == s2) { 53 | return 1.0f; 54 | } 55 | return 0.0f; 56 | } 57 | float d = DLDistance(s1, s2); 58 | return 1 - (d / Math.max(s1.length(), s2.length())); 59 | } 60 | 61 | public static int LDistance(String sentence1, String sentence2) { 62 | String[] s1 = sentence1.split(" "); 63 | String[] s2 = sentence2.split(" "); 64 | int[][] solution = new int[s1.length + 1][s2.length + 1]; 65 | 66 | for (int i = 0; i <= s2.length; i++) { 67 | solution[0][i] = i; 68 | } 69 | 70 | for (int i = 0; i <= s1.length; i++) { 71 | solution[i][0] = i; 72 | } 73 | 74 | int m = s1.length; 75 | int n = s2.length; 76 | for (int i = 1; i <= m; i++) { 77 | for (int j = 1; j <= n; j++) { 78 | if (s1[i - 1].equals(s2[j - 1])) solution[i][j] = solution[i - 1][j - 1]; 79 | else 80 | solution[i][j] = 81 | 1 82 | + Math.min( 83 | solution[i][j - 1], Math.min(solution[i - 1][j], solution[i - 1][j - 1])); 84 | } 85 | } 86 | return solution[s1.length][s2.length]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/util/GitService.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.util; 2 | 3 | import com.github.smartcommit.model.DiffFile; 4 | import com.github.smartcommit.model.DiffHunk; 5 | 6 | import java.nio.charset.Charset; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** A list of helper functions related with Git */ 11 | public interface GitService { 12 | /** 13 | * Get the diff files in the current working tree 14 | * 15 | * @return 16 | */ 17 | ArrayList getChangedFilesInWorkingTree(String repoPath); 18 | 19 | /** 20 | * Get the diff files between one commit and its previous commit 21 | * 22 | * @return 23 | */ 24 | ArrayList getChangedFilesAtCommit(String repoPath, String commitID); 25 | 26 | /** 27 | * Get the diff hunks in the current working tree 28 | * 29 | * @param repoPath 30 | * @return 31 | */ 32 | List getDiffHunksInWorkingTree(String repoPath, List diffFiles); 33 | 34 | /** 35 | * Get the diff hunks between one commit and its previous commit 36 | * 37 | * @param repoPath 38 | * @param commitID 39 | * @return 40 | */ 41 | List getDiffHunksAtCommit(String repoPath, String commitID, List diffFiles); 42 | 43 | /** 44 | * Get the file content at HEAD 45 | * 46 | * @param relativePath 47 | * @return 48 | */ 49 | String getContentAtHEAD(Charset charset, String repoDir, String relativePath); 50 | 51 | /** 52 | * Get the file content at one specific commit 53 | * 54 | * @param relativePath 55 | * @returnØØ 56 | */ 57 | String getContentAtCommit(Charset charset, String repoDir, String relativePath, String commitID); 58 | 59 | /** 60 | * Get the name of the author of a commit 61 | * @param repoDir 62 | * @param commitID 63 | * @return 64 | */ 65 | String getCommitterName(String repoDir, String commitID); 66 | 67 | /** 68 | * Get the email of the author of a commit 69 | * 70 | * @param repoDir 71 | * @param commitID 72 | * @return 73 | */ 74 | String getCommitterEmail(String repoDir, String commitID); 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/util/GitServiceJGit.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.util; 2 | 3 | import com.github.smartcommit.model.DiffFile; 4 | import com.github.smartcommit.model.DiffHunk; 5 | 6 | import java.nio.charset.Charset; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** Implementation of helper functions based on jGit (the java implementation of Git). */ 11 | public class GitServiceJGit implements GitService { 12 | @Override 13 | public ArrayList getChangedFilesInWorkingTree(String repoPath) { 14 | return null; 15 | } 16 | 17 | @Override 18 | public ArrayList getChangedFilesAtCommit(String repoPath, String commitID) { 19 | return null; 20 | } 21 | 22 | @Override 23 | public List getDiffHunksInWorkingTree(String repoPath, List diffFiles) { 24 | return null; 25 | } 26 | 27 | @Override 28 | public List getDiffHunksAtCommit( 29 | String repoPath, String commitID, List diffFiles) { 30 | return null; 31 | } 32 | 33 | @Override 34 | public String getContentAtHEAD(Charset charset, String repoDir, String relativePath) { 35 | return null; 36 | } 37 | 38 | @Override 39 | public String getContentAtCommit( 40 | Charset charset, String repoDir, String relativePath, String commitID) { 41 | return null; 42 | } 43 | 44 | @Override 45 | public String getCommitterName(String repoDir, String commitID) { 46 | return null; 47 | } 48 | 49 | @Override 50 | public String getCommitterEmail(String repoDir, String commitID) { 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/util/JDTParser.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.util; 2 | 3 | import com.github.smartcommit.model.DiffFile; 4 | import org.apache.commons.lang3.tuple.Pair; 5 | import org.apache.log4j.Logger; 6 | import org.eclipse.jdt.core.JavaCore; 7 | import org.eclipse.jdt.core.dom.ASTParser; 8 | import org.eclipse.jdt.core.dom.CompilationUnit; 9 | 10 | import java.util.Map; 11 | 12 | public class JDTParser { 13 | private static final Logger logger = Logger.getLogger(JDTParser.class); 14 | private String repoPath; 15 | private String jrePath; 16 | 17 | public JDTParser(String repoPath, String jrePath) { 18 | this.repoPath = repoPath; 19 | this.jrePath = jrePath; 20 | } 21 | 22 | /** 23 | * Currently for Java 8 24 | * 25 | * @param diffFile 26 | * @return 27 | */ 28 | public Pair generateCUPair(DiffFile diffFile) { 29 | 30 | ASTParser parser = initASTParser(); 31 | parser.setUnitName(Utils.getFileNameFromPath(diffFile.getBaseRelativePath())); 32 | parser.setSource(diffFile.getBaseContent().toCharArray()); 33 | CompilationUnit oldCU = (CompilationUnit) parser.createAST(null); 34 | if (!oldCU.getAST().hasBindingsRecovery()) { 35 | logger.error("Binding not enabled: " + diffFile.getBaseRelativePath()); 36 | } 37 | 38 | parser = initASTParser(); 39 | parser.setUnitName(Utils.getFileNameFromPath(diffFile.getCurrentRelativePath())); 40 | parser.setSource(diffFile.getCurrentContent().toCharArray()); 41 | CompilationUnit newCU = (CompilationUnit) parser.createAST(null); 42 | if (!newCU.getAST().hasBindingsRecovery()) { 43 | logger.error("Binding not enabled: " + diffFile.getCurrentRelativePath()); 44 | } 45 | return Pair.of(oldCU, newCU); 46 | } 47 | 48 | /** 49 | * Init the JDT ASTParser 50 | * 51 | * @return 52 | */ 53 | public ASTParser initASTParser() { 54 | // set up the parser and resolver options 55 | ASTParser parser = ASTParser.newParser(8); 56 | parser.setResolveBindings(true); 57 | parser.setKind(ASTParser.K_COMPILATION_UNIT); 58 | parser.setBindingsRecovery(true); 59 | Map options = JavaCore.getOptions(); 60 | parser.setCompilerOptions(options); 61 | 62 | // set up the arguments 63 | String[] sources = {this.repoPath}; // sources to resolve symbols 64 | String[] classpath = {this.jrePath}; // local java runtime (rt.jar) path 65 | parser.setEnvironment(classpath, sources, new String[] {"UTF-8"}, true); 66 | return parser; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/util/NameResolver.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit.util; 2 | 3 | import org.eclipse.jdt.core.dom.*; 4 | 5 | import java.util.Set; 6 | 7 | /** 8 | * Evaluates fully qualified name of TypeDeclaration, Type and Name objects. 9 | * 10 | * 11 | */ 12 | public class NameResolver { 13 | 14 | private static Set srcPathSet = null; 15 | 16 | /** 17 | * Evaluates fully qualified name of the TypeDeclaration object. 18 | * 19 | * @param decl 20 | * @return 21 | */ 22 | public static String getFullName(TypeDeclaration decl) { 23 | String name = decl.getName().getIdentifier(); 24 | ASTNode parent = decl.getParent(); 25 | // resolve full name e.g.: A.B 26 | while (parent != null && parent.getClass() == TypeDeclaration.class) { 27 | name = ((TypeDeclaration) parent).getName().getIdentifier() + "." + name; 28 | parent = parent.getParent(); 29 | } 30 | // resolve fully qualified name e.g.: some.package.A.B 31 | if (decl.getRoot().getClass() == CompilationUnit.class) { 32 | CompilationUnit root = (CompilationUnit) decl.getRoot(); 33 | if (root.getPackage() != null) { 34 | PackageDeclaration pack = root.getPackage(); 35 | name = pack.getName().getFullyQualifiedName() + "." + name; 36 | } 37 | } 38 | return name; 39 | } 40 | 41 | /** 42 | * Evaluates fully qualified name of the TypeDeclaration object. 43 | * 44 | * @param decl 45 | * @return 46 | */ 47 | public static String getFullName(EnumDeclaration decl) { 48 | String name = decl.getName().getIdentifier(); 49 | ASTNode parent = decl.getParent(); 50 | // resolve full name e.g.: A.B 51 | while (parent != null && parent.getClass() == TypeDeclaration.class) { 52 | name = ((TypeDeclaration) parent).getName().getIdentifier() + "." + name; 53 | parent = parent.getParent(); 54 | } 55 | // resolve fully qualified name e.g.: some.package.A.B 56 | if (decl.getRoot().getClass() == CompilationUnit.class) { 57 | CompilationUnit root = (CompilationUnit) decl.getRoot(); 58 | if (root.getPackage() != null) { 59 | PackageDeclaration pack = root.getPackage(); 60 | name = pack.getName().getFullyQualifiedName() + "." + name; 61 | } 62 | } 63 | return name; 64 | } 65 | 66 | public static String getFullName(AnnotationTypeDeclaration decl) { 67 | String name = decl.getName().getIdentifier(); 68 | ASTNode parent = decl.getParent(); 69 | // resolve fully qualified name e.g.: some.package.A.B 70 | if (decl.getRoot().getClass() == CompilationUnit.class) { 71 | CompilationUnit root = (CompilationUnit) decl.getRoot(); 72 | if (root.getPackage() != null) { 73 | PackageDeclaration pack = root.getPackage(); 74 | name = pack.getName().getFullyQualifiedName() + "." + name; 75 | } 76 | } 77 | return name; 78 | } 79 | 80 | /** Evaluates fully qualified name of the Type object. */ 81 | public static String getFullName(Type t) { 82 | if (t == null) return null; 83 | if (t.isParameterizedType()) { 84 | ParameterizedType t0 = (ParameterizedType) t; 85 | return getFullName(t0.getType()); 86 | } else if (t.isQualifiedType()) { 87 | QualifiedType t0 = (QualifiedType) t; 88 | return getFullName(t0.getQualifier()) + "." + t0.getName().getIdentifier(); 89 | } else if (t.isSimpleType()) { 90 | SimpleType t0 = (SimpleType) t; 91 | return getFullName(t0.getName()); 92 | } else { 93 | return "?"; 94 | } 95 | } 96 | 97 | /** Evaluates fully qualified name of the Name object. */ 98 | private static String getFullName(Name name) { 99 | // check if the root node is a CompilationUnit 100 | if (name.getRoot().getClass() != CompilationUnit.class) { 101 | // cannot resolve a full name, CompilationUnit root node is missing 102 | return name.getFullyQualifiedName(); 103 | } 104 | // get the root node 105 | CompilationUnit root = (CompilationUnit) name.getRoot(); 106 | // check if the name is declared in the same file 107 | TypeDeclVisitor tdVisitor = new TypeDeclVisitor(name.getFullyQualifiedName()); 108 | root.accept(tdVisitor); 109 | if (tdVisitor.getFound()) { 110 | // the name is the use of the TypeDeclaration in the same file 111 | return getFullName(tdVisitor.getTypeDecl()); 112 | } 113 | // check if the name is declared in the same package or imported 114 | PckgImprtVisitor piVisitor = new PckgImprtVisitor(name.getFullyQualifiedName()); 115 | root.accept(piVisitor); 116 | if (piVisitor.getFound()) { 117 | // the name is declared in the same package or imported 118 | return piVisitor.getFullName(); 119 | } 120 | // could be a class from the java.lang (String) or a param name (T, E,...) 121 | return name.getFullyQualifiedName(); 122 | } 123 | 124 | public static Set getSrcPathSet() { 125 | return srcPathSet; 126 | } 127 | 128 | public static void setSrcPathSet(Set srcPathSet) { 129 | NameResolver.srcPathSet = srcPathSet; 130 | } 131 | 132 | private static class PckgImprtVisitor extends ASTVisitor { 133 | private boolean found = false; 134 | private String fullName; 135 | private String name; 136 | private String[] nameParts; 137 | 138 | PckgImprtVisitor(String aName) { 139 | super(); 140 | name = aName; 141 | nameParts = name.split("\\."); 142 | } 143 | 144 | private void checkInDir(String dirName) { 145 | String name = dirName + "." + nameParts[0] + ".java"; 146 | for (String fileName : srcPathSet) { 147 | fileName = fileName.replace("\\", ".").replace("/", "."); 148 | if (fileName.contains(name)) { 149 | fullName = dirName; 150 | for (String namePart : nameParts) { 151 | fullName += "." + namePart; 152 | } 153 | found = true; 154 | } 155 | } 156 | } 157 | 158 | public boolean visit(PackageDeclaration node) { 159 | String pckgName = node.getName().getFullyQualifiedName(); 160 | checkInDir(pckgName); 161 | return true; 162 | } 163 | 164 | public boolean visit(ImportDeclaration node) { 165 | if (node.isOnDemand()) { 166 | String pckgName = node.getName().getFullyQualifiedName(); 167 | checkInDir(pckgName); 168 | } else { 169 | String importName = node.getName().getFullyQualifiedName(); 170 | if (importName.endsWith("." + nameParts[0])) { 171 | fullName = importName; 172 | for (int i = 1; i < nameParts.length; i++) { 173 | fullName += "." + nameParts[i]; 174 | } 175 | found = true; 176 | } 177 | } 178 | return true; 179 | } 180 | 181 | boolean getFound() { 182 | return found; 183 | } 184 | 185 | String getFullName() { 186 | return fullName; 187 | } 188 | } 189 | 190 | private static class TypeDeclVisitor extends ASTVisitor { 191 | private boolean found = false; 192 | private TypeDeclaration typeDecl; 193 | private String name; 194 | 195 | TypeDeclVisitor(String aName) { 196 | super(); 197 | name = aName; 198 | } 199 | 200 | public boolean visit(TypeDeclaration node) { 201 | if (getFullName(node).endsWith("." + name)) { 202 | found = true; 203 | typeDecl = node; 204 | } 205 | return true; 206 | } 207 | 208 | boolean getFound() { 209 | return found; 210 | } 211 | 212 | TypeDeclaration getTypeDecl() { 213 | return typeDecl; 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/util/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"); you may not use this file 5 | * except in compliance with the License. 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 distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.github.smartcommit.util.diffparser.api; 15 | 16 | import com.github.smartcommit.util.diffparser.api.model.Diff; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.util.List; 22 | 23 | /** 24 | * Interface to a parser that parses a textual diff between two text files. See the javadoc of the 25 | * implementation you want to use to see what diff format it is expecting as input. 26 | * 27 | * @author Tom Hombergs 28 | */ 29 | @SuppressWarnings("UnusedDeclaration") 30 | public interface DiffParser { 31 | 32 | /** 33 | * Constructs a list of Diffs from a textual InputStream. 34 | * 35 | * @param in the input stream to parse 36 | * @return list of Diff objects parsed from the InputStream. 37 | */ 38 | List parse(InputStream in); 39 | 40 | /** 41 | * Constructs a list of Diffs from a textual byte array. 42 | * 43 | * @param bytes the byte array to parse 44 | * @return list of Diff objects parsed from the byte array. 45 | */ 46 | List parse(byte[] bytes); 47 | 48 | /** 49 | * Constructs a list of Diffs from a textual File 50 | * 51 | * @param file the file to parse 52 | * @return list of Diff objects parsed from the File. 53 | */ 54 | List parse(File file) throws IOException; 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/util/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"); you may not use this file 5 | * except in compliance with the License. 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 distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.github.smartcommit.util.diffparser.api; 15 | 16 | import com.github.smartcommit.util.diffparser.api.model.Diff; 17 | import com.github.smartcommit.util.diffparser.api.model.Hunk; 18 | import com.github.smartcommit.util.diffparser.api.model.Line; 19 | import com.github.smartcommit.util.diffparser.api.model.Range; 20 | import com.github.smartcommit.util.diffparser.unified.ParserState; 21 | import com.github.smartcommit.util.diffparser.unified.ResizingParseWindow; 22 | 23 | import java.io.*; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import java.util.regex.Matcher; 27 | import java.util.regex.Pattern; 28 | 29 | /** 30 | * A parser that parses a unified diff from text into a {@link Diff} data structure. 31 | * 32 | *

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

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

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

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

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

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

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

Example line:
158 | * {@code + only the plus at the start is important} 159 | */ 160 | TO_LINE { 161 | @Override 162 | public ParserState nextState(ParseWindow window) { 163 | String line = window.getFocusLine(); 164 | if (matchesFromLinePattern(line)) { 165 | logTransition(line, TO_LINE, FROM_LINE); 166 | return FROM_LINE; 167 | } else if (matchesToLinePattern(line)) { 168 | logTransition(line, TO_LINE, TO_LINE); 169 | return TO_LINE; 170 | } else if (matchesEndPattern(line, window)) { 171 | logTransition(line, TO_LINE, END); 172 | return END; 173 | } else if (matchesHunkStartPattern(line)) { 174 | logTransition(line, TO_LINE, HUNK_START); 175 | return HUNK_START; 176 | } else if (matchesNeutralPattern(line)) { 177 | logTransition(line, TO_LINE, NEUTRAL_LINE); 178 | return NEUTRAL_LINE; 179 | } else { 180 | logTransition(line, TO_LINE, HEADER); 181 | return HEADER; 182 | } 183 | } 184 | }, 185 | 186 | /** 187 | * The parser is in this state if it is currently parsing a line that is contained in both files 188 | * (a "neutral" line). This line can contain any string. 189 | */ 190 | NEUTRAL_LINE { 191 | @Override 192 | public ParserState nextState(ParseWindow window) { 193 | String line = window.getFocusLine(); 194 | if (matchesFromLinePattern(line)) { 195 | logTransition(line, NEUTRAL_LINE, FROM_LINE); 196 | return FROM_LINE; 197 | } else if (matchesToLinePattern(line)) { 198 | logTransition(line, NEUTRAL_LINE, TO_LINE); 199 | return TO_LINE; 200 | } else if (matchesEndPattern(line, window)) { 201 | logTransition(line, NEUTRAL_LINE, END); 202 | return END; 203 | } else if (matchesHunkStartPattern(line)) { 204 | logTransition(line, NEUTRAL_LINE, HUNK_START); 205 | return HUNK_START; 206 | } else if (matchesNeutralPattern(line)) { 207 | logTransition(line, TO_LINE, NEUTRAL_LINE); 208 | return NEUTRAL_LINE; 209 | } else { 210 | // logTransition(line, NEUTRAL_LINE, NEUTRAL_LINE); 211 | // return NEUTRAL_LINE; 212 | logTransition(line, NEUTRAL_LINE, HEADER); 213 | return HEADER; 214 | } 215 | } 216 | }, 217 | 218 | /** 219 | * The parser is in this state if it is currently parsing a line that is the delimiter between two 220 | * Diffs. This line is always a new line. 221 | */ 222 | END { 223 | @Override 224 | public ParserState nextState(ParseWindow window) { 225 | String line = window.getFocusLine(); 226 | logTransition(line, END, INITIAL); 227 | return INITIAL; 228 | } 229 | }; 230 | 231 | protected static Logger logger = Logger.getLogger(ParserState.class); 232 | 233 | /** 234 | * Returns the next state of the state machine depending on the current state and the content of a 235 | * window of lines around the line that is currently being parsed. 236 | * 237 | * @param window the window around the line currently being parsed. 238 | * @return the next state of the state machine. 239 | */ 240 | public abstract ParserState nextState(ParseWindow window); 241 | 242 | protected void logTransition(String currentLine, ParserState fromState, ParserState toState) { 243 | logger.debug(String.format("%12s -> %12s: %s", fromState, toState, currentLine)); 244 | } 245 | 246 | protected boolean matchesFromFilePattern(String line, String nextLine) { 247 | return line.startsWith("---") && nextLine.startsWith("+++"); 248 | } 249 | 250 | protected boolean matchesToFilePattern(String line) { 251 | return line.startsWith("+++"); 252 | } 253 | 254 | protected boolean matchesFromLinePattern(String line) { 255 | return line.startsWith("-"); 256 | } 257 | 258 | protected boolean matchesNeutralPattern(String line) { 259 | return line.startsWith(" ") || line.startsWith("\\"); 260 | } 261 | 262 | protected boolean matchesToLinePattern(String line) { 263 | return line.startsWith("+"); 264 | } 265 | 266 | protected boolean matchesHunkStartPattern(String line) { 267 | return LINE_RANGE_PATTERN.matcher(line).matches(); 268 | } 269 | 270 | protected boolean matchesEndPattern(String line, ParseWindow window) { 271 | if ("".equals(line.trim())) { 272 | // We have a newline which might be the delimiter between two diffs. It may just be an empty 273 | // line in the current diff or it 274 | // may be the delimiter to the next diff. This has to be disambiguated... 275 | int i = 1; 276 | String futureLine; 277 | while ((futureLine = window.getFutureLine(i)) != null) { 278 | if (matchesFromFilePattern(futureLine, window.getFutureLine(1))) { 279 | // We found the start of a new diff without another newline in between. That makes the 280 | // current line the delimiter 281 | // between this diff and the next. 282 | return true; 283 | } else if ("".equals(futureLine.trim())) { 284 | // We found another newline after the current newline without a start of a new diff in 285 | // between. That makes the 286 | // current line just a newline within the current diff. 287 | return false; 288 | } else { 289 | i++; 290 | } 291 | } 292 | // We reached the end of the stream. 293 | return true; 294 | } else { 295 | // some diff tools like "svn diff" do not put an empty line between two diffs 296 | // we add that empty line and call the method again 297 | String nextFromFileLine = window.getFutureLine(3); 298 | if (nextFromFileLine != null 299 | && matchesFromFilePattern(nextFromFileLine, window.getFutureLine(1))) { 300 | window.addLine(1, ""); 301 | return matchesEndPattern(line, window); 302 | } else { 303 | return false; 304 | } 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/main/java/com/github/smartcommit/util/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"); you may not use this file 5 | * except in compliance with the License. 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 distributed under the 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | * express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.github.smartcommit.util.diffparser.unified; 15 | 16 | import java.io.*; 17 | import java.util.ArrayList; 18 | import java.util.LinkedList; 19 | import java.util.List; 20 | import java.util.regex.Matcher; 21 | import java.util.regex.Pattern; 22 | 23 | /** 24 | * A {@link ResizingParseWindow} slides through the lines of a input stream and offers methods to 25 | * get the currently focused line as well as upcoming lines. It is backed by an automatically 26 | * resizing {@link LinkedList} 27 | * 28 | * @author Tom Hombergs 29 | */ 30 | @SuppressWarnings("UnusedDeclaration") 31 | public class ResizingParseWindow implements ParseWindow { 32 | 33 | private BufferedReader reader; 34 | 35 | private LinkedList lineQueue = new LinkedList<>(); 36 | 37 | private int lineNumber = 0; 38 | 39 | private List ignorePatterns = new ArrayList<>(); 40 | 41 | private boolean isEndOfStream = false; 42 | 43 | public ResizingParseWindow(InputStream in) { 44 | Reader unbufferedReader = new InputStreamReader(in); 45 | this.reader = new BufferedReader(unbufferedReader); 46 | } 47 | 48 | public void addIgnorePattern(String ignorePattern) { 49 | this.ignorePatterns.add(Pattern.compile(ignorePattern)); 50 | } 51 | 52 | @Override 53 | public String getFutureLine(int distance) { 54 | try { 55 | resizeWindowIfNecessary(distance + 1); 56 | return lineQueue.get(distance); 57 | } catch (IndexOutOfBoundsException e) { 58 | return null; 59 | } 60 | } 61 | 62 | @Override 63 | public void addLine(int pos, String line) { 64 | lineQueue.add(pos, line); 65 | } 66 | 67 | /** 68 | * Resizes the sliding window to the given size, if necessary. 69 | * 70 | * @param newSize the new size of the window (i.e. the number of lines in the window). 71 | */ 72 | private void resizeWindowIfNecessary(int newSize) { 73 | try { 74 | int numberOfLinesToLoad = newSize - this.lineQueue.size(); 75 | for (int i = 0; i < numberOfLinesToLoad; i++) { 76 | String nextLine = getNextLine(); 77 | if (nextLine != null) { 78 | lineQueue.addLast(nextLine); 79 | } else { 80 | throw new IndexOutOfBoundsException("End of stream has been reached!"); 81 | } 82 | } 83 | } catch (IOException e) { 84 | throw new RuntimeException(e); 85 | } 86 | } 87 | 88 | @Override 89 | public String slideForward() { 90 | try { 91 | lineQueue.pollFirst(); 92 | lineNumber++; 93 | if (lineQueue.isEmpty()) { 94 | String nextLine = getNextLine(); 95 | if (nextLine != null) { 96 | lineQueue.addLast(nextLine); 97 | } 98 | return nextLine; 99 | } else { 100 | return lineQueue.peekFirst(); 101 | } 102 | } catch (IOException e) { 103 | throw new RuntimeException(e); 104 | } 105 | } 106 | 107 | private String getNextLine() throws IOException { 108 | String nextLine = reader.readLine(); 109 | while (matchesIgnorePattern(nextLine)) { 110 | nextLine = reader.readLine(); 111 | } 112 | 113 | return getNextLineOrVirtualBlankLineAtEndOfStream(nextLine); 114 | } 115 | 116 | /** 117 | * Guarantees that a virtual blank line is injected at the end of the input stream to ensure the 118 | * parser attempts to transition to the {@code END} state, if necessary, when the end of stream is 119 | * reached. 120 | */ 121 | private String getNextLineOrVirtualBlankLineAtEndOfStream(String nextLine) { 122 | if ((nextLine == null) && !isEndOfStream) { 123 | isEndOfStream = true; 124 | return ""; 125 | } 126 | 127 | return nextLine; 128 | } 129 | 130 | private boolean matchesIgnorePattern(String line) { 131 | if (line == null) { 132 | return false; 133 | } else { 134 | for (Pattern pattern : ignorePatterns) { 135 | Matcher matcher = pattern.matcher(line); 136 | if (matcher.matches()) { 137 | return true; 138 | } 139 | } 140 | return false; 141 | } 142 | } 143 | 144 | @Override 145 | public String getFocusLine() { 146 | return lineQueue.element(); 147 | } 148 | 149 | @Override 150 | public int getFocusLineNumber() { 151 | return lineNumber; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/test/java/com/github/smartcommit/MetricsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import smile.validation.metric.AdjustedRandIndex; 5 | 6 | import static org.assertj.core.api.Assertions.*; 7 | 8 | public class MetricsTest { 9 | @Test 10 | public void testAdjustedRandIndex() { 11 | int[] a = new int[] {0, 0, 1, 1}; 12 | int[] b = {0, 0, 1, 2}; 13 | double ari = AdjustedRandIndex.of(a, b); 14 | assertThat(ari).isCloseTo(0.57, withinPercentage(10)); 15 | assertThat(ari).isCloseTo(0.57, within(0.01)); 16 | assertThat(ari).isCloseTo(0.57, offset(0.01)); 17 | assertThat((float) ari).isCloseTo(0.57f, within(0.01f)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/github/smartcommit/TestContentType.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit; 2 | 3 | import com.github.smartcommit.model.constant.ContentType; 4 | import com.github.smartcommit.util.Utils; 5 | import org.apache.log4j.PropertyConfigurator; 6 | import org.junit.jupiter.api.BeforeAll; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | public class TestContentType { 15 | @BeforeAll 16 | public static void setUpBeforeAll() { 17 | PropertyConfigurator.configure("log4j.properties"); 18 | } 19 | 20 | @Test 21 | public void testAllImport() { 22 | List codeLines = new ArrayList<>(); 23 | codeLines.add("import org.apache.log4j.PropertyConfigurator;"); 24 | codeLines.add("import org.junit.jupiter.api.BeforeAll;"); 25 | codeLines.add("import org.junit.jupiter.api.Test;"); 26 | ContentType type = Utils.checkContentType(codeLines); 27 | assertThat(type).isEqualTo(ContentType.IMPORT); 28 | } 29 | 30 | @Test 31 | public void testAllComment() { 32 | List codeLines = new ArrayList<>(); 33 | codeLines.add("/*import org.apache.log4j.PropertyConfigurator;"); 34 | codeLines.add("*import org.junit.jupiter.api.BeforeAll;"); 35 | codeLines.add("*/"); 36 | codeLines.add("// jjjj"); 37 | ContentType type = Utils.checkContentType(codeLines); 38 | assertThat(type).isEqualTo(ContentType.COMMENT); 39 | } 40 | 41 | @Test 42 | public void testMixed() { 43 | List codeLines = new ArrayList<>(); 44 | codeLines.add("import org.apache.log4j.PropertyConfigurator;"); 45 | codeLines.add("import org.junit.jupiter.api.BeforeAll;"); 46 | codeLines.add("public class TestContentType {\n"); 47 | codeLines.add("// jjjj"); 48 | ContentType type = Utils.checkContentType(codeLines); 49 | assertThat(type).isEqualTo(ContentType.CODE); 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/test/java/com/github/smartcommit/TestDistance.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit; 2 | 3 | import com.github.smartcommit.evaluation.Evaluation; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class TestDistance { 14 | // @BeforeAll 15 | // public static void setUpBeforeAll() { 16 | // PropertyConfigurator.configure("log4j.properties"); 17 | // } 18 | 19 | @Test 20 | public void testHierarchyDis() { 21 | Map hier1 = new HashMap<>(); 22 | hier1.put("hunk", 5); 23 | hier1.put("member", 6); 24 | hier1.put("class", 7); 25 | hier1.put("package", 8); 26 | Map hier2 = new HashMap<>(); 27 | hier2.put("hunk", 4); 28 | hier2.put("member", 6); 29 | hier2.put("class", 9); 30 | hier2.put("package", 10); 31 | assertThat(compareHierarchy(hier1, hier2)).isEqualTo(1); 32 | } 33 | 34 | private int compareHierarchy(Map hier1, Map hier2) { 35 | if (hier1.isEmpty() || hier2.isEmpty()) { 36 | return -1; 37 | } 38 | int res = 4; 39 | for (Map.Entry entry : hier1.entrySet()) { 40 | if (hier2.containsKey(entry.getKey())) { 41 | if (hier2.get(entry.getKey()).equals(entry.getValue())) { 42 | int t = -1; 43 | switch (entry.getKey()) { 44 | case "hunk": 45 | t = 0; 46 | break; 47 | case "member": 48 | t = 1; 49 | break; 50 | case "class": 51 | t = 2; 52 | break; 53 | case "package": 54 | t = 3; 55 | break; 56 | } 57 | res = Math.min(res, t); 58 | } 59 | } 60 | } 61 | return res; 62 | } 63 | 64 | @Test 65 | public void testEditDistance() { 66 | List list1 = new ArrayList<>(); 67 | list1.add(0); 68 | list1.add(1); 69 | list1.add(2); 70 | list1.add(3); 71 | List list2 = new ArrayList<>(); 72 | list2.add(1); 73 | list2.add(3); 74 | list2.add(0); 75 | list2.add(2); 76 | assertThat(Evaluation.editDistance(list1, list2)).isEqualTo(2); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/github/smartcommit/TestReformatting.java: -------------------------------------------------------------------------------- 1 | package com.github.smartcommit; 2 | 3 | import com.github.smartcommit.util.Utils; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class TestReformatting { 12 | @Test 13 | public void testWhitespace() { 14 | List s1 = new ArrayList<>(); 15 | s1.add("public static String formatPath(String path) {"); 16 | List s2 = new ArrayList<>(); 17 | s2.add("public static String formatPath(String path) {\");"); 18 | s2.add("\t\n"); 19 | assertThat(Utils.convertListToStringNoFormat(s1)) 20 | .isEqualTo(Utils.convertListToStringNoFormat(s2)); 21 | } 22 | 23 | @Test 24 | public void testIndentation() { 25 | 26 | List s1 = new ArrayList<>(); 27 | s1.add("public static String formatPath(String path) {"); 28 | s1.add("}"); 29 | List s2 = new ArrayList<>(); 30 | s2.add(" public static String formatPath(String path) {\n\");"); 31 | s2.add("\t}"); 32 | assertThat(Utils.convertListToStringNoFormat(s1)) 33 | .isEqualTo(Utils.convertListToStringNoFormat(s2)); 34 | } 35 | 36 | @Test 37 | public void testComment() { 38 | List s1 = new ArrayList<>(); 39 | 40 | s1.add(" /** Convert system-dependent path to the unified unix style */\n"); 41 | s1.add(" public static String formatPath(String path) {\n"); 42 | 43 | List s2 = new ArrayList<>(); 44 | s2.add(" /** "); 45 | s2.add(" * Convert system-dependent path to the unified unix style "); 46 | s2.add("*/\n"); 47 | s2.add(" public static String formatPath(String path) {\n"); 48 | 49 | assertThat(Utils.convertListToStringNoFormat(s1)) 50 | .isEqualTo(Utils.convertListToStringNoFormat(s2)); 51 | } 52 | 53 | @Test 54 | public void testPunctuation() { 55 | List s1 = new ArrayList<>(); 56 | 57 | s1.add("JAR(\".jar\"; \"Jar\"),"); 58 | s1.add("XML(\".xml\"; \"XML\"),"); 59 | s1.add("OTHER(\".*\"; \"Other\");"); 60 | 61 | List s2 = new ArrayList<>(); 62 | s2.add("JAR(\".jar\", \"Jar\");"); 63 | s2.add("XML(\".xml\", \"XML\");"); 64 | s2.add("OTHER(\".*\", \"Other\");"); 65 | 66 | assertThat(Utils.convertListToStringNoFormat(s1)) 67 | .isEqualTo(Utils.convertListToStringNoFormat(s2)); 68 | } 69 | } 70 | --------------------------------------------------------------------------------