Continued work to remove a bunch of Hg-related commands.
[nbgit.git] / src / org / netbeans / modules / git / ui / update / ResolveConflictsExecutor.java
blobaf12857f355087ba0066042f2dccc12ef3c81abf
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common
8 * Development and Distribution License("CDDL") (collectively, the
9 * "License"). You may not use this file except in compliance with the
10 * License. You can obtain a copy of the License at
11 * http://www.netbeans.org/cddl-gplv2.html
12 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13 * specific language governing permissions and limitations under the
14 * License. When distributing the software, include this License Header
15 * Notice in each file and include the License file at
16 * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17 * particular file as subject to the "Classpath" exception as provided
18 * by Sun in the GPL Version 2 section of the License file that
19 * accompanied this code. If applicable, add the following below the
20 * License Header, with the fields enclosed by brackets [] replaced by
21 * your own identifying information:
22 * "Portions Copyrighted [year] [name of copyright owner]"
24 * Contributor(s):
26 * The Original Software is NetBeans. The Initial Developer of the Original
27 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28 * Microsystems, Inc. All Rights Reserved.
29 * Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions).
31 * If you wish your version of this file to be governed by only the CDDL
32 * or only the GPL Version 2, indicate your decision by adding
33 * "[Contributor] elects to include this software in this distribution
34 * under the [CDDL or GPL Version 2] license." If you do not indicate a
35 * single choice of license, a recipient has the option to distribute
36 * your version of this file under either the CDDL, the GPL Version 2 or
37 * to extend the choice of license to its licensees as provided above.
38 * However, if you add GPL Version 2 code and therefore, elected the GPL
39 * Version 2 license, then the option applies only if the new code is
40 * made subject to such option by the copyright holder.
42 package org.netbeans.modules.git.ui.update;
44 import java.awt.Component;
45 import java.io.BufferedReader;
46 import java.io.BufferedWriter;
47 import java.io.File;
48 import java.io.FileOutputStream;
49 import java.io.FileReader;
50 import java.io.FileWriter;
51 import java.io.FilterWriter;
52 import java.io.IOException;
53 import java.io.OutputStreamWriter;
54 import java.io.Reader;
55 import java.io.Writer;
56 import java.nio.charset.Charset;
57 import java.util.ArrayList;
58 import java.util.Iterator;
59 import java.util.Set;
60 import javax.swing.SwingUtilities;
61 import org.netbeans.api.diff.Difference;
62 import org.netbeans.api.diff.StreamSource;
63 import org.netbeans.api.queries.FileEncodingQuery;
64 import org.netbeans.modules.git.GitProgressSupport;
65 import org.netbeans.spi.diff.MergeVisualizer;
66 import org.openide.filesystems.FileAlreadyLockedException;
67 import org.openide.filesystems.FileLock;
68 import org.openide.filesystems.FileObject;
69 import org.openide.filesystems.FileUtil;
70 import org.openide.util.Lookup;
71 import org.openide.windows.TopComponent;
73 /**
74 * Shows basic conflict resolver UI.
76 * This class is copy&pasted from javacvs
78 * @author Martin Entlicher
80 public class ResolveConflictsExecutor extends GitProgressSupport {
82 private static final String TMP_PREFIX = "merge"; // NOI18N
83 private static final String ORIG_SUFFIX = ".orig."; // NOI18N
85 static final String CHANGE_LEFT = "<<<<<<< "; // NOI18N
86 static final String CHANGE_RIGHT = ">>>>>>> "; // NOI18N
87 static final String CHANGE_DELIMETER = "======="; // NOI18N
88 static final String CHANGE_BASE_DELIMETER = "|||||||"; // NOI18N
90 private String leftFileRevision = null;
91 private String rightFileRevision = null;
93 private final File file;
95 public ResolveConflictsExecutor(File file) {
96 super();
97 this.file = file;
100 public void exec() {
101 assert SwingUtilities.isEventDispatchThread();
102 MergeVisualizer merge = (MergeVisualizer) Lookup.getDefault().lookup(MergeVisualizer.class);
103 if (merge == null) {
104 throw new IllegalStateException("No Merge engine found."); // NOI18N
107 try {
108 FileObject fo = FileUtil.toFileObject(file);
109 handleMergeFor(file, fo, fo.lock(), merge);
110 } catch (FileAlreadyLockedException e) {
111 Set components = TopComponent.getRegistry().getOpened();
112 for (Iterator i = components.iterator(); i.hasNext();) {
113 TopComponent tc = (TopComponent) i.next();
114 if (tc.getClientProperty(ResolveConflictsExecutor.class.getName()) != null) {
115 tc.requestActive();
118 } catch (IOException ioex) {
119 org.openide.ErrorManager.getDefault().notify(ioex);
123 private void handleMergeFor(final File file, FileObject fo, FileLock lock,
124 final MergeVisualizer merge) throws IOException {
125 String mimeType = (fo == null) ? "text/plain" : fo.getMIMEType(); // NOI18N
126 String ext = "."+fo.getExt(); // NOI18N
127 File f1 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
128 File f2 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
129 File f3 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
130 f1.deleteOnExit();
131 f2.deleteOnExit();
132 f3.deleteOnExit();
134 final Difference[] diffs = copyParts(true, file, f1, true);
135 if (diffs.length == 0) {
136 ConflictResolvedAction.resolved(file); // remove conflict status
137 return;
140 copyParts(false, file, f2, false);
141 //GraphicalMergeVisualizer merge = new GraphicalMergeVisualizer();
142 String originalLeftFileRevision = leftFileRevision;
143 String originalRightFileRevision = rightFileRevision;
144 if (leftFileRevision != null) leftFileRevision.trim();
145 if (rightFileRevision != null) rightFileRevision.trim();
146 if (leftFileRevision == null || leftFileRevision.equals(file.getAbsolutePath() + ORIG_SUFFIX)){
147 leftFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleWorkingFile"); // NOI18N
148 } else {
149 leftFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleRevision", leftFileRevision); // NOI18N
151 if (rightFileRevision == null || rightFileRevision.equals(file.getAbsolutePath() + ORIG_SUFFIX)) {
152 rightFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleWorkingFile"); // NOI18N
153 } else {
154 rightFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleRevision", rightFileRevision); // NOI18N
157 final StreamSource s1;
158 final StreamSource s2;
159 Charset encoding = FileEncodingQuery.getEncoding(fo);
160 s1 = StreamSource.createSource(file.getName(), leftFileRevision, mimeType, f1);
161 s2 = StreamSource.createSource(file.getName(), rightFileRevision, mimeType, f2);
162 final StreamSource result = new MergeResultWriterInfo(f1, f2, f3, file, mimeType,
163 originalLeftFileRevision,
164 originalRightFileRevision,
165 fo, lock, encoding);
167 try {
168 Component c = merge.createView(diffs, s1, s2, result);
169 if (c instanceof TopComponent) {
170 ((TopComponent) c).putClientProperty(ResolveConflictsExecutor.class.getName(), Boolean.TRUE);
172 } catch (IOException ioex) {
173 org.openide.ErrorManager.getDefault().notify(ioex);
178 * Copy the file and conflict parts into another file.
180 private Difference[] copyParts(boolean generateDiffs, File source,
181 File dest, boolean leftPart) throws IOException {
182 BufferedReader r = new BufferedReader(new FileReader(source));
183 BufferedWriter w = new BufferedWriter(new FileWriter(dest));
184 ArrayList<Difference> diffList = null;
185 if (generateDiffs) {
186 diffList = new ArrayList<Difference>();
188 try {
189 String line;
190 boolean isChangeLeft = false;
191 boolean isChangeRight = false;
192 boolean isChangeBase = false;
193 int f1l1 = 0, f1l2 = 0, f2l1 = 0, f2l2 = 0;
194 StringBuffer text1 = new StringBuffer();
195 StringBuffer text2 = new StringBuffer();
196 int i = 1, j = 1;
197 while ((line = r.readLine()) != null) {
198 // As the Graphical Merge Visualizer does not support 3 way diff,
199 // remove the base diff itself.
200 // Only show the diffs of the two heads against the base
201 if (line.startsWith(CHANGE_BASE_DELIMETER)) {
202 isChangeBase = true;
203 continue;
205 if (isChangeBase && line.startsWith(CHANGE_DELIMETER)) {
206 isChangeBase = false;
207 } else if (isChangeBase) {
208 continue;
210 if (line.startsWith(CHANGE_LEFT)) {
211 if (generateDiffs) {
212 if (leftFileRevision == null) {
213 leftFileRevision = line.substring(CHANGE_LEFT.length());
215 if (isChangeLeft) {
216 f1l2 = i - 1;
217 diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
218 f1l1 - 1, 0, f2l1, f2l2,
219 text1.toString(),
220 text2.toString()) :
221 (f2l1 > f2l2) ? new Difference(Difference.DELETE,
222 f1l1, f1l2, f2l1 - 1, 0,
223 text1.toString(),
224 text2.toString())
225 : new Difference(Difference.CHANGE,
226 f1l1, f1l2, f2l1, f2l2,
227 text1.toString(),
228 text2.toString()));
229 f1l1 = f1l2 = f2l1 = f2l2 = 0;
230 text1.delete(0, text1.length());
231 text2.delete(0, text2.length());
232 } else {
233 f1l1 = i;
236 isChangeLeft = !isChangeLeft;
237 continue;
238 } else if (line.startsWith(CHANGE_RIGHT)) {
239 if (generateDiffs) {
240 if (rightFileRevision == null) {
241 rightFileRevision = line.substring(CHANGE_RIGHT.length());
243 if (isChangeRight) {
244 f2l2 = j - 1;
245 diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
246 f1l1 - 1, 0, f2l1, f2l2,
247 text1.toString(),
248 text2.toString()) :
249 (f2l1 > f2l2) ? new Difference(Difference.DELETE,
250 f1l1, f1l2, f2l1 - 1, 0,
251 text1.toString(),
252 text2.toString())
253 : new Difference(Difference.CHANGE,
254 f1l1, f1l2, f2l1, f2l2,
255 text1.toString(),
256 text2.toString()));
258 diffList.add(new Difference((f1l1 > f1l2) ? Difference.ADD :
259 (f2l1 > f2l2) ? Difference.DELETE :
260 Difference.CHANGE,
261 f1l1, f1l2, f2l1, f2l2));
263 f1l1 = f1l2 = f2l1 = f2l2 = 0;
264 text1.delete(0, text1.length());
265 text2.delete(0, text2.length());
266 } else {
267 f2l1 = j;
270 isChangeRight = !isChangeRight;
271 continue;
272 } else if (isChangeRight && line.indexOf(CHANGE_RIGHT) != -1) {
273 String lineText = line.substring(0, line.lastIndexOf(CHANGE_RIGHT));
274 if (generateDiffs) {
275 if (rightFileRevision == null) {
276 rightFileRevision = line.substring(line.lastIndexOf(CHANGE_RIGHT) + CHANGE_RIGHT.length());
278 text2.append(lineText);
279 f2l2 = j;
280 diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
281 f1l1 - 1, 0, f2l1, f2l2,
282 text1.toString(),
283 text2.toString()) :
284 (f2l1 > f2l2) ? new Difference(Difference.DELETE,
285 f1l1, f1l2, f2l1 - 1, 0,
286 text1.toString(),
287 text2.toString())
288 : new Difference(Difference.CHANGE,
289 f1l1, f1l2, f2l1, f2l2,
290 text1.toString(),
291 text2.toString()));
292 f1l1 = f1l2 = f2l1 = f2l2 = 0;
293 text1.delete(0, text1.length());
294 text2.delete(0, text2.length());
296 if (!leftPart) {
297 w.write(lineText);
298 w.newLine();
300 isChangeRight = !isChangeRight;
301 continue;
302 } else if (line.equals(CHANGE_DELIMETER)) {
303 if (isChangeLeft) {
304 isChangeLeft = false;
305 isChangeRight = true;
306 f1l2 = i - 1;
307 f2l1 = j;
308 continue;
309 } else if (isChangeRight) {
310 isChangeRight = false;
311 isChangeLeft = true;
312 f2l2 = j - 1;
313 f1l1 = i;
314 continue;
316 } else if (line.endsWith(CHANGE_DELIMETER)) {
317 String lineText = line.substring(0, line.length() - CHANGE_DELIMETER.length()) + "\n"; // NOI18N
318 if (isChangeLeft) {
319 text1.append(lineText);
320 if (leftPart) {
321 w.write(lineText);
322 w.newLine();
324 isChangeLeft = false;
325 isChangeRight = true;
326 f1l2 = i;
327 f2l1 = j;
328 } else if (isChangeRight) {
329 text2.append(lineText);
330 if (!leftPart) {
331 w.write(lineText);
332 w.newLine();
334 isChangeRight = false;
335 isChangeLeft = true;
336 f2l2 = j;
337 f1l1 = i;
339 continue;
341 if (!isChangeLeft && !isChangeRight || leftPart == isChangeLeft) {
342 w.write(line);
343 w.newLine();
345 if (isChangeLeft) text1.append(line + "\n"); // NOI18N
346 if (isChangeRight) text2.append(line + "\n"); // NOI18N
347 if (generateDiffs) {
348 if (isChangeLeft) i++;
349 else if (isChangeRight) j++;
350 else {
351 i++;
352 j++;
356 } finally {
357 try {
358 r.close();
359 } finally {
360 w.close();
363 if (generateDiffs) {
364 return diffList.toArray(new Difference[diffList.size()]);
365 } else {
366 return null;
370 public void perform() {
371 exec();
374 @Override
375 public void run() {
376 throw new RuntimeException("Not implemented"); // NOI18N
380 private static class MergeResultWriterInfo extends StreamSource {
382 private File tempf1, tempf2, tempf3, outputFile;
383 private File fileToRepairEntriesOf;
384 private String mimeType;
385 private String leftFileRevision;
386 private String rightFileRevision;
387 private FileObject fo;
388 private FileLock lock;
389 private Charset encoding;
391 public MergeResultWriterInfo(File tempf1, File tempf2, File tempf3,
392 File outputFile, String mimeType,
393 String leftFileRevision, String rightFileRevision,
394 FileObject fo, FileLock lock, Charset encoding) {
395 this.tempf1 = tempf1;
396 this.tempf2 = tempf2;
397 this.tempf3 = tempf3;
398 this.outputFile = outputFile;
399 this.mimeType = mimeType;
400 this.leftFileRevision = leftFileRevision;
401 this.rightFileRevision = rightFileRevision;
402 this.fo = fo;
403 this.lock = lock;
404 if (encoding == null) {
405 encoding = FileEncodingQuery.getEncoding(FileUtil.toFileObject(tempf1));
407 this.encoding = encoding;
410 public String getName() {
411 return outputFile.getName();
414 public String getTitle() {
415 return org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Merge.titleResult"); // NOI18N
418 public String getMIMEType() {
419 return mimeType;
422 public Reader createReader() throws IOException {
423 throw new IOException("No reader of merge result"); // NOI18N
427 * Create a writer, that writes to the source.
428 * @param conflicts The list of conflicts remaining in the source.
429 * Can be <code>null</code> if there are no conflicts.
430 * @return The writer or <code>null</code>, when no writer can be created.
432 public Writer createWriter(Difference[] conflicts) throws IOException {
433 Writer w;
434 if (fo != null) {
435 w = new OutputStreamWriter(fo.getOutputStream(lock), encoding);
436 } else {
437 w = new OutputStreamWriter(new FileOutputStream(outputFile), encoding);
439 if (conflicts == null || conflicts.length == 0) {
440 fileToRepairEntriesOf = outputFile;
441 return w;
442 } else {
443 return new MergeConflictFileWriter(w, fo, conflicts,
444 leftFileRevision, rightFileRevision);
449 * This method is called when the visual merging process is finished.
450 * All possible writting processes are finished before this method is called.
452 @Override
453 public void close() {
454 tempf1.delete();
455 tempf2.delete();
456 tempf3.delete();
457 if (lock != null) {
458 lock.releaseLock();
459 lock = null;
461 fo = null;
462 if (fileToRepairEntriesOf != null) {
463 repairEntries(fileToRepairEntriesOf);
464 fileToRepairEntriesOf = null;
468 private void repairEntries(File file) {
469 ConflictResolvedAction.resolved(file); // remove conflict status
473 private static class MergeConflictFileWriter extends FilterWriter {
475 private Difference[] conflicts;
476 private int lineNumber;
477 private int currentConflict;
478 private String leftName;
479 private String rightName;
480 private FileObject fo;
482 public MergeConflictFileWriter(Writer delegate, FileObject fo,
483 Difference[] conflicts, String leftName,
484 String rightName) throws IOException {
485 super(delegate);
486 this.conflicts = conflicts;
487 this.leftName = leftName;
488 this.rightName = rightName;
489 this.lineNumber = 1;
490 this.currentConflict = 0;
491 if (lineNumber == conflicts[currentConflict].getFirstStart()) {
492 writeConflict(conflicts[currentConflict]);
493 currentConflict++;
495 this.fo = fo;
498 @Override
499 public void write(String str) throws IOException {
500 super.write(str);
501 lineNumber += numChars('\n', str);
502 if (currentConflict < conflicts.length && lineNumber >= conflicts[currentConflict].getFirstStart()) {
503 writeConflict(conflicts[currentConflict]);
504 currentConflict++;
508 private void writeConflict(Difference conflict) throws IOException {
509 super.write(CHANGE_LEFT + leftName + "\n"); // NOI18N
510 super.write(conflict.getFirstText());
511 super.write(CHANGE_DELIMETER + "\n"); // NOI18N
512 super.write(conflict.getSecondText());
513 super.write(CHANGE_RIGHT + rightName + "\n"); // NOI18N
516 private static int numChars(char c, String str) {
517 int n = 0;
518 for (int pos = str.indexOf(c); pos >= 0 && pos < str.length(); pos = str.indexOf(c, pos + 1)) {
519 n++;
521 return n;
524 @Override
525 public void close() throws IOException {
526 super.close();
527 if (fo != null) fo.refresh(true);