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 }