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 }