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
.nbgit
.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
;
59 import javax
.swing
.SwingUtilities
;
60 import org
.netbeans
.api
.diff
.Difference
;
61 import org
.netbeans
.api
.diff
.StreamSource
;
62 import org
.netbeans
.api
.queries
.FileEncodingQuery
;
63 import org
.nbgit
.GitProgressSupport
;
64 import org
.netbeans
.spi
.diff
.MergeVisualizer
;
65 import org
.openide
.filesystems
.FileAlreadyLockedException
;
66 import org
.openide
.filesystems
.FileLock
;
67 import org
.openide
.filesystems
.FileObject
;
68 import org
.openide
.filesystems
.FileUtil
;
69 import org
.openide
.util
.Lookup
;
70 import org
.openide
.windows
.TopComponent
;
73 * Shows basic conflict resolver UI.
75 * This class is copy&pasted from javacvs
77 * @author Martin Entlicher
79 public class ResolveConflictsExecutor
extends GitProgressSupport
{
81 private static final String TMP_PREFIX
= "merge"; // NOI18N
82 private static final String ORIG_SUFFIX
= ".orig."; // NOI18N
83 static final String CHANGE_LEFT
= "<<<<<<< "; // NOI18N
84 static final String CHANGE_RIGHT
= ">>>>>>> "; // NOI18N
85 static final String CHANGE_DELIMETER
= "======="; // NOI18N
86 static final String CHANGE_BASE_DELIMETER
= "|||||||"; // NOI18N
87 private String leftFileRevision
= null;
88 private String rightFileRevision
= null;
89 private final File file
;
91 public ResolveConflictsExecutor(File file
) {
97 assert SwingUtilities
.isEventDispatchThread();
98 MergeVisualizer merge
= Lookup
.getDefault().lookup(MergeVisualizer
.class);
100 throw new IllegalStateException("No Merge engine found."); // NOI18N
104 FileObject fo
= FileUtil
.toFileObject(file
);
105 handleMergeFor(file
, fo
, fo
.lock(), merge
);
106 } catch (FileAlreadyLockedException e
) {
107 Set
<TopComponent
> components
= TopComponent
.getRegistry().getOpened();
108 for (TopComponent tc
: components
) {
109 if (tc
.getClientProperty(ResolveConflictsExecutor
.class.getName()) != null) {
113 } catch (IOException ioex
) {
114 org
.openide
.ErrorManager
.getDefault().notify(ioex
);
118 private void handleMergeFor(final File file
, FileObject fo
, FileLock lock
,
119 final MergeVisualizer merge
) throws IOException
{
120 String mimeType
= (fo
== null) ?
"text/plain" : fo
.getMIMEType(); // NOI18N
121 String ext
= "." + fo
.getExt(); // NOI18N
122 File f1
= FileUtil
.normalizeFile(File
.createTempFile(TMP_PREFIX
, ext
));
123 File f2
= FileUtil
.normalizeFile(File
.createTempFile(TMP_PREFIX
, ext
));
124 File f3
= FileUtil
.normalizeFile(File
.createTempFile(TMP_PREFIX
, ext
));
129 final Difference
[] diffs
= copyParts(true, file
, f1
, true);
130 if (diffs
.length
== 0) {
131 ConflictResolvedAction
.resolved(file
); // remove conflict status
135 copyParts(false, file
, f2
, false);
136 //GraphicalMergeVisualizer merge = new GraphicalMergeVisualizer();
137 String originalLeftFileRevision
= leftFileRevision
;
138 String originalRightFileRevision
= rightFileRevision
;
139 if (leftFileRevision
!= null) {
140 leftFileRevision
.trim();
142 if (rightFileRevision
!= null) {
143 rightFileRevision
.trim();
145 if (leftFileRevision
== null || leftFileRevision
.equals(file
.getAbsolutePath() + ORIG_SUFFIX
)) {
146 leftFileRevision
= org
.openide
.util
.NbBundle
.getMessage(ResolveConflictsExecutor
.class, "Diff.titleWorkingFile"); // NOI18N
148 leftFileRevision
= org
.openide
.util
.NbBundle
.getMessage(ResolveConflictsExecutor
.class, "Diff.titleRevision", leftFileRevision
); // NOI18N
150 if (rightFileRevision
== null || rightFileRevision
.equals(file
.getAbsolutePath() + ORIG_SUFFIX
)) {
151 rightFileRevision
= org
.openide
.util
.NbBundle
.getMessage(ResolveConflictsExecutor
.class, "Diff.titleWorkingFile"); // NOI18N
153 rightFileRevision
= org
.openide
.util
.NbBundle
.getMessage(ResolveConflictsExecutor
.class, "Diff.titleRevision", rightFileRevision
); // NOI18N
156 final StreamSource s1
;
157 final StreamSource s2
;
158 Charset encoding
= FileEncodingQuery
.getEncoding(fo
);
159 s1
= StreamSource
.createSource(file
.getName(), leftFileRevision
, mimeType
, f1
);
160 s2
= StreamSource
.createSource(file
.getName(), rightFileRevision
, mimeType
, f2
);
161 final StreamSource result
= new MergeResultWriterInfo(f1
, f2
, f3
, file
, mimeType
,
162 originalLeftFileRevision
,
163 originalRightFileRevision
,
167 Component c
= merge
.createView(diffs
, s1
, s2
, result
);
168 if (c
instanceof TopComponent
) {
169 ((TopComponent
) c
).putClientProperty(ResolveConflictsExecutor
.class.getName(), Boolean
.TRUE
);
171 } catch (IOException ioex
) {
172 org
.openide
.ErrorManager
.getDefault().notify(ioex
);
177 * Copy the file and conflict parts into another file.
179 private Difference
[] copyParts(boolean generateDiffs
, File source
,
180 File dest
, boolean leftPart
) throws IOException
{
181 BufferedReader r
= new BufferedReader(new FileReader(source
));
182 BufferedWriter w
= new BufferedWriter(new FileWriter(dest
));
183 ArrayList
<Difference
> diffList
= null;
185 diffList
= new ArrayList
<Difference
>();
189 boolean isChangeLeft
= false;
190 boolean isChangeRight
= false;
191 boolean isChangeBase
= false;
192 int f1l1
= 0, f1l2
= 0, f2l1
= 0, f2l2
= 0;
193 StringBuffer text1
= new StringBuffer();
194 StringBuffer text2
= new StringBuffer();
196 while ((line
= r
.readLine()) != null) {
197 // As the Graphical Merge Visualizer does not support 3 way diff,
198 // remove the base diff itself.
199 // Only show the diffs of the two heads against the base
200 if (line
.startsWith(CHANGE_BASE_DELIMETER
)) {
204 if (isChangeBase
&& line
.startsWith(CHANGE_DELIMETER
)) {
205 isChangeBase
= false;
206 } else if (isChangeBase
) {
209 if (line
.startsWith(CHANGE_LEFT
)) {
211 if (leftFileRevision
== null) {
212 leftFileRevision
= line
.substring(CHANGE_LEFT
.length());
216 diffList
.add((f1l1
> f1l2
) ?
new Difference(Difference
.ADD
,
217 f1l1
- 1, 0, f2l1
, f2l2
,
219 text2
.toString()) : (f2l1
> f2l2
) ?
new Difference(Difference
.DELETE
,
220 f1l1
, f1l2
, f2l1
- 1, 0,
223 : new Difference(Difference
.CHANGE
,
224 f1l1
, f1l2
, f2l1
, f2l2
,
227 f1l1
= f1l2
= f2l1
= f2l2
= 0;
228 text1
.delete(0, text1
.length());
229 text2
.delete(0, text2
.length());
234 isChangeLeft
= !isChangeLeft
;
236 } else if (line
.startsWith(CHANGE_RIGHT
)) {
238 if (rightFileRevision
== null) {
239 rightFileRevision
= line
.substring(CHANGE_RIGHT
.length());
243 diffList
.add((f1l1
> f1l2
) ?
new Difference(Difference
.ADD
,
244 f1l1
- 1, 0, f2l1
, f2l2
,
246 text2
.toString()) : (f2l1
> f2l2
) ?
new Difference(Difference
.DELETE
,
247 f1l1
, f1l2
, f2l1
- 1, 0,
250 : new Difference(Difference
.CHANGE
,
251 f1l1
, f1l2
, f2l1
, f2l2
,
255 diffList.add(new Difference((f1l1 > f1l2) ? Difference.ADD :
256 (f2l1 > f2l2) ? Difference.DELETE :
258 f1l1, f1l2, f2l1, f2l2));
260 f1l1
= f1l2
= f2l1
= f2l2
= 0;
261 text1
.delete(0, text1
.length());
262 text2
.delete(0, text2
.length());
267 isChangeRight
= !isChangeRight
;
269 } else if (isChangeRight
&& line
.indexOf(CHANGE_RIGHT
) != -1) {
270 String lineText
= line
.substring(0, line
.lastIndexOf(CHANGE_RIGHT
));
272 if (rightFileRevision
== null) {
273 rightFileRevision
= line
.substring(line
.lastIndexOf(CHANGE_RIGHT
) + CHANGE_RIGHT
.length());
275 text2
.append(lineText
);
277 diffList
.add((f1l1
> f1l2
) ?
new Difference(Difference
.ADD
,
278 f1l1
- 1, 0, f2l1
, f2l2
,
280 text2
.toString()) : (f2l1
> f2l2
) ?
new Difference(Difference
.DELETE
,
281 f1l1
, f1l2
, f2l1
- 1, 0,
284 : new Difference(Difference
.CHANGE
,
285 f1l1
, f1l2
, f2l1
, f2l2
,
288 f1l1
= f1l2
= f2l1
= f2l2
= 0;
289 text1
.delete(0, text1
.length());
290 text2
.delete(0, text2
.length());
296 isChangeRight
= !isChangeRight
;
298 } else if (line
.equals(CHANGE_DELIMETER
)) {
300 isChangeLeft
= false;
301 isChangeRight
= true;
305 } else if (isChangeRight
) {
306 isChangeRight
= false;
312 } else if (line
.endsWith(CHANGE_DELIMETER
)) {
313 String lineText
= line
.substring(0, line
.length() - CHANGE_DELIMETER
.length()) + "\n"; // NOI18N
315 text1
.append(lineText
);
320 isChangeLeft
= false;
321 isChangeRight
= true;
324 } else if (isChangeRight
) {
325 text2
.append(lineText
);
330 isChangeRight
= false;
337 if (!isChangeLeft
&& !isChangeRight
|| leftPart
== isChangeLeft
) {
342 text1
.append(line
+ "\n"); // NOI18N
345 text2
.append(line
+ "\n"); // NOI18N
350 } else if (isChangeRight
) {
366 return diffList
.toArray(new Difference
[diffList
.size()]);
372 public void perform() {
378 throw new RuntimeException("Not implemented"); // NOI18N
381 private static class MergeResultWriterInfo
extends StreamSource
{
383 private File tempf1
, tempf2
, tempf3
, outputFile
;
384 private File fileToRepairEntriesOf
;
385 private String mimeType
;
386 private String leftFileRevision
;
387 private String rightFileRevision
;
388 private FileObject fo
;
389 private FileLock lock
;
390 private Charset encoding
;
392 public MergeResultWriterInfo(File tempf1
, File tempf2
, File tempf3
,
393 File outputFile
, String mimeType
,
394 String leftFileRevision
, String rightFileRevision
,
395 FileObject fo
, FileLock lock
, Charset encoding
) {
396 this.tempf1
= tempf1
;
397 this.tempf2
= tempf2
;
398 this.tempf3
= tempf3
;
399 this.outputFile
= outputFile
;
400 this.mimeType
= mimeType
;
401 this.leftFileRevision
= leftFileRevision
;
402 this.rightFileRevision
= rightFileRevision
;
405 if (encoding
== null) {
406 encoding
= FileEncodingQuery
.getEncoding(FileUtil
.toFileObject(tempf1
));
408 this.encoding
= encoding
;
411 public String
getName() {
412 return outputFile
.getName();
415 public String
getTitle() {
416 return org
.openide
.util
.NbBundle
.getMessage(ResolveConflictsExecutor
.class, "Merge.titleResult"); // NOI18N
419 public String
getMIMEType() {
423 public Reader
createReader() throws IOException
{
424 throw new IOException("No reader of merge result"); // NOI18N
428 * Create a writer, that writes to the source.
429 * @param conflicts The list of conflicts remaining in the source.
430 * Can be <code>null</code> if there are no conflicts.
431 * @return The writer or <code>null</code>, when no writer can be created.
433 public Writer
createWriter(Difference
[] conflicts
) throws IOException
{
436 w
= new OutputStreamWriter(fo
.getOutputStream(lock
), encoding
);
438 w
= new OutputStreamWriter(new FileOutputStream(outputFile
), encoding
);
440 if (conflicts
== null || conflicts
.length
== 0) {
441 fileToRepairEntriesOf
= outputFile
;
444 return new MergeConflictFileWriter(w
, fo
, conflicts
,
445 leftFileRevision
, rightFileRevision
);
450 * This method is called when the visual merging process is finished.
451 * All possible writting processes are finished before this method is called.
454 public void close() {
463 if (fileToRepairEntriesOf
!= null) {
464 repairEntries(fileToRepairEntriesOf
);
465 fileToRepairEntriesOf
= null;
469 private void repairEntries(File file
) {
470 ConflictResolvedAction
.resolved(file
); // remove conflict status
474 private static class MergeConflictFileWriter
extends FilterWriter
{
476 private Difference
[] conflicts
;
477 private int lineNumber
;
478 private int currentConflict
;
479 private String leftName
;
480 private String rightName
;
481 private FileObject fo
;
483 public MergeConflictFileWriter(Writer delegate
, FileObject fo
,
484 Difference
[] conflicts
, String leftName
,
485 String rightName
) throws IOException
{
487 this.conflicts
= conflicts
;
488 this.leftName
= leftName
;
489 this.rightName
= rightName
;
491 this.currentConflict
= 0;
492 if (lineNumber
== conflicts
[currentConflict
].getFirstStart()) {
493 writeConflict(conflicts
[currentConflict
]);
500 public void write(String str
) throws IOException
{
502 lineNumber
+= numChars('\n', str
);
503 if (currentConflict
< conflicts
.length
&& lineNumber
>= conflicts
[currentConflict
].getFirstStart()) {
504 writeConflict(conflicts
[currentConflict
]);
509 private void writeConflict(Difference conflict
) throws IOException
{
510 super.write(CHANGE_LEFT
+ leftName
+ "\n"); // NOI18N
511 super.write(conflict
.getFirstText());
512 super.write(CHANGE_DELIMETER
+ "\n"); // NOI18N
513 super.write(conflict
.getSecondText());
514 super.write(CHANGE_RIGHT
+ rightName
+ "\n"); // NOI18N
517 private static int numChars(char c
, String str
) {
519 for (int pos
= str
.indexOf(c
); pos
>= 0 && pos
< str
.length(); pos
= str
.indexOf(c
, pos
+ 1)) {
526 public void close() throws IOException
{