├── .idea ├── artifacts │ └── j2kt_commiter_jar.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── libraries │ ├── KotlinJavaRuntime.xml │ ├── jansi.xml │ ├── jgit.xml │ └── slf4j.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── j2kt-commiter.iml ├── lib ├── jansi-1.11-sources.jar ├── jansi-1.11.jar ├── org.eclipse.jgit-4.1.0.201509280440-r-sources.jar ├── org.eclipse.jgit-4.1.0.201509280440-r.jar ├── slf4j-api-1.7.12-sources.jar ├── slf4j-api-1.7.12.jar ├── slf4j-simple-1.7.12-sources.jar └── slf4j-simple-1.7.12.jar └── src ├── META-INF └── MANIFEST.MF ├── ansi.kt ├── main.kt └── monitor.kt /.idea/artifacts/j2kt_commiter_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/ 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/libraries/KotlinJavaRuntime.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/jansi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/jgit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/slf4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Latest release](https://github.com/develar/j2kt-commiter/releases/latest) 2 | 3 | Convert java to kotlin as usual and run script in the idea project dir (`java -jar j2kt-commiter.jar`). VCS mappings are used to support several repositories. 4 | 5 | To dry-run (no commits, no file renaming - only log) pass `true` as a first argument: `java -jar j2kt-commiter.jar true` 6 | 7 | Converted files will be committed and history is preserved (only and only converted files will be committed — working directory can be dirty). 8 | 9 | What script does: 10 | 11 | 1. kotlin file renamed to java file (foo.kt -> foo.java). 12 | 2. first commit. 13 | 3. foo.java renamed back to foo.kt. 14 | 4. second (and last) commit. 15 | 16 | Please note — only and only converted files will be committed, so, you should commit another changed files (e.g. if some java file modified to use kotlin API). 17 | 18 | If something went wrong, use `git reset HEAD~1` to delete your last commit and VCS -> Local History to restore your changes. -------------------------------------------------------------------------------- /j2kt-commiter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/jansi-1.11-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/develar/j2kt-commiter/f6fb6975e2c184483d7b77bb351b6820e8d54b94/lib/jansi-1.11-sources.jar -------------------------------------------------------------------------------- /lib/jansi-1.11.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/develar/j2kt-commiter/f6fb6975e2c184483d7b77bb351b6820e8d54b94/lib/jansi-1.11.jar -------------------------------------------------------------------------------- /lib/org.eclipse.jgit-4.1.0.201509280440-r-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/develar/j2kt-commiter/f6fb6975e2c184483d7b77bb351b6820e8d54b94/lib/org.eclipse.jgit-4.1.0.201509280440-r-sources.jar -------------------------------------------------------------------------------- /lib/org.eclipse.jgit-4.1.0.201509280440-r.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/develar/j2kt-commiter/f6fb6975e2c184483d7b77bb351b6820e8d54b94/lib/org.eclipse.jgit-4.1.0.201509280440-r.jar -------------------------------------------------------------------------------- /lib/slf4j-api-1.7.12-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/develar/j2kt-commiter/f6fb6975e2c184483d7b77bb351b6820e8d54b94/lib/slf4j-api-1.7.12-sources.jar -------------------------------------------------------------------------------- /lib/slf4j-api-1.7.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/develar/j2kt-commiter/f6fb6975e2c184483d7b77bb351b6820e8d54b94/lib/slf4j-api-1.7.12.jar -------------------------------------------------------------------------------- /lib/slf4j-simple-1.7.12-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/develar/j2kt-commiter/f6fb6975e2c184483d7b77bb351b6820e8d54b94/lib/slf4j-simple-1.7.12-sources.jar -------------------------------------------------------------------------------- /lib/slf4j-simple-1.7.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/develar/j2kt-commiter/f6fb6975e2c184483d7b77bb351b6820e8d54b94/lib/slf4j-simple-1.7.12.jar -------------------------------------------------------------------------------- /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: org.develar.j2ktCommiter.MainKt 3 | 4 | -------------------------------------------------------------------------------- /src/ansi.kt: -------------------------------------------------------------------------------- 1 | package org.develar.j2ktCommiter 2 | 3 | import org.fusesource.jansi.Ansi 4 | 5 | fun Ansi.bold(text: String) = bold().a(text).boldOff() 6 | 7 | fun Ansi.green(text: String) = fg(Ansi.Color.GREEN).a(text).reset() -------------------------------------------------------------------------------- /src/main.kt: -------------------------------------------------------------------------------- 1 | package org.develar.j2ktCommiter 2 | 3 | import org.eclipse.jgit.api.CommitCommand 4 | import org.eclipse.jgit.api.Git 5 | import org.eclipse.jgit.lib.Constants 6 | import org.eclipse.jgit.lib.IndexDiff 7 | import org.eclipse.jgit.lib.ProgressMonitor 8 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder 9 | import org.eclipse.jgit.treewalk.FileTreeIterator 10 | import org.fusesource.jansi.Ansi 11 | import org.fusesource.jansi.Ansi.ansi 12 | import org.fusesource.jansi.AnsiConsole 13 | import org.w3c.dom.NodeList 14 | import org.xml.sax.InputSource 15 | import java.io.File 16 | import java.nio.file.Files 17 | import java.nio.file.Path 18 | import java.nio.file.Paths 19 | import java.util.* 20 | import javax.xml.xpath.XPathConstants 21 | import javax.xml.xpath.XPathFactory 22 | import kotlin.dom.attribute 23 | import kotlin.dom.toElementList 24 | 25 | private val vcsConfigRelativePath = ".idea${File.separatorChar}vcs.xml" 26 | private val DOT_KT = ".kt" 27 | 28 | fun main(args: Array) { 29 | // call it here to ensure that app will not crash/slowdown unexpectedly 30 | AnsiConsole.systemInstall() 31 | 32 | val dryRun = args.getOrNull(0)?.toBoolean() ?: false 33 | var projectDir = args.getOrNull(1) ?: System.getProperty("user.dir") 34 | val vcsFile = if (projectDir.isNullOrEmpty()) { 35 | Paths.get(vcsConfigRelativePath) 36 | } 37 | else { 38 | projectDir = expandUserHome(projectDir!!) 39 | Paths.get(projectDir, vcsConfigRelativePath) 40 | } 41 | 42 | if (!Files.isRegularFile(vcsFile)) { 43 | System.err.println("$vcsConfigRelativePath not found, please ensure that you run script in the project dir") 44 | return 45 | } 46 | 47 | val directories = ArrayList() 48 | for (element in (XPathFactory.newInstance().newXPath().evaluate("/project/component[@name='VcsDirectoryMappings']/mapping", InputSource(Files.newInputStream(vcsFile)), XPathConstants.NODESET) as NodeList).toElementList()) { 49 | if (element.attribute("vcs") == "Git") { 50 | val directory = element.attribute("directory") 51 | if (!directory.isNullOrEmpty()) { 52 | directories.add(Paths.get(directory.replace("\$PROJECT_DIR$", projectDir))) 53 | } 54 | } 55 | } 56 | 57 | if (directories.isEmpty()) { 58 | System.out.println("warn: GIT VCS mappings not found") 59 | return 60 | } 61 | 62 | for (workingDir in directories) { 63 | if (!Files.isDirectory(workingDir)) { 64 | System.out.println("warn: $workingDir is not a directory") 65 | continue 66 | } 67 | 68 | processRepository(workingDir, dryRun) 69 | } 70 | } 71 | 72 | private fun processRepository(workingDir: Path, dryRun: Boolean) { 73 | System.out.println(ansi().a("Repository ").bold(workingDir.toString())) 74 | 75 | val git = Git(FileRepositoryBuilder().setWorkTree(workingDir.toFile()).setMustExist(true).readEnvironment().build()) 76 | val javaToKotlinFileMap = renameKotlinToJava(dryRun, git, workingDir) 77 | if (javaToKotlinFileMap.isEmpty()) { 78 | System.out.print(ansi().eraseLine().green("\rSkip, no converted files").a(" ".repeat(LEFT_PADDING)).newline().newline()) 79 | return 80 | } 81 | 82 | val commitMessage = StringBuilder() 83 | commitMessage.append("convert ") 84 | for ((javaFile, kotlinFile) in javaToKotlinFileMap) { 85 | commitMessage.append(kotlinFile.className) 86 | commitMessage.append(", ") 87 | } 88 | commitMessage.setLength(commitMessage.length - 2) 89 | 90 | commitMessage.append(" to kotlin") 91 | 92 | if (dryRun) { 93 | System.out.println(ansi().fg(Ansi.Color.GREEN).a("Commit message will be ").bold().a(commitMessage).reset()) 94 | } 95 | else { 96 | val commitMessageAsString = commitMessage.toString() 97 | createCommitCommand(git, javaToKotlinFileMap, true).setMessage(commitMessageAsString).call() 98 | System.out.println(ansi().green("Commit, step 1 of 2 done")) 99 | 100 | val addCommand = git.add() 101 | val removeCommand = git.rm() 102 | for ((javaFile, kotlinFile) in javaToKotlinFileMap) { 103 | Files.move(javaFile.file, kotlinFile.file) 104 | addCommand.addFilepattern(kotlinFile.path) 105 | removeCommand.addFilepattern(javaFile.path) 106 | } 107 | 108 | addCommand.call() 109 | removeCommand.call() 110 | 111 | createCommitCommand(git, javaToKotlinFileMap, false).setMessage(commitMessageAsString).call() 112 | System.out.println(ansi().green("Commit, step 2 of 2 done")) 113 | } 114 | 115 | System.out.println() 116 | } 117 | 118 | private fun renameKotlinToJava(dryRun: Boolean, git: Git, workingDir: Path): List> { 119 | val workingTreeIterator = FileTreeIterator(git.repository) 120 | val diff: IndexDiff 121 | try { 122 | diff = IndexDiff(git.repository, Constants.HEAD, workingTreeIterator) 123 | diff.diff(TextProgressMonitor(System.out), ProgressMonitor.UNKNOWN, ProgressMonitor.UNKNOWN, "Compute status") 124 | } 125 | finally { 126 | workingTreeIterator.reset() 127 | } 128 | 129 | var lineErased = false 130 | 131 | val addCommand = git.add() 132 | val removeCommand = git.rm() 133 | 134 | val javaToKotlinFileMap = ArrayList>() 135 | for (path in diff.added.filter { it.endsWith(DOT_KT) }) { 136 | val pathWithoutExtension = path.substring(0, path.length - DOT_KT.length) 137 | val javaCounterpartPath = "$pathWithoutExtension.java" 138 | if (!(diff.removed.contains(javaCounterpartPath) || diff.missing.contains(javaCounterpartPath))) { 139 | continue 140 | } 141 | 142 | var message = ansi() 143 | if (!lineErased) { 144 | message.a('\r') 145 | lineErased = true 146 | } 147 | val className = getFileName(pathWithoutExtension) 148 | System.out.println(message.a("rename ").bold(className).a(" (").a(getParentPath(pathWithoutExtension)).a(")")) 149 | 150 | val kotlinFile = workingDir.resolve(path) 151 | val javaFile = workingDir.resolve(javaCounterpartPath) 152 | javaToKotlinFileMap.add(FileInfo(javaCounterpartPath, javaFile, className) to FileInfo(path, kotlinFile, className)) 153 | 154 | if (!dryRun) { 155 | Files.move(kotlinFile, javaFile) 156 | addCommand.addFilepattern(javaCounterpartPath) 157 | removeCommand.addFilepattern(path) 158 | } 159 | } 160 | 161 | if (!dryRun && javaToKotlinFileMap.isNotEmpty()) { 162 | addCommand.call() 163 | removeCommand.call() 164 | } 165 | return javaToKotlinFileMap 166 | } 167 | 168 | private fun createCommitCommand(git: Git, javaToKotlinFileMap: List>, onlyJavaFile: Boolean): CommitCommand { 169 | val commitCommand = git.commit() 170 | for ((javaFile, kotlinFile) in javaToKotlinFileMap) { 171 | commitCommand.setOnly(javaFile.path) 172 | if (!onlyJavaFile) { 173 | commitCommand.setOnly(kotlinFile.path) 174 | } 175 | } 176 | return commitCommand 177 | } 178 | 179 | // system-independent path relative to working directory 180 | private data class FileInfo(val path: String, val file: Path, val className: String) 181 | 182 | private fun expandUserHome(path: String) = if (path.startsWith("~/") || path.startsWith("~\\")) "${System.getProperty("user.home")}${path.substring(1)}" else path 183 | 184 | fun getFileName(path: String): String { 185 | if (path.isEmpty()) { 186 | return "" 187 | } 188 | val c = path[path.length - 1] 189 | val end = if (c == '/' || c == '\\') path.length - 1 else path.length 190 | val start = Math.max(path.lastIndexOf('/', end - 1), path.lastIndexOf('\\', end - 1)) + 1 191 | return path.substring(start, end) 192 | } 193 | 194 | fun getParentPath(path: String): String { 195 | if (path.length == 0) { 196 | return "" 197 | } 198 | var end = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')) 199 | if (end == path.length - 1) { 200 | end = Math.max(path.lastIndexOf('/', end - 1), path.lastIndexOf('\\', end - 1)) 201 | } 202 | return if (end == -1) "" else path.substring(0, end) 203 | } -------------------------------------------------------------------------------- /src/monitor.kt: -------------------------------------------------------------------------------- 1 | package org.develar.j2ktCommiter 2 | 3 | import org.eclipse.jgit.lib.BatchingProgressMonitor 4 | import java.io.Flushable 5 | import java.io.PrintWriter 6 | 7 | const val LEFT_PADDING = 20 8 | 9 | open class TextProgressMonitor(private val out: Appendable = PrintWriter(System.err)) : BatchingProgressMonitor() { 10 | override fun onUpdate(taskName: String, workCurr: Int) { 11 | val s = StringBuilder() 12 | format(s, taskName, workCurr) 13 | send(s) 14 | } 15 | 16 | override fun onEndTask(taskName: String, workCurr: Int) { 17 | val s = StringBuilder() 18 | format(s, taskName, workCurr) 19 | send(s) 20 | } 21 | 22 | private fun format(s: StringBuilder, taskName: String, workCurr: Int) { 23 | s.append('\r') 24 | s.append(taskName) 25 | s.append(": ") 26 | while (s.length < LEFT_PADDING) { 27 | s.append(' ') 28 | } 29 | s.append(workCurr) 30 | } 31 | 32 | override fun onUpdate(taskName: String, cmp: Int, totalWork: Int, pcnt: Int) { 33 | val s = StringBuilder() 34 | format(s, taskName, cmp, totalWork, pcnt) 35 | send(s) 36 | } 37 | 38 | override fun onEndTask(taskName: String, cmp: Int, totalWork: Int, pcnt: Int) { 39 | val s = StringBuilder() 40 | format(s, taskName, cmp, totalWork, pcnt) 41 | send(s) 42 | } 43 | 44 | private fun format(s: StringBuilder, taskName: String, cmp: Int, totalWork: Int, pcnt: Int) { 45 | s.append('\r') 46 | s.append(taskName) 47 | s.append(": ") 48 | while (s.length < LEFT_PADDING) { 49 | s.append(' ') 50 | } 51 | 52 | val endStr = totalWork.toString() 53 | var curStr = cmp.toString() 54 | while (curStr.length < endStr.length) { 55 | curStr = " $curStr" 56 | } 57 | if (pcnt < 100) { 58 | s.append(' ') 59 | } 60 | if (pcnt < 10) { 61 | s.append(' ') 62 | } 63 | s.append(pcnt) 64 | s.append("% (") 65 | s.append(curStr) 66 | s.append('/') 67 | s.append(endStr) 68 | s.append(')') 69 | } 70 | 71 | private fun send(s: StringBuilder) { 72 | out.append(s) 73 | if (out is Flushable) { 74 | out.flush() 75 | } 76 | } 77 | } --------------------------------------------------------------------------------