├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 |
8 |
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 | }
--------------------------------------------------------------------------------