larc r24

181 lines ยท 5.6 KB Raw
1 package org.iwakura.larcjb.changes
2
3 import com.intellij.openapi.diagnostic.Logger
4 import com.intellij.openapi.progress.ProgressIndicator
5 import com.intellij.openapi.project.Project
6 import com.intellij.openapi.vcs.FilePath
7 import com.intellij.openapi.vcs.FileStatus
8 import com.intellij.openapi.vcs.ProjectLevelVcsManager
9 import com.intellij.openapi.vcs.VcsKey
10 import com.intellij.openapi.vcs.changes.*
11 import com.intellij.openapi.vcs.history.VcsRevisionNumber
12 import com.intellij.openapi.vfs.VirtualFile
13 import com.intellij.vcsUtil.VcsUtil
14 import org.iwakura.larcjb.LarcExecutable
15 import org.iwakura.larcjb.LarcVcs
16
17 /**
18 * Provides file change information by parsing `larc status` output.
19 */
20 class LarcChangeProvider(
21 private val project: Project,
22 private val executable: LarcExecutable
23 ) : ChangeProvider {
24
25 private val log = Logger.getInstance(LarcChangeProvider::class.java)
26
27 private val vcsKey: VcsKey by lazy {
28 val vcs = ProjectLevelVcsManager.getInstance(project).findVcsByName(LarcVcs.NAME)
29 vcs?.keyInstanceMethod ?: throw IllegalStateException("Larc VCS not found")
30 }
31
32 override fun getChanges(
33 dirtyScope: VcsDirtyScope,
34 builder: ChangelistBuilder,
35 progress: ProgressIndicator,
36 addGate: ChangeListManagerGate
37 ) {
38 val roots = dirtyScope.affectedContentRoots
39 for (root in roots) {
40 progress.checkCanceled()
41 collectChangesForRoot(root, builder)
42 }
43 }
44
45 private fun collectChangesForRoot(root: VirtualFile, builder: ChangelistBuilder) {
46 val result = executable.status(root)
47
48 if (!result.isSuccess) {
49 log.warn("Failed to get status for ${root.path}: ${result.stderr}")
50 return
51 }
52
53 parseStatusOutput(result.stdout, root, builder)
54 }
55
56 /**
57 * Parse larc status output format:
58 *
59 * Untracked files:
60 * newfile.txt
61 *
62 * Staged changes:
63 * added: file1.txt
64 * modified: file2.txt
65 * deleted: file3.txt
66 *
67 * Unstaged changes:
68 * modified: file4.txt
69 * deleted: file5.txt
70 */
71 private fun parseStatusOutput(output: String, root: VirtualFile, builder: ChangelistBuilder) {
72 var section = Section.NONE
73 val rootPath = root.path
74
75 for (line in output.lines()) {
76 val trimmed = line.trim()
77
78 when {
79 trimmed.isEmpty() -> continue
80 trimmed.startsWith("Untracked files:") -> section = Section.UNTRACKED
81 trimmed.startsWith("Staged changes:") -> section = Section.STAGED
82 trimmed.startsWith("Unstaged changes:") -> section = Section.UNSTAGED
83 trimmed.startsWith("No changes") -> return
84 else -> {
85 when (section) {
86 Section.UNTRACKED -> {
87 val filePath = VcsUtil.getFilePath("$rootPath/$trimmed")
88 builder.processUnversionedFile(filePath)
89 }
90 Section.STAGED -> {
91 processChange(trimmed, rootPath, builder, staged = true)
92 }
93 Section.UNSTAGED -> {
94 processChange(trimmed, rootPath, builder, staged = false)
95 }
96 Section.NONE -> { /* skip header lines */ }
97 }
98 }
99 }
100 }
101 }
102
103 private fun processChange(
104 line: String,
105 rootPath: String,
106 builder: ChangelistBuilder,
107 staged: Boolean
108 ) {
109 val parts = line.split(":", limit = 2)
110 if (parts.size != 2) return
111
112 val changeType = parts[0].trim().lowercase()
113 val fileName = parts[1].trim()
114 val filePath = VcsUtil.getFilePath("$rootPath/$fileName")
115
116 val change = when (changeType) {
117 "added" -> {
118 Change(null, CurrentContentRevision(filePath), FileStatus.ADDED)
119 }
120 "modified" -> {
121 Change(
122 LarcContentRevision(filePath, executable, rootPath),
123 CurrentContentRevision(filePath),
124 FileStatus.MODIFIED
125 )
126 }
127 "deleted" -> {
128 Change(
129 LarcContentRevision(filePath, executable, rootPath),
130 null,
131 FileStatus.DELETED
132 )
133 }
134 else -> null
135 }
136
137 if (change != null) {
138 builder.processChange(change, vcsKey)
139 }
140 }
141
142 override fun isModifiedDocumentTrackingRequired(): Boolean = false
143
144 private enum class Section {
145 NONE, UNTRACKED, STAGED, UNSTAGED
146 }
147 }
148
149 /**
150 * Content revision for current working copy
151 */
152 class CurrentContentRevision(private val filePath: FilePath) : ContentRevision {
153 override fun getContent(): String? {
154 val file = filePath.virtualFile ?: return null
155 return String(file.contentsToByteArray(), Charsets.UTF_8)
156 }
157
158 override fun getFile(): FilePath = filePath
159
160 override fun getRevisionNumber(): VcsRevisionNumber = VcsRevisionNumber.NULL
161 }
162
163 /**
164 * Content revision for larc committed version
165 */
166 class LarcContentRevision(
167 private val filePath: FilePath,
168 private val executable: LarcExecutable,
169 private val rootPath: String
170 ) : ContentRevision {
171
172 override fun getContent(): String? {
173 /* TODO(kroot): implement fetching content from last committed revision */
174 return null
175 }
176
177 override fun getFile(): FilePath = filePath
178
179 override fun getRevisionNumber(): VcsRevisionNumber = VcsRevisionNumber.NULL
180 }
181