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 RevCommit getBaseCommit(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 }