View Javadoc
1   /*
2    * Copyright (C) 2008-2013, Google Inc.
3    * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  
45  package org.eclipse.jgit.merge;
46  
47  import java.io.IOException;
48  import java.text.MessageFormat;
49  
50  import org.eclipse.jgit.annotations.Nullable;
51  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
52  import org.eclipse.jgit.errors.NoMergeBaseException;
53  import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
54  import org.eclipse.jgit.internal.JGitText;
55  import org.eclipse.jgit.lib.AnyObjectId;
56  import org.eclipse.jgit.lib.NullProgressMonitor;
57  import org.eclipse.jgit.lib.ObjectId;
58  import org.eclipse.jgit.lib.ObjectInserter;
59  import org.eclipse.jgit.lib.ObjectReader;
60  import org.eclipse.jgit.lib.ProgressMonitor;
61  import org.eclipse.jgit.lib.Repository;
62  import org.eclipse.jgit.revwalk.RevCommit;
63  import org.eclipse.jgit.revwalk.RevObject;
64  import org.eclipse.jgit.revwalk.RevTree;
65  import org.eclipse.jgit.revwalk.RevWalk;
66  import org.eclipse.jgit.revwalk.filter.RevFilter;
67  import org.eclipse.jgit.treewalk.AbstractTreeIterator;
68  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
69  
70  /**
71   * Instance of a specific {@link org.eclipse.jgit.merge.MergeStrategy} for a
72   * single {@link org.eclipse.jgit.lib.Repository}.
73   */
74  public abstract class Merger {
75  	/**
76  	 * The repository this merger operates on.
77  	 * <p>
78  	 * Null if and only if the merger was constructed with {@link
79  	 * #Merger(ObjectInserter)}. Callers that want to assume the repo is not null
80  	 * (e.g. because of a previous check that the merger is not in-core) may use
81  	 * {@link #nonNullRepo()}.
82  	 */
83  	@Nullable
84  	protected final Repository db;
85  
86  	/** Reader to support {@link #walk} and other object loading. */
87  	protected ObjectReader reader;
88  
89  	/** A RevWalk for computing merge bases, or listing incoming commits. */
90  	protected RevWalk walk;
91  
92  	private ObjectInserter inserter;
93  
94  	/** The original objects supplied in the merge; this can be any tree-ish. */
95  	protected RevObject[] sourceObjects;
96  
97  	/** If {@link #sourceObjects}[i] is a commit, this is the commit. */
98  	protected RevCommit[] sourceCommits;
99  
100 	/** The trees matching every entry in {@link #sourceObjects}. */
101 	protected RevTree[] sourceTrees;
102 
103 	/**
104 	 * A progress monitor.
105 	 *
106 	 * @since 4.2
107 	 */
108 	protected ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
109 
110 	/**
111 	 * Create a new merge instance for a repository.
112 	 *
113 	 * @param local
114 	 *            the repository this merger will read and write data on.
115 	 */
116 	protected Merger(Repository local) {
117 		if (local == null) {
118 			throw new NullPointerException(JGitText.get().repositoryIsRequired);
119 		}
120 		db = local;
121 		inserter = local.newObjectInserter();
122 		reader = inserter.newReader();
123 		walk = new RevWalk(reader);
124 	}
125 
126 	/**
127 	 * Create a new in-core merge instance from an inserter.
128 	 *
129 	 * @param oi
130 	 *            the inserter to write objects to. Will be closed at the
131 	 *            conclusion of {@code merge}, unless {@code flush} is false.
132 	 * @since 4.8
133 	 */
134 	protected Merger(ObjectInserter oi) {
135 		db = null;
136 		inserter = oi;
137 		reader = oi.newReader();
138 		walk = new RevWalk(reader);
139 	}
140 
141 	/**
142 	 * Get the repository this merger operates on.
143 	 *
144 	 * @return the repository this merger operates on.
145 	 */
146 	@Nullable
147 	public Repository getRepository() {
148 		return db;
149 	}
150 
151 	/**
152 	 * Get non-null repository instance
153 	 *
154 	 * @return non-null repository instance
155 	 * @throws java.lang.NullPointerException
156 	 *             if the merger was constructed without a repository.
157 	 * @since 4.8
158 	 */
159 	protected Repository nonNullRepo() {
160 		if (db == null) {
161 			throw new NullPointerException(JGitText.get().repositoryIsRequired);
162 		}
163 		return db;
164 	}
165 
166 	/**
167 	 * Get an object writer to create objects, writing objects to
168 	 * {@link #getRepository()}
169 	 *
170 	 * @return an object writer to create objects, writing objects to
171 	 *         {@link #getRepository()} (if a repository was provided).
172 	 */
173 	public ObjectInserter getObjectInserter() {
174 		return inserter;
175 	}
176 
177 	/**
178 	 * Set the inserter this merger will use to create objects.
179 	 * <p>
180 	 * If an inserter was already set on this instance (such as by a prior set,
181 	 * or a prior call to {@link #getObjectInserter()}), the prior inserter as
182 	 * well as the in-progress walk will be released.
183 	 *
184 	 * @param oi
185 	 *            the inserter instance to use. Must be associated with the
186 	 *            repository instance returned by {@link #getRepository()} (if a
187 	 *            repository was provided). Will be closed at the conclusion of
188 	 *            {@code merge}, unless {@code flush} is false.
189 	 */
190 	public void setObjectInserter(ObjectInserter oi) {
191 		walk.close();
192 		reader.close();
193 		inserter.close();
194 		inserter = oi;
195 		reader = oi.newReader();
196 		walk = new RevWalk(reader);
197 	}
198 
199 	/**
200 	 * Merge together two or more tree-ish objects.
201 	 * <p>
202 	 * Any tree-ish may be supplied as inputs. Commits and/or tags pointing at
203 	 * trees or commits may be passed as input objects.
204 	 *
205 	 * @param tips
206 	 *            source trees to be combined together. The merge base is not
207 	 *            included in this set.
208 	 * @return true if the merge was completed without conflicts; false if the
209 	 *         merge strategy cannot handle this merge or there were conflicts
210 	 *         preventing it from automatically resolving all paths.
211 	 * @throws IncorrectObjectTypeException
212 	 *             one of the input objects is not a commit, but the strategy
213 	 *             requires it to be a commit.
214 	 * @throws java.io.IOException
215 	 *             one or more sources could not be read, or outputs could not
216 	 *             be written to the Repository.
217 	 */
218 	public boolean merge(AnyObjectId... tips) throws IOException {
219 		return merge(true, tips);
220 	}
221 
222 	/**
223 	 * Merge together two or more tree-ish objects.
224 	 * <p>
225 	 * Any tree-ish may be supplied as inputs. Commits and/or tags pointing at
226 	 * trees or commits may be passed as input objects.
227 	 *
228 	 * @since 3.5
229 	 * @param flush
230 	 *            whether to flush and close the underlying object inserter when
231 	 *            finished to store any content-merged blobs and virtual merged
232 	 *            bases; if false, callers are responsible for flushing.
233 	 * @param tips
234 	 *            source trees to be combined together. The merge base is not
235 	 *            included in this set.
236 	 * @return true if the merge was completed without conflicts; false if the
237 	 *         merge strategy cannot handle this merge or there were conflicts
238 	 *         preventing it from automatically resolving all paths.
239 	 * @throws IncorrectObjectTypeException
240 	 *             one of the input objects is not a commit, but the strategy
241 	 *             requires it to be a commit.
242 	 * @throws java.io.IOException
243 	 *             one or more sources could not be read, or outputs could not
244 	 *             be written to the Repository.
245 	 */
246 	public boolean merge(boolean flush, AnyObjectId... tips)
247 			throws IOException {
248 		sourceObjects = new RevObject[tips.length];
249 		for (int i = 0; i < tips.length; i++)
250 			sourceObjects[i] = walk.parseAny(tips[i]);
251 
252 		sourceCommits = new RevCommit[sourceObjects.length];
253 		for (int i = 0; i < sourceObjects.length; i++) {
254 			try {
255 				sourceCommits[i] = walk.parseCommit(sourceObjects[i]);
256 			} catch (IncorrectObjectTypeException err) {
257 				sourceCommits[i] = null;
258 			}
259 		}
260 
261 		sourceTrees = new RevTree[sourceObjects.length];
262 		for (int i = 0; i < sourceObjects.length; i++)
263 			sourceTrees[i] = walk.parseTree(sourceObjects[i]);
264 
265 		try {
266 			boolean ok = mergeImpl();
267 			if (ok && flush)
268 				inserter.flush();
269 			return ok;
270 		} finally {
271 			if (flush)
272 				inserter.close();
273 			reader.close();
274 		}
275 	}
276 
277 	/**
278 	 * Get the ID of the commit that was used as merge base for merging
279 	 *
280 	 * @return the ID of the commit that was used as merge base for merging, or
281 	 *         null if no merge base was used or it was set manually
282 	 * @since 3.2
283 	 */
284 	public abstract ObjectId getBaseCommitId();
285 
286 	/**
287 	 * Return the merge base of two commits.
288 	 *
289 	 * @param a
290 	 *            the first commit in {@link #sourceObjects}.
291 	 * @param b
292 	 *            the second commit in {@link #sourceObjects}.
293 	 * @return the merge base of two commits
294 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
295 	 *             one of the input objects is not a commit.
296 	 * @throws java.io.IOException
297 	 *             objects are missing or multiple merge bases were found.
298 	 * @since 3.0
299 	 */
300 	protected RevCommit getBaseCommit(RevCommit a, RevCommit b)
301 			throws IncorrectObjectTypeException, IOException {
302 		walk.reset();
303 		walk.setRevFilter(RevFilter.MERGE_BASE);
304 		walk.markStart(a);
305 		walk.markStart(b);
306 		final RevCommit base = walk.next();
307 		if (base == null)
308 			return null;
309 		final RevCommit base2 = walk.next();
310 		if (base2 != null) {
311 			throw new NoMergeBaseException(
312 					MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED,
313 					MessageFormat.format(
314 					JGitText.get().multipleMergeBasesFor, a.name(), b.name(),
315 					base.name(), base2.name()));
316 		}
317 		return base;
318 	}
319 
320 	/**
321 	 * Open an iterator over a tree.
322 	 *
323 	 * @param treeId
324 	 *            the tree to scan; must be a tree (not a treeish).
325 	 * @return an iterator for the tree.
326 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
327 	 *             the input object is not a tree.
328 	 * @throws java.io.IOException
329 	 *             the tree object is not found or cannot be read.
330 	 */
331 	protected AbstractTreeIterator openTree(AnyObjectId treeId)
332 			throws IncorrectObjectTypeException, IOException {
333 		return new CanonicalTreeParser(null, reader, treeId);
334 	}
335 
336 	/**
337 	 * Execute the merge.
338 	 * <p>
339 	 * This method is called from {@link #merge(AnyObjectId[])} after the
340 	 * {@link #sourceObjects}, {@link #sourceCommits} and {@link #sourceTrees}
341 	 * have been populated.
342 	 *
343 	 * @return true if the merge was completed without conflicts; false if the
344 	 *         merge strategy cannot handle this merge or there were conflicts
345 	 *         preventing it from automatically resolving all paths.
346 	 * @throws IncorrectObjectTypeException
347 	 *             one of the input objects is not a commit, but the strategy
348 	 *             requires it to be a commit.
349 	 * @throws java.io.IOException
350 	 *             one or more sources could not be read, or outputs could not
351 	 *             be written to the Repository.
352 	 */
353 	protected abstract boolean mergeImpl() throws IOException;
354 
355 	/**
356 	 * Get resulting tree.
357 	 *
358 	 * @return resulting tree, if {@link #merge(AnyObjectId[])} returned true.
359 	 */
360 	public abstract ObjectId getResultTreeId();
361 
362 	/**
363 	 * Set a progress monitor.
364 	 *
365 	 * @param monitor
366 	 *            Monitor to use, can be null to indicate no progress reporting
367 	 *            is desired.
368 	 * @since 4.2
369 	 */
370 	public void setProgressMonitor(ProgressMonitor monitor) {
371 		if (monitor == null) {
372 			this.monitor = NullProgressMonitor.INSTANCE;
373 		} else {
374 			this.monitor = monitor;
375 		}
376 	}
377 }