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