You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MergeCommand.java 20KB

Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
6 years ago
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
6 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /*
  2. * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  3. * Copyright (C) 2010, 2014, Stefan Lay <stefan.lay@sap.com>
  4. * Copyright (C) 2016, 2021 Laurent Delaigue <laurent.delaigue@obeo.fr> and others
  5. *
  6. * This program and the accompanying materials are made available under the
  7. * terms of the Eclipse Distribution License v. 1.0 which is available at
  8. * https://www.eclipse.org/org/documents/edl-v10.php.
  9. *
  10. * SPDX-License-Identifier: BSD-3-Clause
  11. */
  12. package org.eclipse.jgit.api;
  13. import java.io.IOException;
  14. import java.text.MessageFormat;
  15. import java.util.Arrays;
  16. import java.util.Collections;
  17. import java.util.LinkedList;
  18. import java.util.List;
  19. import java.util.Locale;
  20. import java.util.Map;
  21. import org.eclipse.jgit.annotations.Nullable;
  22. import org.eclipse.jgit.api.MergeResult.MergeStatus;
  23. import org.eclipse.jgit.api.errors.CheckoutConflictException;
  24. import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
  25. import org.eclipse.jgit.api.errors.GitAPIException;
  26. import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
  27. import org.eclipse.jgit.api.errors.JGitInternalException;
  28. import org.eclipse.jgit.api.errors.NoHeadException;
  29. import org.eclipse.jgit.api.errors.NoMessageException;
  30. import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
  31. import org.eclipse.jgit.dircache.DirCacheCheckout;
  32. import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
  33. import org.eclipse.jgit.internal.JGitText;
  34. import org.eclipse.jgit.lib.AnyObjectId;
  35. import org.eclipse.jgit.lib.Config.ConfigEnum;
  36. import org.eclipse.jgit.lib.Constants;
  37. import org.eclipse.jgit.lib.NullProgressMonitor;
  38. import org.eclipse.jgit.lib.ObjectId;
  39. import org.eclipse.jgit.lib.ObjectIdRef;
  40. import org.eclipse.jgit.lib.ProgressMonitor;
  41. import org.eclipse.jgit.lib.Ref;
  42. import org.eclipse.jgit.lib.Ref.Storage;
  43. import org.eclipse.jgit.lib.RefUpdate;
  44. import org.eclipse.jgit.lib.RefUpdate.Result;
  45. import org.eclipse.jgit.lib.Repository;
  46. import org.eclipse.jgit.merge.ContentMergeStrategy;
  47. import org.eclipse.jgit.merge.MergeConfig;
  48. import org.eclipse.jgit.merge.MergeMessageFormatter;
  49. import org.eclipse.jgit.merge.MergeStrategy;
  50. import org.eclipse.jgit.merge.Merger;
  51. import org.eclipse.jgit.merge.ResolveMerger;
  52. import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
  53. import org.eclipse.jgit.merge.SquashMessageFormatter;
  54. import org.eclipse.jgit.revwalk.RevCommit;
  55. import org.eclipse.jgit.revwalk.RevWalk;
  56. import org.eclipse.jgit.revwalk.RevWalkUtils;
  57. import org.eclipse.jgit.treewalk.FileTreeIterator;
  58. import org.eclipse.jgit.util.StringUtils;
  59. /**
  60. * A class used to execute a {@code Merge} command. It has setters for all
  61. * supported options and arguments of this command and a {@link #call()} method
  62. * to finally execute the command. Each instance of this class should only be
  63. * used for one invocation of the command (means: one call to {@link #call()})
  64. *
  65. * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-merge.html"
  66. * >Git documentation about Merge</a>
  67. */
  68. public class MergeCommand extends GitCommand<MergeResult> {
  69. private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE;
  70. private ContentMergeStrategy contentStrategy;
  71. private List<Ref> commits = new LinkedList<>();
  72. private Boolean squash;
  73. private FastForwardMode fastForwardMode;
  74. private String message;
  75. private boolean insertChangeId;
  76. private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
  77. /**
  78. * The modes available for fast forward merges corresponding to the
  79. * <code>--ff</code>, <code>--no-ff</code> and <code>--ff-only</code>
  80. * options under <code>branch.&lt;name&gt;.mergeoptions</code>.
  81. */
  82. public enum FastForwardMode implements ConfigEnum {
  83. /**
  84. * Corresponds to the default --ff option (for a fast forward update the
  85. * branch pointer only).
  86. */
  87. FF,
  88. /**
  89. * Corresponds to the --no-ff option (create a merge commit even for a
  90. * fast forward).
  91. */
  92. NO_FF,
  93. /**
  94. * Corresponds to the --ff-only option (abort unless the merge is a fast
  95. * forward).
  96. */
  97. FF_ONLY;
  98. @Override
  99. public String toConfigValue() {
  100. return "--" + name().toLowerCase(Locale.ROOT).replace('_', '-'); //$NON-NLS-1$
  101. }
  102. @Override
  103. public boolean matchConfigValue(String in) {
  104. if (StringUtils.isEmptyOrNull(in))
  105. return false;
  106. if (!in.startsWith("--")) //$NON-NLS-1$
  107. return false;
  108. return name().equalsIgnoreCase(in.substring(2).replace('-', '_'));
  109. }
  110. /**
  111. * The modes available for fast forward merges corresponding to the
  112. * options under <code>merge.ff</code>.
  113. */
  114. public enum Merge {
  115. /**
  116. * {@link FastForwardMode#FF}.
  117. */
  118. TRUE,
  119. /**
  120. * {@link FastForwardMode#NO_FF}.
  121. */
  122. FALSE,
  123. /**
  124. * {@link FastForwardMode#FF_ONLY}.
  125. */
  126. ONLY;
  127. /**
  128. * Map from <code>FastForwardMode</code> to
  129. * <code>FastForwardMode.Merge</code>.
  130. *
  131. * @param ffMode
  132. * the <code>FastForwardMode</code> value to be mapped
  133. * @return the mapped <code>FastForwardMode.Merge</code> value
  134. */
  135. public static Merge valueOf(FastForwardMode ffMode) {
  136. switch (ffMode) {
  137. case NO_FF:
  138. return FALSE;
  139. case FF_ONLY:
  140. return ONLY;
  141. default:
  142. return TRUE;
  143. }
  144. }
  145. }
  146. /**
  147. * Map from <code>FastForwardMode.Merge</code> to
  148. * <code>FastForwardMode</code>.
  149. *
  150. * @param ffMode
  151. * the <code>FastForwardMode.Merge</code> value to be mapped
  152. * @return the mapped <code>FastForwardMode</code> value
  153. */
  154. public static FastForwardMode valueOf(FastForwardMode.Merge ffMode) {
  155. switch (ffMode) {
  156. case FALSE:
  157. return NO_FF;
  158. case ONLY:
  159. return FF_ONLY;
  160. default:
  161. return FF;
  162. }
  163. }
  164. }
  165. private Boolean commit;
  166. /**
  167. * Constructor for MergeCommand.
  168. *
  169. * @param repo
  170. * the {@link org.eclipse.jgit.lib.Repository}
  171. */
  172. protected MergeCommand(Repository repo) {
  173. super(repo);
  174. }
  175. /**
  176. * {@inheritDoc}
  177. * <p>
  178. * Execute the {@code Merge} command with all the options and parameters
  179. * collected by the setter methods (e.g. {@link #include(Ref)}) of this
  180. * class. Each instance of this class should only be used for one invocation
  181. * of the command. Don't call this method twice on an instance.
  182. */
  183. @Override
  184. @SuppressWarnings("boxing")
  185. public MergeResult call() throws GitAPIException, NoHeadException,
  186. ConcurrentRefUpdateException, CheckoutConflictException,
  187. InvalidMergeHeadsException, WrongRepositoryStateException, NoMessageException {
  188. checkCallable();
  189. fallBackToConfiguration();
  190. checkParameters();
  191. DirCacheCheckout dco = null;
  192. try (RevWalk revWalk = new RevWalk(repo)) {
  193. Ref head = repo.exactRef(Constants.HEAD);
  194. if (head == null)
  195. throw new NoHeadException(
  196. JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
  197. StringBuilder refLogMessage = new StringBuilder("merge "); //$NON-NLS-1$
  198. // Check for FAST_FORWARD, ALREADY_UP_TO_DATE
  199. // we know for now there is only one commit
  200. Ref ref = commits.get(0);
  201. refLogMessage.append(ref.getName());
  202. // handle annotated tags
  203. ref = repo.getRefDatabase().peel(ref);
  204. ObjectId objectId = ref.getPeeledObjectId();
  205. if (objectId == null)
  206. objectId = ref.getObjectId();
  207. RevCommit srcCommit = revWalk.lookupCommit(objectId);
  208. ObjectId headId = head.getObjectId();
  209. if (headId == null) {
  210. revWalk.parseHeaders(srcCommit);
  211. dco = new DirCacheCheckout(repo,
  212. repo.lockDirCache(), srcCommit.getTree());
  213. dco.setFailOnConflict(true);
  214. dco.setProgressMonitor(monitor);
  215. dco.checkout();
  216. RefUpdate refUpdate = repo
  217. .updateRef(head.getTarget().getName());
  218. refUpdate.setNewObjectId(objectId);
  219. refUpdate.setExpectedOldObjectId(null);
  220. refUpdate.setRefLogMessage("initial pull", false); //$NON-NLS-1$
  221. if (refUpdate.update() != Result.NEW)
  222. throw new NoHeadException(
  223. JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
  224. setCallable(false);
  225. return new MergeResult(srcCommit, srcCommit, new ObjectId[] {
  226. null, srcCommit }, MergeStatus.FAST_FORWARD,
  227. mergeStrategy, null, null);
  228. }
  229. RevCommit headCommit = revWalk.lookupCommit(headId);
  230. if (revWalk.isMergedInto(srcCommit, headCommit)) {
  231. setCallable(false);
  232. return new MergeResult(headCommit, srcCommit, new ObjectId[] {
  233. headCommit, srcCommit },
  234. MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy, null, null);
  235. } else if (revWalk.isMergedInto(headCommit, srcCommit)
  236. && fastForwardMode != FastForwardMode.NO_FF) {
  237. // FAST_FORWARD detected: skip doing a real merge but only
  238. // update HEAD
  239. refLogMessage.append(": " + MergeStatus.FAST_FORWARD); //$NON-NLS-1$
  240. dco = new DirCacheCheckout(repo,
  241. headCommit.getTree(), repo.lockDirCache(),
  242. srcCommit.getTree());
  243. dco.setProgressMonitor(monitor);
  244. dco.setFailOnConflict(true);
  245. dco.checkout();
  246. String msg = null;
  247. ObjectId newHead, base = null;
  248. MergeStatus mergeStatus = null;
  249. if (!squash) {
  250. updateHead(refLogMessage, srcCommit, headId);
  251. newHead = base = srcCommit;
  252. mergeStatus = MergeStatus.FAST_FORWARD;
  253. } else {
  254. msg = JGitText.get().squashCommitNotUpdatingHEAD;
  255. newHead = base = headId;
  256. mergeStatus = MergeStatus.FAST_FORWARD_SQUASHED;
  257. List<RevCommit> squashedCommits = RevWalkUtils.find(
  258. revWalk, srcCommit, headCommit);
  259. String squashMessage = new SquashMessageFormatter().format(
  260. squashedCommits, head);
  261. repo.writeSquashCommitMsg(squashMessage);
  262. }
  263. setCallable(false);
  264. return new MergeResult(newHead, base, new ObjectId[] {
  265. headCommit, srcCommit }, mergeStatus, mergeStrategy,
  266. null, msg);
  267. } else {
  268. if (fastForwardMode == FastForwardMode.FF_ONLY) {
  269. return new MergeResult(headCommit, srcCommit,
  270. new ObjectId[] { headCommit, srcCommit },
  271. MergeStatus.ABORTED, mergeStrategy, null, null);
  272. }
  273. String mergeMessage = ""; //$NON-NLS-1$
  274. if (!squash) {
  275. if (message != null)
  276. mergeMessage = message;
  277. else
  278. mergeMessage = new MergeMessageFormatter().format(
  279. commits, head);
  280. repo.writeMergeCommitMsg(mergeMessage);
  281. repo.writeMergeHeads(Arrays.asList(ref.getObjectId()));
  282. } else {
  283. List<RevCommit> squashedCommits = RevWalkUtils.find(
  284. revWalk, srcCommit, headCommit);
  285. String squashMessage = new SquashMessageFormatter().format(
  286. squashedCommits, head);
  287. repo.writeSquashCommitMsg(squashMessage);
  288. }
  289. Merger merger = mergeStrategy.newMerger(repo);
  290. merger.setProgressMonitor(monitor);
  291. boolean noProblems;
  292. Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults = null;
  293. Map<String, MergeFailureReason> failingPaths = null;
  294. List<String> unmergedPaths = null;
  295. if (merger instanceof ResolveMerger) {
  296. ResolveMerger resolveMerger = (ResolveMerger) merger;
  297. resolveMerger.setContentMergeStrategy(contentStrategy);
  298. resolveMerger.setCommitNames(new String[] {
  299. "BASE", "HEAD", ref.getName() }); //$NON-NLS-1$ //$NON-NLS-2$
  300. resolveMerger.setWorkingTreeIterator(new FileTreeIterator(repo));
  301. noProblems = merger.merge(headCommit, srcCommit);
  302. lowLevelResults = resolveMerger
  303. .getMergeResults();
  304. failingPaths = resolveMerger.getFailingPaths();
  305. unmergedPaths = resolveMerger.getUnmergedPaths();
  306. if (!resolveMerger.getModifiedFiles().isEmpty()) {
  307. repo.fireEvent(new WorkingTreeModifiedEvent(
  308. resolveMerger.getModifiedFiles(), null));
  309. }
  310. } else
  311. noProblems = merger.merge(headCommit, srcCommit);
  312. refLogMessage.append(": Merge made by "); //$NON-NLS-1$
  313. if (!revWalk.isMergedInto(headCommit, srcCommit))
  314. refLogMessage.append(mergeStrategy.getName());
  315. else
  316. refLogMessage.append("recursive"); //$NON-NLS-1$
  317. refLogMessage.append('.');
  318. if (noProblems) {
  319. dco = new DirCacheCheckout(repo,
  320. headCommit.getTree(), repo.lockDirCache(),
  321. merger.getResultTreeId());
  322. dco.setFailOnConflict(true);
  323. dco.setProgressMonitor(monitor);
  324. dco.checkout();
  325. String msg = null;
  326. ObjectId newHeadId = null;
  327. MergeStatus mergeStatus = null;
  328. if (!commit && squash) {
  329. mergeStatus = MergeStatus.MERGED_SQUASHED_NOT_COMMITTED;
  330. }
  331. if (!commit && !squash) {
  332. mergeStatus = MergeStatus.MERGED_NOT_COMMITTED;
  333. }
  334. if (commit && !squash) {
  335. try (Git git = new Git(getRepository())) {
  336. newHeadId = git.commit()
  337. .setReflogComment(refLogMessage.toString())
  338. .setInsertChangeId(insertChangeId)
  339. .call().getId();
  340. }
  341. mergeStatus = MergeStatus.MERGED;
  342. getRepository().autoGC(monitor);
  343. }
  344. if (commit && squash) {
  345. msg = JGitText.get().squashCommitNotUpdatingHEAD;
  346. newHeadId = headCommit.getId();
  347. mergeStatus = MergeStatus.MERGED_SQUASHED;
  348. }
  349. return new MergeResult(newHeadId, null,
  350. new ObjectId[] { headCommit.getId(),
  351. srcCommit.getId() }, mergeStatus,
  352. mergeStrategy, null, msg);
  353. }
  354. if (failingPaths != null) {
  355. repo.writeMergeCommitMsg(null);
  356. repo.writeMergeHeads(null);
  357. return new MergeResult(null, merger.getBaseCommitId(),
  358. new ObjectId[] { headCommit.getId(),
  359. srcCommit.getId() },
  360. MergeStatus.FAILED, mergeStrategy, lowLevelResults,
  361. failingPaths, null);
  362. }
  363. String mergeMessageWithConflicts = new MergeMessageFormatter()
  364. .formatWithConflicts(mergeMessage, unmergedPaths);
  365. repo.writeMergeCommitMsg(mergeMessageWithConflicts);
  366. return new MergeResult(null, merger.getBaseCommitId(),
  367. new ObjectId[] { headCommit.getId(),
  368. srcCommit.getId() },
  369. MergeStatus.CONFLICTING, mergeStrategy, lowLevelResults,
  370. null);
  371. }
  372. } catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
  373. List<String> conflicts = (dco == null) ? Collections
  374. .<String> emptyList() : dco.getConflicts();
  375. throw new CheckoutConflictException(conflicts, e);
  376. } catch (IOException e) {
  377. throw new JGitInternalException(
  378. MessageFormat.format(
  379. JGitText.get().exceptionCaughtDuringExecutionOfMergeCommand,
  380. e), e);
  381. }
  382. }
  383. private void checkParameters() throws InvalidMergeHeadsException {
  384. if (squash.booleanValue() && fastForwardMode == FastForwardMode.NO_FF) {
  385. throw new JGitInternalException(
  386. JGitText.get().cannotCombineSquashWithNoff);
  387. }
  388. if (commits.size() != 1)
  389. throw new InvalidMergeHeadsException(
  390. commits.isEmpty() ? JGitText.get().noMergeHeadSpecified
  391. : MessageFormat.format(
  392. JGitText.get().mergeStrategyDoesNotSupportHeads,
  393. mergeStrategy.getName(),
  394. Integer.valueOf(commits.size())));
  395. }
  396. /**
  397. * Use values from the configuration if they have not been explicitly
  398. * defined via the setters
  399. */
  400. private void fallBackToConfiguration() {
  401. MergeConfig config = MergeConfig.getConfigForCurrentBranch(repo);
  402. if (squash == null)
  403. squash = Boolean.valueOf(config.isSquash());
  404. if (commit == null)
  405. commit = Boolean.valueOf(config.isCommit());
  406. if (fastForwardMode == null)
  407. fastForwardMode = config.getFastForwardMode();
  408. }
  409. private void updateHead(StringBuilder refLogMessage, ObjectId newHeadId,
  410. ObjectId oldHeadID) throws IOException,
  411. ConcurrentRefUpdateException {
  412. RefUpdate refUpdate = repo.updateRef(Constants.HEAD);
  413. refUpdate.setNewObjectId(newHeadId);
  414. refUpdate.setRefLogMessage(refLogMessage.toString(), false);
  415. refUpdate.setExpectedOldObjectId(oldHeadID);
  416. Result rc = refUpdate.update();
  417. switch (rc) {
  418. case NEW:
  419. case FAST_FORWARD:
  420. return;
  421. case REJECTED:
  422. case LOCK_FAILURE:
  423. throw new ConcurrentRefUpdateException(
  424. JGitText.get().couldNotLockHEAD, refUpdate.getRef(), rc);
  425. default:
  426. throw new JGitInternalException(MessageFormat.format(
  427. JGitText.get().updatingRefFailed, Constants.HEAD,
  428. newHeadId.toString(), rc));
  429. }
  430. }
  431. /**
  432. * Set merge strategy
  433. *
  434. * @param mergeStrategy
  435. * the {@link org.eclipse.jgit.merge.MergeStrategy} to be used
  436. * @return {@code this}
  437. */
  438. public MergeCommand setStrategy(MergeStrategy mergeStrategy) {
  439. checkCallable();
  440. this.mergeStrategy = mergeStrategy;
  441. return this;
  442. }
  443. /**
  444. * Sets the content merge strategy to use if the
  445. * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or
  446. * "recursive".
  447. *
  448. * @param strategy
  449. * the {@link ContentMergeStrategy} to be used
  450. * @return {@code this}
  451. * @since 5.12
  452. */
  453. public MergeCommand setContentMergeStrategy(ContentMergeStrategy strategy) {
  454. checkCallable();
  455. this.contentStrategy = strategy;
  456. return this;
  457. }
  458. /**
  459. * Reference to a commit to be merged with the current head
  460. *
  461. * @param aCommit
  462. * a reference to a commit which is merged with the current head
  463. * @return {@code this}
  464. */
  465. public MergeCommand include(Ref aCommit) {
  466. checkCallable();
  467. commits.add(aCommit);
  468. return this;
  469. }
  470. /**
  471. * Id of a commit which is to be merged with the current head
  472. *
  473. * @param aCommit
  474. * the Id of a commit which is merged with the current head
  475. * @return {@code this}
  476. */
  477. public MergeCommand include(AnyObjectId aCommit) {
  478. return include(aCommit.getName(), aCommit);
  479. }
  480. /**
  481. * Include a commit
  482. *
  483. * @param name
  484. * a name of a {@code Ref} pointing to the commit
  485. * @param aCommit
  486. * the Id of a commit which is merged with the current head
  487. * @return {@code this}
  488. */
  489. public MergeCommand include(String name, AnyObjectId aCommit) {
  490. return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
  491. aCommit.copy()));
  492. }
  493. /**
  494. * If <code>true</code>, will prepare the next commit in working tree and
  495. * index as if a real merge happened, but do not make the commit or move the
  496. * HEAD. Otherwise, perform the merge and commit the result.
  497. * <p>
  498. * In case the merge was successful but this flag was set to
  499. * <code>true</code> a {@link org.eclipse.jgit.api.MergeResult} with status
  500. * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_SQUASHED} or
  501. * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#FAST_FORWARD_SQUASHED}
  502. * is returned.
  503. *
  504. * @param squash
  505. * whether to squash commits or not
  506. * @return {@code this}
  507. * @since 2.0
  508. */
  509. public MergeCommand setSquash(boolean squash) {
  510. checkCallable();
  511. this.squash = Boolean.valueOf(squash);
  512. return this;
  513. }
  514. /**
  515. * Sets the fast forward mode.
  516. *
  517. * @param fastForwardMode
  518. * corresponds to the --ff/--no-ff/--ff-only options. If
  519. * {@code null} use the value of the {@code merge.ff} option
  520. * configured in git config. If this option is not configured
  521. * --ff is the built-in default.
  522. * @return {@code this}
  523. * @since 2.2
  524. */
  525. public MergeCommand setFastForward(
  526. @Nullable FastForwardMode fastForwardMode) {
  527. checkCallable();
  528. this.fastForwardMode = fastForwardMode;
  529. return this;
  530. }
  531. /**
  532. * Controls whether the merge command should automatically commit after a
  533. * successful merge
  534. *
  535. * @param commit
  536. * <code>true</code> if this command should commit (this is the
  537. * default behavior). <code>false</code> if this command should
  538. * not commit. In case the merge was successful but this flag was
  539. * set to <code>false</code> a
  540. * {@link org.eclipse.jgit.api.MergeResult} with type
  541. * {@link org.eclipse.jgit.api.MergeResult} with status
  542. * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_NOT_COMMITTED}
  543. * is returned
  544. * @return {@code this}
  545. * @since 3.0
  546. */
  547. public MergeCommand setCommit(boolean commit) {
  548. this.commit = Boolean.valueOf(commit);
  549. return this;
  550. }
  551. /**
  552. * Set the commit message to be used for the merge commit (in case one is
  553. * created)
  554. *
  555. * @param message
  556. * the message to be used for the merge commit
  557. * @return {@code this}
  558. * @since 3.5
  559. */
  560. public MergeCommand setMessage(String message) {
  561. this.message = message;
  562. return this;
  563. }
  564. /**
  565. * If set to true a change id will be inserted into the commit message
  566. *
  567. * An existing change id is not replaced. An initial change id (I000...)
  568. * will be replaced by the change id.
  569. *
  570. * @param insertChangeId
  571. * whether to insert a change id
  572. * @return {@code this}
  573. * @since 5.0
  574. */
  575. public MergeCommand setInsertChangeId(boolean insertChangeId) {
  576. checkCallable();
  577. this.insertChangeId = insertChangeId;
  578. return this;
  579. }
  580. /**
  581. * The progress monitor associated with the diff operation. By default, this
  582. * is set to <code>NullProgressMonitor</code>
  583. *
  584. * @see NullProgressMonitor
  585. * @param monitor
  586. * A progress monitor
  587. * @return this instance
  588. * @since 4.2
  589. */
  590. public MergeCommand setProgressMonitor(ProgressMonitor monitor) {
  591. if (monitor == null) {
  592. monitor = NullProgressMonitor.INSTANCE;
  593. }
  594. this.monitor = monitor;
  595. return this;
  596. }
  597. }