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]"
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
;
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
;
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
;
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
) {
101 assert SwingUtilities
.isEventDispatchThread();
102 MergeVisualizer merge
= (MergeVisualizer
) Lookup
.getDefault().lookup(MergeVisualizer
.class);
104 throw new IllegalStateException("No Merge engine found."); // NOI18N
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) {
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
));
134 final Difference
[] diffs
= copyParts(true, file
, f1
, true);
135 if (diffs
.length
== 0) {
136 ConflictResolvedAction
.resolved(file
); // remove conflict status
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
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
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
,
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;
186 diffList
= new ArrayList
<Difference
>();
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();
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
)) {
205 if (isChangeBase
&& line
.startsWith(CHANGE_DELIMETER
)) {
206 isChangeBase
= false;
207 } else if (isChangeBase
) {
210 if (line
.startsWith(CHANGE_LEFT
)) {
212 if (leftFileRevision
== null) {
213 leftFileRevision
= line
.substring(CHANGE_LEFT
.length());
217 diffList
.add((f1l1
> f1l2
) ?
new Difference(Difference
.ADD
,
218 f1l1
- 1, 0, f2l1
, f2l2
,
221 (f2l1
> f2l2
) ?
new Difference(Difference
.DELETE
,
222 f1l1
, f1l2
, f2l1
- 1, 0,
225 : new Difference(Difference
.CHANGE
,
226 f1l1
, f1l2
, f2l1
, f2l2
,
229 f1l1
= f1l2
= f2l1
= f2l2
= 0;
230 text1
.delete(0, text1
.length());
231 text2
.delete(0, text2
.length());
236 isChangeLeft
= !isChangeLeft
;
238 } else if (line
.startsWith(CHANGE_RIGHT
)) {
240 if (rightFileRevision
== null) {
241 rightFileRevision
= line
.substring(CHANGE_RIGHT
.length());
245 diffList
.add((f1l1
> f1l2
) ?
new Difference(Difference
.ADD
,
246 f1l1
- 1, 0, f2l1
, f2l2
,
249 (f2l1
> f2l2
) ?
new Difference(Difference
.DELETE
,
250 f1l1
, f1l2
, f2l1
- 1, 0,
253 : new Difference(Difference
.CHANGE
,
254 f1l1
, f1l2
, f2l1
, f2l2
,
258 diffList.add(new Difference((f1l1 > f1l2) ? Difference.ADD :
259 (f2l1 > f2l2) ? Difference.DELETE :
261 f1l1, f1l2, f2l1, f2l2));
263 f1l1
= f1l2
= f2l1
= f2l2
= 0;
264 text1
.delete(0, text1
.length());
265 text2
.delete(0, text2
.length());
270 isChangeRight
= !isChangeRight
;
272 } else if (isChangeRight
&& line
.indexOf(CHANGE_RIGHT
) != -1) {
273 String lineText
= line
.substring(0, line
.lastIndexOf(CHANGE_RIGHT
));
275 if (rightFileRevision
== null) {
276 rightFileRevision
= line
.substring(line
.lastIndexOf(CHANGE_RIGHT
) + CHANGE_RIGHT
.length());
278 text2
.append(lineText
);
280 diffList
.add((f1l1
> f1l2
) ?
new Difference(Difference
.ADD
,
281 f1l1
- 1, 0, f2l1
, f2l2
,
284 (f2l1
> f2l2
) ?
new Difference(Difference
.DELETE
,
285 f1l1
, f1l2
, f2l1
- 1, 0,
288 : new Difference(Difference
.CHANGE
,
289 f1l1
, f1l2
, f2l1
, f2l2
,
292 f1l1
= f1l2
= f2l1
= f2l2
= 0;
293 text1
.delete(0, text1
.length());
294 text2
.delete(0, text2
.length());
300 isChangeRight
= !isChangeRight
;
302 } else if (line
.equals(CHANGE_DELIMETER
)) {
304 isChangeLeft
= false;
305 isChangeRight
= true;
309 } else if (isChangeRight
) {
310 isChangeRight
= false;
316 } else if (line
.endsWith(CHANGE_DELIMETER
)) {
317 String lineText
= line
.substring(0, line
.length() - CHANGE_DELIMETER
.length()) + "\n"; // NOI18N
319 text1
.append(lineText
);
324 isChangeLeft
= false;
325 isChangeRight
= true;
328 } else if (isChangeRight
) {
329 text2
.append(lineText
);
334 isChangeRight
= false;
341 if (!isChangeLeft
&& !isChangeRight
|| leftPart
== isChangeLeft
) {
345 if (isChangeLeft
) text1
.append(line
+ "\n"); // NOI18N
346 if (isChangeRight
) text2
.append(line
+ "\n"); // NOI18N
348 if (isChangeLeft
) i
++;
349 else if (isChangeRight
) j
++;
364 return diffList
.toArray(new Difference
[diffList
.size()]);
370 public void perform() {
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
;
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() {
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
{
435 w
= new OutputStreamWriter(fo
.getOutputStream(lock
), encoding
);
437 w
= new OutputStreamWriter(new FileOutputStream(outputFile
), encoding
);
439 if (conflicts
== null || conflicts
.length
== 0) {
440 fileToRepairEntriesOf
= outputFile
;
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.
453 public void close() {
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
{
486 this.conflicts
= conflicts
;
487 this.leftName
= leftName
;
488 this.rightName
= rightName
;
490 this.currentConflict
= 0;
491 if (lineNumber
== conflicts
[currentConflict
].getFirstStart()) {
492 writeConflict(conflicts
[currentConflict
]);
499 public void write(String str
) throws IOException
{
501 lineNumber
+= numChars('\n', str
);
502 if (currentConflict
< conflicts
.length
&& lineNumber
>= conflicts
[currentConflict
].getFirstStart()) {
503 writeConflict(conflicts
[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
) {
518 for (int pos
= str
.indexOf(c
); pos
>= 0 && pos
< str
.length(); pos
= str
.indexOf(c
, pos
+ 1)) {
525 public void close() throws IOException
{
527 if (fo
!= null) fo
.refresh(true);