Bug 54091 - Allow headless diff and merge
Summary: Allow headless diff and merge
Status: RESOLVED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: Compare (show other bugs)
Version: 3.0   Edit
Hardware: PC Windows XP
: P2 enhancement (vote)
Target Milestone: 3.0 M9   Edit
Assignee: Andre Weinand CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2004-03-08 16:35 EST by Jean-Michel Lemieux CLA
Modified: 2004-04-20 11:35 EDT (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jean-Michel Lemieux CLA 2004-03-08 16:35:11 EST
Currently there is no headless API for accessing the text differencing engine. 
This makes it imposible to integrate tools into Eclipse that require both 
silent and visual diff/merge capabilities.
Comment 1 Andre Weinand CLA 2004-03-08 17:35:04 EST
Just one clarification: the text differencing engine in org.eclipse.compare.rangedifferencer provides a 
fully headless API (which will not change if I fullfill this enhancement request). So it is certainly not 
"impossible" to integrate tools with the above mentioned requirements.
But I see the need for a new headless org.eclipse.compare.core project.

What do you envision for the input/output abstractions for comparing/merging?
FileBuffers, Streams, IStrorage?
Comment 2 Andre Weinand CLA 2004-03-08 19:25:57 EST
This is an implementation of a headless cvs "update" operation based on the current compare plugin.
All incoming changes of the result of a three-way compare a merged into the file "mine".
Is this a use case for you?

import java.io.PrintStream;
import org.eclipse.rangedifferencer.*;

public class Merge {
	/**
	 * Merges incoming non-conflicting changes into "mine"
	 */
	static void update(PrintStream os, LineComparator a, LineComparator m, LineComparator y) {
		
		RangeDifference[] diffs= RangeDifferencer.findRanges(a, m, y);
		
		for (int i= 0; i < diffs.length; i++) {
			RangeDifference rd= diffs[i];
			switch (rd.kind()) {
			case RangeDifference.NOCHANGE:
			case RangeDifference.RIGHT:
				for (int j= rd.rightStart(); j < rd.rightEnd(); j++)
					os.println(y.get(j));
				break;
			case RangeDifference.LEFT:
				for (int j= rd.leftStart(); j < rd.leftEnd(); j++)
					os.println(m.get(j));
				break;
			case RangeDifference.CONFLICT:
			default:
				break;
			}
		}
	}
	public static void main(String[] args) {
		LineComparator a= new LineComparator("ancestor");
		LineComparator m= new LineComparator("mine");
		LineComparator y= new LineComparator("yours");
		update(System.out, a, m, y);
	}
}
Comment 3 Andre Weinand CLA 2004-03-09 06:13:22 EST
And this is the LineComparator:

import java.io.*;
import java.util.ArrayList;
import org.eclipse.rangedifferencer.IRangeComparator;

public class LineComparator implements IRangeComparator {
	
	private String[] fLines;
	
	public LineComparator(String path) {
		InputStream is= getClass().getResourceAsStream(path);
		BufferedReader br= new BufferedReader(new InputStreamReader(is));
		String line;
		ArrayList ar= new ArrayList();
		try {
			while ((line= br.readLine()) != null)
				ar.add(line);
		} catch (IOException e) {
		}
		try {
			is.close();
		} catch (IOException e1) {
		}
		fLines= (String[]) ar.toArray(new String[ar.size()]);
	}

	String getLine(int ix) {
		return fLines[ix];
	}

	public int getRangeCount() {
		return fLines.length;
	}

	public boolean rangesEqual(int thisIndex, IRangeComparator other, int otherIndex) {
		String s1= fLines[thisIndex];
		String s2= ((LineComparator)other).fLines[otherIndex];
		return s1.equals(s2);
	}

	public boolean skipRangeComparison(int length, int maxLength, IRangeComparator other) {
		return false;
	}
}
Comment 4 Jean-Michel Lemieux CLA 2004-03-09 10:21:14 EST
Good point, about using RangeComparator for headless compare/merge. I too 
noticed this and actually wrote something similar to understand that this was 
in fact possible. In the short term we could use this to prototype the silent 
merge support for text files.

Then what did you have in mind for the APIs in headless 
org.eclipse.compare.core? 

My initial thoughts were to have content-based access to a differencer and 
mergers so that clients don't have to guess the content-type and then figure 
out which diff/merge classes to use. This starts to look similar to the plug-
in content merge viewers in compare, but instead of registering a content 
merge viewer you contribute a differencer and a merger. Is this close to what 
you were thinking? Does it make any sense?
Comment 5 Andre Weinand CLA 2004-03-31 03:47:56 EST
> Then what did you have in mind for the APIs in headless org.eclipse.compare.core? 

In a first step I would just move the rangedifferencer package into a new project.
I'm not so sure what to do for the merge case, since the code is so simple.
In addition I would add some utilities that make it easier to use diff/merge on line-oriented files
(like the LineComparator above).

I don't think that I'm going to add a registry for diff/merge tools because compare.core does not 
(and cannot) define black-box differs/mergers. For example the output (result) of the rangedifferencer 
is a data structure that is tightly coupled to the rangedifferencer. It is not a general purpose /textual) 
format for communicating changes between diff and merge tools (because such a format is most likely 
dependent on the high-level tools).
Comment 6 Jean-Michel Lemieux CLA 2004-04-01 10:02:39 EST
I will attempt to further explain how the merge APIs would be useful and what I
think there general shape could be.

Let's say you are writting a repository provider and would like to support
visual merges and silent merges. For the visual merge case the repository
provider creates a IComparInput with ITypeElement instances that will be used to
determine the visual component used to display the compare/merge. The compare
plugin will determine the types in the compare input (e.g. by file extension,
aliases, or the new content type support) and the repository provider is
agnostic regarding the UI components that are used to display the results.

The headless APIs should support the case were the repository provider would 
provide a workflow that consists of trying to auto-merge first then only
displayed the compare editors if an auto-merge wasn't supported (e.g. conflicts,
no registered silent merge provider). So I agree that the diff results of a
silent merge are to domain specific, but an API to simply merge is not. The main
advantage of this kind of support in Eclipse is that tools can benefit from the
visual compare/merge and use the same tools that display the compare/merge to
actually perform the headless merge. The visual and silent results will be
consistent.

As a result, the silent merge API doesn't need to return an abstract diff model
but instead be spec'd to find the merge provider and perform the merge. If a
merge is not possible (e.g. conflict) then simply return indicating the reason
for not being able to perform the merge.
Comment 7 Andre Weinand CLA 2004-04-02 03:13:28 EST
So the Headless Compare/Merge seems to be just a registry for automergers (and not a registry for 
separate comparers and mergers)? 

And the API would be something like:
    OutputStream automerge(IEncodedStorage ancestor, IEncodedStorage left, IEncodedStorage right)
(and some way to return an IStatus and pass in an IProgressMonitor).

Is there a need to have a Core/UI split (that is having a separate plugin for the automerge registry),
or could that registry live in the existing compare plugin?

Will CVS make use of automergers?
Comment 8 Jean-Michel Lemieux CLA 2004-04-02 08:59:15 EST
> Is there a need to have a Core/UI split

That's a good question. For CVS and ClearCase the answer is no. In the
foreseeable future those plug-ins won't be run in a platform that doesn't have
the SDK installed. And even if we provide a headless application (e.g. quick way
of invoking the merge without loading the workbench), the UI plug-ins would be
available. 

Also, if the merge support was added to the current compare plug-in, could the
UI references be made optional?

However, from a design perspective if the merge support doesn't have any UI
dependencies than it may be cleaner to make the split now? If you do choose to
split it would be for future usages that we haven't considered yet.
Comment 9 Andre Weinand CLA 2004-04-08 11:36:13 EDT
Jean-Michel, could you please comment on the proposed API from comment 7?
I'm most interested in understanding what the result of an automerge would be.

In addition what "UI references" are you refering to?
Comment 10 Jean-Michel Lemieux CLA 2004-04-08 12:29:58 EDT
> In addition what "UI references" are you refering to?

The imports in the o.e.compare manifest.

> Comment on the proposed merge API

The result would be an output stream.

IStatus automerge(OutputStream result, IEncodedStorage ancestor, IEncodedStorage
left, IEncodedStorage right, IProgressMonitor monitor);
Comment 11 Andre Weinand CLA 2004-04-20 11:35:50 EDT
For I20040420:
- added new interface org.eclipse.compare.IStreamMerger
- added new extension point org.eclipse.compare.streamMergers
- new methods on org.eclipse.compare.CompareUI
       IStreamMerger createStreamMerger(String type)
       IStreamMerger createStreamMerger(IContentType type)
- a default TextStreamMerger has been registered for text content types