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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  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, Laurent Delaigue <laurent.delaigue@obeo.fr>
  5. * and other copyright owners as documented in the project's IP log.
  6. *
  7. * This program and the accompanying materials are made available
  8. * under the terms of the Eclipse Distribution License v1.0 which
  9. * accompanies this distribution, is reproduced below, and is
  10. * available at http://www.eclipse.org/org/documents/edl-v10.php
  11. *
  12. * All rights reserved.
  13. *
  14. * Redistribution and use in source and binary forms, with or
  15. * without modification, are permitted provided that the following
  16. * conditions are met:
  17. *
  18. * - Redistributions of source code must retain the above copyright
  19. * notice, this list of conditions and the following disclaimer.
  20. *
  21. * - Redistributions in binary form must reproduce the above
  22. * copyright notice, this list of conditions and the following
  23. * disclaimer in the documentation and/or other materials provided
  24. * with the distribution.
  25. *
  26. * - Neither the name of the Eclipse Foundation, Inc. nor the
  27. * names of its contributors may be used to endorse or promote
  28. * products derived from this software without specific prior
  29. * written permission.
  30. *
  31. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  32. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  33. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  34. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  35. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  36. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  37. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  38. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  39. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  40. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  41. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  42. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  43. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. */
  45. package org.eclipse.jgit.api;
  46. import java.io.IOException;
  47. import java.text.MessageFormat;
  48. import java.util.Arrays;
  49. import java.util.Collections;
  50. import java.util.LinkedList;
  51. import java.util.List;
  52. import java.util.Locale;
  53. import java.util.Map;
  54. import org.eclipse.jgit.annotations.Nullable;
  55. import org.eclipse.jgit.api.MergeResult.MergeStatus;
  56. import org.eclipse.jgit.api.errors.CheckoutConflictException;
  57. import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
  58. import org.eclipse.jgit.api.errors.GitAPIException;
  59. import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
  60. import org.eclipse.jgit.api.errors.JGitInternalException;
  61. import org.eclipse.jgit.api.errors.NoHeadException;
  62. import org.eclipse.jgit.api.errors.NoMessageException;
  63. import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
  64. import org.eclipse.jgit.dircache.DirCacheCheckout;
  65. import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
  66. import org.eclipse.jgit.internal.JGitText;
  67. import org.eclipse.jgit.lib.AnyObjectId;
  68. import org.eclipse.jgit.lib.Config.ConfigEnum;
  69. import org.eclipse.jgit.lib.Constants;
  70. import org.eclipse.jgit.lib.NullProgressMonitor;
  71. import org.eclipse.jgit.lib.ObjectId;
  72. import org.eclipse.jgit.lib.ObjectIdRef;
  73. import org.eclipse.jgit.lib.ProgressMonitor;
  74. import org.eclipse.jgit.lib.Ref;
  75. import org.eclipse.jgit.lib.Ref.Storage;
  76. import org.eclipse.jgit.lib.RefUpdate;
  77. import org.eclipse.jgit.lib.RefUpdate.Result;
  78. import org.eclipse.jgit.lib.Repository;
  79. import org.eclipse.jgit.merge.MergeConfig;
  80. import org.eclipse.jgit.merge.MergeMessageFormatter;
  81. import org.eclipse.jgit.merge.MergeStrategy;
  82. import org.eclipse.jgit.merge.Merger;
  83. import org.eclipse.jgit.merge.ResolveMerger;
  84. import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
  85. import org.eclipse.jgit.merge.SquashMessageFormatter;
  86. import org.eclipse.jgit.revwalk.RevCommit;
  87. import org.eclipse.jgit.revwalk.RevWalk;
  88. import org.eclipse.jgit.revwalk.RevWalkUtils;
  89. import org.eclipse.jgit.treewalk.FileTreeIterator;
  90. import org.eclipse.jgit.util.StringUtils;
  91. /**
  92. * A class used to execute a {@code Merge} command. It has setters for all
  93. * supported options and arguments of this command and a {@link #call()} method
  94. * to finally execute the command. Each instance of this class should only be
  95. * used for one invocation of the command (means: one call to {@link #call()})
  96. *
  97. * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-merge.html"
  98. * >Git documentation about Merge</a>
  99. */
  100. public class MergeCommand extends GitCommand<MergeResult> {
  101. private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE;
  102. private List<Ref> commits = new LinkedList<>();
  103. private Boolean squash;
  104. private FastForwardMode fastForwardMode;
  105. private String message;
  106. private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
  107. /**
  108. * The modes available for fast forward merges corresponding to the
  109. * <code>--ff</code>, <code>--no-ff</code> and <code>--ff-only</code>
  110. * options under <code>branch.&lt;name&gt;.mergeoptions</code>.
  111. */
  112. public enum FastForwardMode implements ConfigEnum {
  113. /**
  114. * Corresponds to the default --ff option (for a fast forward update the
  115. * branch pointer only).
  116. */
  117. FF,
  118. /**
  119. * Corresponds to the --no-ff option (create a merge commit even for a
  120. * fast forward).
  121. */
  122. NO_FF,
  123. /**
  124. * Corresponds to the --ff-only option (abort unless the merge is a fast
  125. * forward).
  126. */
  127. FF_ONLY;
  128. @Override
  129. public String toConfigValue() {
  130. return "--" + name().toLowerCase(Locale.ROOT).replace('_', '-'); //$NON-NLS-1$
  131. }
  132. @Override
  133. public boolean matchConfigValue(String in) {
  134. if (StringUtils.isEmptyOrNull(in))
  135. return false;
  136. if (!in.startsWith("--")) //$NON-NLS-1$
  137. return false;
  138. return name().equalsIgnoreCase(in.substring(2).replace('-', '_'));
  139. }
  140. /**
  141. * The modes available for fast forward merges corresponding to the
  142. * options under <code>merge.ff</code>.
  143. */
  144. public enum Merge {
  145. /**
  146. * {@link FastForwardMode#FF}.
  147. */
  148. TRUE,
  149. /**
  150. * {@link FastForwardMode#NO_FF}.
  151. */
  152. FALSE,
  153. /**
  154. * {@link FastForwardMode#FF_ONLY}.
  155. */
  156. ONLY;
  157. /**
  158. * Map from <code>FastForwardMode</code> to
  159. * <code>FastForwardMode.Merge</code>.
  160. *
  161. * @param ffMode
  162. * the <code>FastForwardMode</code> value to be mapped
  163. * @return the mapped <code>FastForwardMode.Merge</code> value
  164. */
  165. public static Merge valueOf(FastForwardMode ffMode) {
  166. switch (ffMode) {
  167. case NO_FF:
  168. return FALSE;
  169. case FF_ONLY:
  170. return ONLY;
  171. default:
  172. return TRUE;
  173. }
  174. }
  175. }
  176. /**
  177. * Map from <code>FastForwardMode.Merge</code> to
  178. * <code>FastForwardMode</code>.
  179. *
  180. * @param ffMode
  181. * the <code>FastForwardMode.Merge</code> value to be mapped
  182. * @return the mapped <code>FastForwardMode</code> value
  183. */
  184. public static FastForwardMode valueOf(FastForwardMode.Merge ffMode) {
  185. switch (ffMode) {
  186. case FALSE:
  187. return NO_FF;
  188. case ONLY:
  189. return FF_ONLY;
  190. default:
  191. return FF;
  192. }
  193. }
  194. }
  195. private Boolean commit;
  196. /**
  197. * @param repo
  198. */
  199. protected MergeCommand(Repository repo) {
  200. super(repo);
  201. }
  202. /**
  203. * Executes the {@code Merge} command with all the options and parameters
  204. * collected by the setter methods (e.g. {@link #include(Ref)}) of this
  205. * class. Each instance of this class should only be used for one invocation
  206. * of the command. Don't call this method twice on an instance.
  207. *
  208. * @return the result of the merge
  209. */
  210. @Override
  211. @SuppressWarnings("boxing")
  212. public MergeResult call() throws GitAPIException, NoHeadException,
  213. ConcurrentRefUpdateException, CheckoutConflictException,
  214. InvalidMergeHeadsException, WrongRepositoryStateException, NoMessageException {
  215. checkCallable();
  216. fallBackToConfiguration();
  217. checkParameters();
  218. RevWalk revWalk = null;
  219. DirCacheCheckout dco = null;
  220. try {
  221. Ref head = repo.exactRef(Constants.HEAD);
  222. if (head == null)
  223. throw new NoHeadException(
  224. JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
  225. StringBuilder refLogMessage = new StringBuilder("merge "); //$NON-NLS-1$
  226. // Check for FAST_FORWARD, ALREADY_UP_TO_DATE
  227. revWalk = new RevWalk(repo);
  228. // we know for now there is only one commit
  229. Ref ref = commits.get(0);
  230. refLogMessage.append(ref.getName());
  231. // handle annotated tags
  232. ref = repo.peel(ref);
  233. ObjectId objectId = ref.getPeeledObjectId();
  234. if (objectId == null)
  235. objectId = ref.getObjectId();
  236. RevCommit srcCommit = revWalk.lookupCommit(objectId);
  237. ObjectId headId = head.getObjectId();
  238. if (headId == null) {
  239. revWalk.parseHeaders(srcCommit);
  240. dco = new DirCacheCheckout(repo,
  241. repo.lockDirCache(), srcCommit.getTree());
  242. dco.setFailOnConflict(true);
  243. dco.checkout();
  244. RefUpdate refUpdate = repo
  245. .updateRef(head.getTarget().getName());
  246. refUpdate.setNewObjectId(objectId);
  247. refUpdate.setExpectedOldObjectId(null);
  248. refUpdate.setRefLogMessage("initial pull", false); //$NON-NLS-1$
  249. if (refUpdate.update() != Result.NEW)
  250. throw new NoHeadException(
  251. JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
  252. setCallable(false);
  253. return new MergeResult(srcCommit, srcCommit, new ObjectId[] {
  254. null, srcCommit }, MergeStatus.FAST_FORWARD,
  255. mergeStrategy, null, null);
  256. }
  257. RevCommit headCommit = revWalk.lookupCommit(headId);
  258. if (revWalk.isMergedInto(srcCommit, headCommit)) {
  259. setCallable(false);
  260. return new MergeResult(headCommit, srcCommit, new ObjectId[] {
  261. headCommit, srcCommit },
  262. MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy, null, null);
  263. } else if (revWalk.isMergedInto(headCommit, srcCommit)
  264. && fastForwardMode != FastForwardMode.NO_FF) {
  265. // FAST_FORWARD detected: skip doing a real merge but only
  266. // update HEAD
  267. refLogMessage.append(": " + MergeStatus.FAST_FORWARD); //$NON-NLS-1$
  268. dco = new DirCacheCheckout(repo,
  269. headCommit.getTree(), repo.lockDirCache(),
  270. srcCommit.getTree());
  271. dco.setFailOnConflict(true);
  272. dco.checkout();
  273. String msg = null;
  274. ObjectId newHead, base = null;
  275. MergeStatus mergeStatus = null;
  276. if (!squash) {
  277. updateHead(refLogMessage, srcCommit, headId);
  278. newHead = base = srcCommit;
  279. mergeStatus = MergeStatus.FAST_FORWARD;
  280. } else {
  281. msg = JGitText.get().squashCommitNotUpdatingHEAD;
  282. newHead = base = headId;
  283. mergeStatus = MergeStatus.FAST_FORWARD_SQUASHED;
  284. List<RevCommit> squashedCommits = RevWalkUtils.find(
  285. revWalk, srcCommit, headCommit);
  286. String squashMessage = new SquashMessageFormatter().format(
  287. squashedCommits, head);
  288. repo.writeSquashCommitMsg(squashMessage);
  289. }
  290. setCallable(false);
  291. return new MergeResult(newHead, base, new ObjectId[] {
  292. headCommit, srcCommit }, mergeStatus, mergeStrategy,
  293. null, msg);
  294. } else {
  295. if (fastForwardMode == FastForwardMode.FF_ONLY) {
  296. return new MergeResult(headCommit, srcCommit,
  297. new ObjectId[] { headCommit, srcCommit },
  298. MergeStatus.ABORTED, mergeStrategy, null, null);
  299. }
  300. String mergeMessage = ""; //$NON-NLS-1$
  301. if (!squash) {
  302. if (message != null)
  303. mergeMessage = message;
  304. else
  305. mergeMessage = new MergeMessageFormatter().format(
  306. commits, head);
  307. repo.writeMergeCommitMsg(mergeMessage);
  308. repo.writeMergeHeads(Arrays.asList(ref.getObjectId()));
  309. } else {
  310. List<RevCommit> squashedCommits = RevWalkUtils.find(
  311. revWalk, srcCommit, headCommit);
  312. String squashMessage = new SquashMessageFormatter().format(
  313. squashedCommits, head);
  314. repo.writeSquashCommitMsg(squashMessage);
  315. }
  316. Merger merger = mergeStrategy.newMerger(repo);
  317. merger.setProgressMonitor(monitor);
  318. boolean noProblems;
  319. Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults = null;
  320. Map<String, MergeFailureReason> failingPaths = null;
  321. List<String> unmergedPaths = null;
  322. if (merger instanceof ResolveMerger) {
  323. ResolveMerger resolveMerger = (ResolveMerger) merger;
  324. resolveMerger.setCommitNames(new String[] {
  325. "BASE", "HEAD", ref.getName() }); //$NON-NLS-1$ //$NON-NLS-2$
  326. resolveMerger.setWorkingTreeIterator(new FileTreeIterator(repo));
  327. noProblems = merger.merge(headCommit, srcCommit);
  328. lowLevelResults = resolveMerger
  329. .getMergeResults();
  330. failingPaths = resolveMerger.getFailingPaths();
  331. unmergedPaths = resolveMerger.getUnmergedPaths();
  332. if (!resolveMerger.getModifiedFiles().isEmpty()) {
  333. repo.fireEvent(new WorkingTreeModifiedEvent(
  334. resolveMerger.getModifiedFiles(), null));
  335. }
  336. } else
  337. noProblems = merger.merge(headCommit, srcCommit);
  338. refLogMessage.append(": Merge made by "); //$NON-NLS-1$
  339. if (!revWalk.isMergedInto(headCommit, srcCommit))
  340. refLogMessage.append(mergeStrategy.getName());
  341. else
  342. refLogMessage.append("recursive"); //$NON-NLS-1$
  343. refLogMessage.append('.');
  344. if (noProblems) {
  345. dco = new DirCacheCheckout(repo,
  346. headCommit.getTree(), repo.lockDirCache(),
  347. merger.getResultTreeId());
  348. dco.setFailOnConflict(true);
  349. dco.checkout();
  350. String msg = null;
  351. ObjectId newHeadId = null;
  352. MergeStatus mergeStatus = null;
  353. if (!commit && squash) {
  354. mergeStatus = MergeStatus.MERGED_SQUASHED_NOT_COMMITTED;
  355. }
  356. if (!commit && !squash) {
  357. mergeStatus = MergeStatus.MERGED_NOT_COMMITTED;
  358. }
  359. if (commit && !squash) {
  360. try (Git git = new Git(getRepository())) {
  361. newHeadId = git.commit()
  362. .setReflogComment(refLogMessage.toString())
  363. .call().getId();
  364. }
  365. mergeStatus = MergeStatus.MERGED;
  366. getRepository().autoGC(monitor);
  367. }
  368. if (commit && squash) {
  369. msg = JGitText.get().squashCommitNotUpdatingHEAD;
  370. newHeadId = headCommit.getId();
  371. mergeStatus = MergeStatus.MERGED_SQUASHED;
  372. }
  373. return new MergeResult(newHeadId, null,
  374. new ObjectId[] { headCommit.getId(),
  375. srcCommit.getId() }, mergeStatus,
  376. mergeStrategy, null, msg);
  377. } else {
  378. if (failingPaths != null) {
  379. repo.writeMergeCommitMsg(null);
  380. repo.writeMergeHeads(null);
  381. return new MergeResult(null, merger.getBaseCommitId(),
  382. new ObjectId[] {
  383. headCommit.getId(), srcCommit.getId() },
  384. MergeStatus.FAILED, mergeStrategy,
  385. lowLevelResults, failingPaths, null);
  386. } else {
  387. String mergeMessageWithConflicts = new MergeMessageFormatter()
  388. .formatWithConflicts(mergeMessage,
  389. unmergedPaths);
  390. repo.writeMergeCommitMsg(mergeMessageWithConflicts);
  391. return new MergeResult(null, merger.getBaseCommitId(),
  392. new ObjectId[] { headCommit.getId(),
  393. srcCommit.getId() },
  394. MergeStatus.CONFLICTING, mergeStrategy,
  395. lowLevelResults, null);
  396. }
  397. }
  398. }
  399. } catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
  400. List<String> conflicts = (dco == null) ? Collections
  401. .<String> emptyList() : dco.getConflicts();
  402. throw new CheckoutConflictException(conflicts, e);
  403. } catch (IOException e) {
  404. throw new JGitInternalException(
  405. MessageFormat.format(
  406. JGitText.get().exceptionCaughtDuringExecutionOfMergeCommand,
  407. e), e);
  408. } finally {
  409. if (revWalk != null)
  410. revWalk.close();
  411. }
  412. }
  413. private void checkParameters() throws InvalidMergeHeadsException {
  414. if (squash.booleanValue() && fastForwardMode == FastForwardMode.NO_FF) {
  415. throw new JGitInternalException(
  416. JGitText.get().cannotCombineSquashWithNoff);
  417. }
  418. if (commits.size() != 1)
  419. throw new InvalidMergeHeadsException(
  420. commits.isEmpty() ? JGitText.get().noMergeHeadSpecified
  421. : MessageFormat.format(
  422. JGitText.get().mergeStrategyDoesNotSupportHeads,
  423. mergeStrategy.getName(),
  424. Integer.valueOf(commits.size())));
  425. }
  426. /**
  427. * Use values from the configuation if they have not been explicitly defined
  428. * via the setters
  429. */
  430. private void fallBackToConfiguration() {
  431. MergeConfig config = MergeConfig.getConfigForCurrentBranch(repo);
  432. if (squash == null)
  433. squash = Boolean.valueOf(config.isSquash());
  434. if (commit == null)
  435. commit = Boolean.valueOf(config.isCommit());
  436. if (fastForwardMode == null)
  437. fastForwardMode = config.getFastForwardMode();
  438. }
  439. private void updateHead(StringBuilder refLogMessage, ObjectId newHeadId,
  440. ObjectId oldHeadID) throws IOException,
  441. ConcurrentRefUpdateException {
  442. RefUpdate refUpdate = repo.updateRef(Constants.HEAD);
  443. refUpdate.setNewObjectId(newHeadId);
  444. refUpdate.setRefLogMessage(refLogMessage.toString(), false);
  445. refUpdate.setExpectedOldObjectId(oldHeadID);
  446. Result rc = refUpdate.update();
  447. switch (rc) {
  448. case NEW:
  449. case FAST_FORWARD:
  450. return;
  451. case REJECTED:
  452. case LOCK_FAILURE:
  453. throw new ConcurrentRefUpdateException(
  454. JGitText.get().couldNotLockHEAD, refUpdate.getRef(), rc);
  455. default:
  456. throw new JGitInternalException(MessageFormat.format(
  457. JGitText.get().updatingRefFailed, Constants.HEAD,
  458. newHeadId.toString(), rc));
  459. }
  460. }
  461. /**
  462. *
  463. * @param mergeStrategy
  464. * the {@link MergeStrategy} to be used
  465. * @return {@code this}
  466. */
  467. public MergeCommand setStrategy(MergeStrategy mergeStrategy) {
  468. checkCallable();
  469. this.mergeStrategy = mergeStrategy;
  470. return this;
  471. }
  472. /**
  473. * @param aCommit
  474. * a reference to a commit which is merged with the current head
  475. * @return {@code this}
  476. */
  477. public MergeCommand include(Ref aCommit) {
  478. checkCallable();
  479. commits.add(aCommit);
  480. return this;
  481. }
  482. /**
  483. * @param aCommit
  484. * the Id of a commit which is merged with the current head
  485. * @return {@code this}
  486. */
  487. public MergeCommand include(AnyObjectId aCommit) {
  488. return include(aCommit.getName(), aCommit);
  489. }
  490. /**
  491. * @param name
  492. * a name given to the commit
  493. * @param aCommit
  494. * the Id of a commit which is merged with the current head
  495. * @return {@code this}
  496. */
  497. public MergeCommand include(String name, AnyObjectId aCommit) {
  498. return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
  499. aCommit.copy()));
  500. }
  501. /**
  502. * If <code>true</code>, will prepare the next commit in working tree and
  503. * index as if a real merge happened, but do not make the commit or move the
  504. * HEAD. Otherwise, perform the merge and commit the result.
  505. * <p>
  506. * In case the merge was successful but this flag was set to
  507. * <code>true</code> a {@link MergeResult} with status
  508. * {@link MergeStatus#MERGED_SQUASHED} or
  509. * {@link MergeStatus#FAST_FORWARD_SQUASHED} is returned.
  510. *
  511. * @param squash
  512. * whether to squash commits or not
  513. * @return {@code this}
  514. * @since 2.0
  515. */
  516. public MergeCommand setSquash(boolean squash) {
  517. checkCallable();
  518. this.squash = Boolean.valueOf(squash);
  519. return this;
  520. }
  521. /**
  522. * Sets the fast forward mode.
  523. *
  524. * @param fastForwardMode
  525. * corresponds to the --ff/--no-ff/--ff-only options. If
  526. * {@code null} use the value of the {@code merge.ff} option
  527. * configured in git config. If this option is not configured
  528. * --ff is the built-in default.
  529. * @return {@code this}
  530. * @since 2.2
  531. */
  532. public MergeCommand setFastForward(
  533. @Nullable FastForwardMode fastForwardMode) {
  534. checkCallable();
  535. this.fastForwardMode = fastForwardMode;
  536. return this;
  537. }
  538. /**
  539. * Controls whether the merge command should automatically commit after a
  540. * successful merge
  541. *
  542. * @param commit
  543. * <code>true</code> if this command should commit (this is the
  544. * default behavior). <code>false</code> if this command should
  545. * not commit. In case the merge was successful but this flag was
  546. * set to <code>false</code> a {@link MergeResult} with type
  547. * {@link MergeResult} with status
  548. * {@link MergeStatus#MERGED_NOT_COMMITTED} is returned
  549. * @return {@code this}
  550. * @since 3.0
  551. */
  552. public MergeCommand setCommit(boolean commit) {
  553. this.commit = Boolean.valueOf(commit);
  554. return this;
  555. }
  556. /**
  557. * Set the commit message to be used for the merge commit (in case one is
  558. * created)
  559. *
  560. * @param message
  561. * the message to be used for the merge commit
  562. * @return {@code this}
  563. * @since 3.5
  564. */
  565. public MergeCommand setMessage(String message) {
  566. this.message = message;
  567. return this;
  568. }
  569. /**
  570. * The progress monitor associated with the diff operation. By default, this
  571. * is set to <code>NullProgressMonitor</code>
  572. *
  573. * @see NullProgressMonitor
  574. *
  575. * @param monitor
  576. * A progress monitor
  577. * @return this instance
  578. * @since 4.2
  579. */
  580. public MergeCommand setProgressMonitor(ProgressMonitor monitor) {
  581. if (monitor == null) {
  582. monitor = NullProgressMonitor.INSTANCE;
  583. }
  584. this.monitor = monitor;
  585. return this;
  586. }
  587. }