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.

ResolveMerger.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. /*
  2. * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>,
  3. * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
  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. package org.eclipse.jgit.merge;
  45. import java.io.File;
  46. import java.io.FileInputStream;
  47. import java.io.FileNotFoundException;
  48. import java.io.FileOutputStream;
  49. import java.io.IOException;
  50. import java.io.InputStream;
  51. import java.util.ArrayList;
  52. import java.util.Arrays;
  53. import java.util.Collections;
  54. import java.util.HashMap;
  55. import java.util.Iterator;
  56. import java.util.LinkedList;
  57. import java.util.List;
  58. import java.util.Map;
  59. import org.eclipse.jgit.diff.DiffAlgorithm;
  60. import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
  61. import org.eclipse.jgit.diff.RawText;
  62. import org.eclipse.jgit.diff.RawTextComparator;
  63. import org.eclipse.jgit.diff.Sequence;
  64. import org.eclipse.jgit.dircache.DirCache;
  65. import org.eclipse.jgit.dircache.DirCacheBuildIterator;
  66. import org.eclipse.jgit.dircache.DirCacheBuilder;
  67. import org.eclipse.jgit.dircache.DirCacheCheckout;
  68. import org.eclipse.jgit.dircache.DirCacheEntry;
  69. import org.eclipse.jgit.errors.CorruptObjectException;
  70. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  71. import org.eclipse.jgit.errors.IndexWriteException;
  72. import org.eclipse.jgit.errors.MissingObjectException;
  73. import org.eclipse.jgit.errors.NoWorkTreeException;
  74. import org.eclipse.jgit.internal.JGitText;
  75. import org.eclipse.jgit.lib.ConfigConstants;
  76. import org.eclipse.jgit.lib.Constants;
  77. import org.eclipse.jgit.lib.FileMode;
  78. import org.eclipse.jgit.lib.ObjectId;
  79. import org.eclipse.jgit.lib.ObjectInserter;
  80. import org.eclipse.jgit.lib.ObjectReader;
  81. import org.eclipse.jgit.lib.Repository;
  82. import org.eclipse.jgit.treewalk.CanonicalTreeParser;
  83. import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
  84. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  85. import org.eclipse.jgit.util.FileUtils;
  86. /**
  87. * A three-way merger performing a content-merge if necessary
  88. */
  89. public class ResolveMerger extends ThreeWayMerger {
  90. /**
  91. * If the merge fails (means: not stopped because of unresolved conflicts)
  92. * this enum is used to explain why it failed
  93. */
  94. public enum MergeFailureReason {
  95. /** the merge failed because of a dirty index */
  96. DIRTY_INDEX,
  97. /** the merge failed because of a dirty workingtree */
  98. DIRTY_WORKTREE,
  99. /** the merge failed because of a file could not be deleted */
  100. COULD_NOT_DELETE
  101. }
  102. private NameConflictTreeWalk tw;
  103. private String commitNames[];
  104. private static final int T_BASE = 0;
  105. private static final int T_OURS = 1;
  106. private static final int T_THEIRS = 2;
  107. private static final int T_INDEX = 3;
  108. private static final int T_FILE = 4;
  109. private DirCacheBuilder builder;
  110. private ObjectId resultTree;
  111. private List<String> unmergedPaths = new ArrayList<String>();
  112. private List<String> modifiedFiles = new LinkedList<String>();
  113. private Map<String, DirCacheEntry> toBeCheckedOut = new HashMap<String, DirCacheEntry>();
  114. private Map<String, MergeResult<? extends Sequence>> mergeResults = new HashMap<String, MergeResult<? extends Sequence>>();
  115. private Map<String, MergeFailureReason> failingPaths = new HashMap<String, MergeFailureReason>();
  116. private ObjectInserter oi;
  117. private boolean enterSubtree;
  118. private boolean inCore;
  119. private DirCache dircache;
  120. private WorkingTreeIterator workingTreeIterator;
  121. private MergeAlgorithm mergeAlgorithm;
  122. /**
  123. * @param local
  124. * @param inCore
  125. */
  126. protected ResolveMerger(Repository local, boolean inCore) {
  127. super(local);
  128. SupportedAlgorithm diffAlg = local.getConfig().getEnum(
  129. ConfigConstants.CONFIG_DIFF_SECTION, null,
  130. ConfigConstants.CONFIG_KEY_ALGORITHM,
  131. SupportedAlgorithm.HISTOGRAM);
  132. mergeAlgorithm = new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
  133. commitNames = new String[] { "BASE", "OURS", "THEIRS" };
  134. oi = getObjectInserter();
  135. this.inCore = inCore;
  136. if (inCore) {
  137. dircache = DirCache.newInCore();
  138. }
  139. }
  140. /**
  141. * @param local
  142. */
  143. protected ResolveMerger(Repository local) {
  144. this(local, false);
  145. }
  146. @Override
  147. protected boolean mergeImpl() throws IOException {
  148. boolean implicitDirCache = false;
  149. if (dircache == null) {
  150. dircache = getRepository().lockDirCache();
  151. implicitDirCache = true;
  152. }
  153. try {
  154. builder = dircache.builder();
  155. DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder);
  156. tw = new NameConflictTreeWalk(db);
  157. tw.addTree(mergeBase());
  158. tw.addTree(sourceTrees[0]);
  159. tw.addTree(sourceTrees[1]);
  160. tw.addTree(buildIt);
  161. if (workingTreeIterator != null)
  162. tw.addTree(workingTreeIterator);
  163. while (tw.next()) {
  164. if (!processEntry(
  165. tw.getTree(T_BASE, CanonicalTreeParser.class),
  166. tw.getTree(T_OURS, CanonicalTreeParser.class),
  167. tw.getTree(T_THEIRS, CanonicalTreeParser.class),
  168. tw.getTree(T_INDEX, DirCacheBuildIterator.class),
  169. (workingTreeIterator == null) ? null : tw.getTree(T_FILE, WorkingTreeIterator.class))) {
  170. cleanUp();
  171. return false;
  172. }
  173. if (tw.isSubtree() && enterSubtree)
  174. tw.enterSubtree();
  175. }
  176. if (!inCore) {
  177. // All content-merges are successfully done. If we can now write the
  178. // new index we are on quite safe ground. Even if the checkout of
  179. // files coming from "theirs" fails the user can work around such
  180. // failures by checking out the index again.
  181. if (!builder.commit()) {
  182. cleanUp();
  183. throw new IndexWriteException();
  184. }
  185. builder = null;
  186. // No problem found. The only thing left to be done is to checkout
  187. // all files from "theirs" which have been selected to go into the
  188. // new index.
  189. checkout();
  190. } else {
  191. builder.finish();
  192. builder = null;
  193. }
  194. if (getUnmergedPaths().isEmpty()) {
  195. resultTree = dircache.writeTree(oi);
  196. return true;
  197. } else {
  198. resultTree = null;
  199. return false;
  200. }
  201. } finally {
  202. if (implicitDirCache)
  203. dircache.unlock();
  204. }
  205. }
  206. private void checkout() throws NoWorkTreeException, IOException {
  207. ObjectReader r = db.getObjectDatabase().newReader();
  208. try {
  209. for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
  210. .entrySet()) {
  211. File f = new File(db.getWorkTree(), entry.getKey());
  212. if (entry.getValue() != null) {
  213. createDir(f.getParentFile());
  214. DirCacheCheckout.checkoutEntry(db, f, entry.getValue(), r);
  215. } else {
  216. if (!f.delete())
  217. failingPaths.put(entry.getKey(),
  218. MergeFailureReason.COULD_NOT_DELETE);
  219. }
  220. modifiedFiles.add(entry.getKey());
  221. }
  222. } finally {
  223. r.release();
  224. }
  225. }
  226. private void createDir(File f) throws IOException {
  227. if (!f.isDirectory() && !f.mkdirs()) {
  228. File p = f;
  229. while (p != null && !p.exists())
  230. p = p.getParentFile();
  231. if (p == null || p.isDirectory())
  232. throw new IOException(JGitText.get().cannotCreateDirectory);
  233. FileUtils.delete(p);
  234. if (!f.mkdirs())
  235. throw new IOException(JGitText.get().cannotCreateDirectory);
  236. }
  237. }
  238. /**
  239. * Reverts the worktree after an unsuccessful merge. We know that for all
  240. * modified files the old content was in the old index and the index
  241. * contained only stage 0. In case if inCore operation just clear
  242. * the history of modified files.
  243. *
  244. * @throws IOException
  245. * @throws CorruptObjectException
  246. * @throws NoWorkTreeException
  247. */
  248. private void cleanUp() throws NoWorkTreeException, CorruptObjectException, IOException {
  249. if (inCore) {
  250. modifiedFiles.clear();
  251. return;
  252. }
  253. DirCache dc = db.readDirCache();
  254. ObjectReader or = db.getObjectDatabase().newReader();
  255. Iterator<String> mpathsIt=modifiedFiles.iterator();
  256. while(mpathsIt.hasNext()) {
  257. String mpath=mpathsIt.next();
  258. DirCacheEntry entry = dc.getEntry(mpath);
  259. FileOutputStream fos = new FileOutputStream(new File(db.getWorkTree(), mpath));
  260. try {
  261. or.open(entry.getObjectId()).copyTo(fos);
  262. } finally {
  263. fos.close();
  264. }
  265. mpathsIt.remove();
  266. }
  267. }
  268. /**
  269. * adds a new path with the specified stage to the index builder
  270. *
  271. * @param path
  272. * @param p
  273. * @param stage
  274. * @return the entry which was added to the index
  275. */
  276. private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage) {
  277. if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
  278. DirCacheEntry e = new DirCacheEntry(path, stage);
  279. e.setFileMode(p.getEntryFileMode());
  280. e.setObjectId(p.getEntryObjectId());
  281. builder.add(e);
  282. return e;
  283. }
  284. return null;
  285. }
  286. /**
  287. * Processes one path and tries to merge. This method will do all do all
  288. * trivial (not content) merges and will also detect if a merge will fail.
  289. * The merge will fail when one of the following is true
  290. * <ul>
  291. * <li>the index entry does not match the entry in ours. When merging one
  292. * branch into the current HEAD, ours will point to HEAD and theirs will
  293. * point to the other branch. It is assumed that the index matches the HEAD
  294. * because it will only not match HEAD if it was populated before the merge
  295. * operation. But the merge commit should not accidentally contain
  296. * modifications done before the merge. Check the <a href=
  297. * "http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html#_3_way_merge"
  298. * >git read-tree</a> documentation for further explanations.</li>
  299. * <li>A conflict was detected and the working-tree file is dirty. When a
  300. * conflict is detected the content-merge algorithm will try to write a
  301. * merged version into the working-tree. If the file is dirty we would
  302. * override unsaved data.</li>
  303. *
  304. * @param base
  305. * the common base for ours and theirs
  306. * @param ours
  307. * the ours side of the merge. When merging a branch into the
  308. * HEAD ours will point to HEAD
  309. * @param theirs
  310. * the theirs side of the merge. When merging a branch into the
  311. * current HEAD theirs will point to the branch which is merged
  312. * into HEAD.
  313. * @param index
  314. * the index entry
  315. * @param work
  316. * the file in the working tree
  317. * @return <code>false</code> if the merge will fail because the index entry
  318. * didn't match ours or the working-dir file was dirty and a
  319. * conflict occurred
  320. * @throws MissingObjectException
  321. * @throws IncorrectObjectTypeException
  322. * @throws CorruptObjectException
  323. * @throws IOException
  324. */
  325. private boolean processEntry(CanonicalTreeParser base,
  326. CanonicalTreeParser ours, CanonicalTreeParser theirs,
  327. DirCacheBuildIterator index, WorkingTreeIterator work)
  328. throws MissingObjectException, IncorrectObjectTypeException,
  329. CorruptObjectException, IOException {
  330. enterSubtree = true;
  331. final int modeO = tw.getRawMode(T_OURS);
  332. final int modeT = tw.getRawMode(T_THEIRS);
  333. final int modeB = tw.getRawMode(T_BASE);
  334. if (modeO == 0 && modeT == 0 && modeB == 0)
  335. // File is either untracked or new, staged but uncommitted
  336. return true;
  337. if (isIndexDirty())
  338. return false;
  339. if (nonTree(modeO) && nonTree(modeT) && tw.idEqual(T_OURS, T_THEIRS)) {
  340. // OURS and THEIRS have equal content. Check the file mode
  341. if (modeO == modeT) {
  342. // content and mode of OURS and THEIRS are equal: it doesn't
  343. // matter which one we choose. OURS is chosen.
  344. add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0);
  345. // no checkout needed!
  346. return true;
  347. } else {
  348. // same content but different mode on OURS and THEIRS.
  349. // Try to merge the mode and report an error if this is
  350. // not possible.
  351. int newMode = mergeFileModes(modeB, modeO, modeT);
  352. if (newMode != FileMode.MISSING.getBits()) {
  353. if (newMode == modeO)
  354. // ours version is preferred
  355. add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0);
  356. else {
  357. // the preferred version THEIRS has a different mode
  358. // than ours. Check it out!
  359. if (isWorktreeDirty(work))
  360. return false;
  361. DirCacheEntry e = add(tw.getRawPath(), theirs,
  362. DirCacheEntry.STAGE_0);
  363. toBeCheckedOut.put(tw.getPathString(), e);
  364. }
  365. return true;
  366. } else {
  367. // FileModes are not mergeable. We found a conflict on modes
  368. add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
  369. add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
  370. add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3);
  371. unmergedPaths.add(tw.getPathString());
  372. mergeResults.put(
  373. tw.getPathString(),
  374. new MergeResult<RawText>(Collections
  375. .<RawText> emptyList()));
  376. }
  377. return true;
  378. }
  379. }
  380. if (nonTree(modeO) && modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
  381. // THEIRS was not changed compared to BASE. All changes must be in
  382. // OURS. OURS is chosen.
  383. add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0);
  384. // no checkout needed!
  385. return true;
  386. }
  387. if (modeB == modeO && tw.idEqual(T_BASE, T_OURS)) {
  388. // OURS was not changed compared to BASE. All changes must be in
  389. // THEIRS. THEIRS is chosen.
  390. // Check worktree before checking out THEIRS
  391. if (isWorktreeDirty(work))
  392. return false;
  393. if (nonTree(modeT)) {
  394. DirCacheEntry e = add(tw.getRawPath(), theirs,
  395. DirCacheEntry.STAGE_0);
  396. if (e != null)
  397. toBeCheckedOut.put(tw.getPathString(), e);
  398. return true;
  399. } else if (modeT == 0 && modeB != 0) {
  400. // we want THEIRS ... but THEIRS contains the deletion of the
  401. // file
  402. toBeCheckedOut.put(tw.getPathString(), null);
  403. return true;
  404. }
  405. }
  406. if (tw.isSubtree()) {
  407. // file/folder conflicts: here I want to detect only file/folder
  408. // conflict between ours and theirs. file/folder conflicts between
  409. // base/index/workingTree and something else are not relevant or
  410. // detected later
  411. if (nonTree(modeO) && !nonTree(modeT)) {
  412. if (nonTree(modeB))
  413. add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
  414. add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
  415. unmergedPaths.add(tw.getPathString());
  416. enterSubtree = false;
  417. return true;
  418. }
  419. if (nonTree(modeT) && !nonTree(modeO)) {
  420. if (nonTree(modeB))
  421. add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
  422. add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3);
  423. unmergedPaths.add(tw.getPathString());
  424. enterSubtree = false;
  425. return true;
  426. }
  427. // ours and theirs are both folders or both files (and treewalk
  428. // tells us we are in a subtree because of index or working-dir).
  429. // If they are both folders no content-merge is required - we can
  430. // return here.
  431. if (!nonTree(modeO))
  432. return true;
  433. // ours and theirs are both files, just fall out of the if block
  434. // and do the content merge
  435. }
  436. if (nonTree(modeO) && nonTree(modeT)) {
  437. // Check worktree before modifying files
  438. if (isWorktreeDirty(work))
  439. return false;
  440. MergeResult<RawText> result = contentMerge(base, ours, theirs);
  441. File of = writeMergedFile(result);
  442. updateIndex(base, ours, theirs, result, of);
  443. if (result.containsConflicts())
  444. unmergedPaths.add(tw.getPathString());
  445. modifiedFiles.add(tw.getPathString());
  446. } else if (modeO != modeT) {
  447. // OURS or THEIRS has been deleted
  448. if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
  449. .idEqual(T_BASE, T_THEIRS)))) {
  450. add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
  451. add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
  452. DirCacheEntry e = add(tw.getRawPath(), theirs,
  453. DirCacheEntry.STAGE_3);
  454. // OURS was deleted checkout THEIRS
  455. if (modeO == 0) {
  456. // Check worktree before checking out THEIRS
  457. if (isWorktreeDirty(work))
  458. return false;
  459. if (nonTree(modeT)) {
  460. if (e != null)
  461. toBeCheckedOut.put(tw.getPathString(), e);
  462. }
  463. }
  464. unmergedPaths.add(tw.getPathString());
  465. // generate a MergeResult for the deleted file
  466. mergeResults.put(tw.getPathString(),
  467. contentMerge(base, ours, theirs));
  468. }
  469. }
  470. return true;
  471. }
  472. /**
  473. * Does the content merge. The three texts base, ours and theirs are
  474. * specified with {@link CanonicalTreeParser}. If any of the parsers is
  475. * specified as <code>null</code> then an empty text will be used instead.
  476. *
  477. * @param base
  478. * @param ours
  479. * @param theirs
  480. *
  481. * @return the result of the content merge
  482. * @throws IOException
  483. */
  484. private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
  485. CanonicalTreeParser ours, CanonicalTreeParser theirs)
  486. throws IOException {
  487. RawText baseText = base == null ? RawText.EMPTY_TEXT : getRawText(
  488. base.getEntryObjectId(), db);
  489. RawText ourText = ours == null ? RawText.EMPTY_TEXT : getRawText(
  490. ours.getEntryObjectId(), db);
  491. RawText theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText(
  492. theirs.getEntryObjectId(), db);
  493. return (mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
  494. ourText, theirsText));
  495. }
  496. private boolean isIndexDirty() {
  497. final int modeI = tw.getRawMode(T_INDEX);
  498. final int modeO = tw.getRawMode(T_OURS);
  499. // Index entry has to match ours to be considered clean
  500. final boolean isDirty = nonTree(modeI)
  501. && !(modeO == modeI && tw.idEqual(T_INDEX, T_OURS));
  502. if (isDirty)
  503. failingPaths
  504. .put(tw.getPathString(), MergeFailureReason.DIRTY_INDEX);
  505. return isDirty;
  506. }
  507. private boolean isWorktreeDirty(WorkingTreeIterator work) {
  508. if (inCore)
  509. return false;
  510. final int modeF = tw.getRawMode(T_FILE);
  511. final int modeO = tw.getRawMode(T_OURS);
  512. // Worktree entry has to match ours to be considered clean
  513. final boolean isDirty;
  514. if (nonTree(modeF))
  515. isDirty = work.isModeDifferent(modeO)
  516. || !tw.idEqual(T_FILE, T_OURS);
  517. else
  518. isDirty = false;
  519. if (isDirty)
  520. failingPaths.put(tw.getPathString(),
  521. MergeFailureReason.DIRTY_WORKTREE);
  522. return isDirty;
  523. }
  524. /**
  525. * Updates the index after a content merge has happened. If no conflict has
  526. * occurred this includes persisting the merged content to the object
  527. * database. In case of conflicts this method takes care to write the
  528. * correct stages to the index.
  529. *
  530. * @param base
  531. * @param ours
  532. * @param theirs
  533. * @param result
  534. * @param of
  535. * @throws FileNotFoundException
  536. * @throws IOException
  537. */
  538. private void updateIndex(CanonicalTreeParser base,
  539. CanonicalTreeParser ours, CanonicalTreeParser theirs,
  540. MergeResult<RawText> result, File of) throws FileNotFoundException,
  541. IOException {
  542. if (result.containsConflicts()) {
  543. // a conflict occurred, the file will contain conflict markers
  544. // the index will be populated with the three stages and only the
  545. // workdir (if used) contains the halfways merged content
  546. add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
  547. add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
  548. add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3);
  549. mergeResults.put(tw.getPathString(), result);
  550. } else {
  551. // no conflict occurred, the file will contain fully merged content.
  552. // the index will be populated with the new merged version
  553. DirCacheEntry dce = new DirCacheEntry(tw.getPathString());
  554. int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1),
  555. tw.getRawMode(2));
  556. // set the mode for the new content. Fall back to REGULAR_FILE if
  557. // you can't merge modes of OURS and THEIRS
  558. dce.setFileMode((newMode == FileMode.MISSING.getBits()) ? FileMode.REGULAR_FILE
  559. : FileMode.fromBits(newMode));
  560. dce.setLastModified(of.lastModified());
  561. dce.setLength((int) of.length());
  562. InputStream is = new FileInputStream(of);
  563. try {
  564. dce.setObjectId(oi.insert(Constants.OBJ_BLOB, of.length(), is));
  565. } finally {
  566. is.close();
  567. if (inCore)
  568. FileUtils.delete(of);
  569. }
  570. builder.add(dce);
  571. }
  572. }
  573. /**
  574. * Writes merged file content to the working tree. In case {@link #inCore}
  575. * is set and we don't have a working tree the content is written to a
  576. * temporary file
  577. *
  578. * @param result
  579. * the result of the content merge
  580. * @return the file to which the merged content was written
  581. * @throws FileNotFoundException
  582. * @throws IOException
  583. */
  584. private File writeMergedFile(MergeResult<RawText> result)
  585. throws FileNotFoundException, IOException {
  586. MergeFormatter fmt = new MergeFormatter();
  587. File of = null;
  588. FileOutputStream fos;
  589. if (!inCore) {
  590. File workTree = db.getWorkTree();
  591. if (workTree == null)
  592. // TODO: This should be handled by WorkingTreeIterators which
  593. // support write operations
  594. throw new UnsupportedOperationException();
  595. of = new File(workTree, tw.getPathString());
  596. fos = new FileOutputStream(of);
  597. try {
  598. fmt.formatMerge(fos, result, Arrays.asList(commitNames),
  599. Constants.CHARACTER_ENCODING);
  600. } finally {
  601. fos.close();
  602. }
  603. }
  604. else if (!result.containsConflicts()) {
  605. // When working inCore, only trivial merges can be handled,
  606. // so we generate objects only in conflict free cases
  607. of = File.createTempFile("merge_", "_temp", null);
  608. fos = new FileOutputStream(of);
  609. try {
  610. fmt.formatMerge(fos, result, Arrays.asList(commitNames),
  611. Constants.CHARACTER_ENCODING);
  612. } finally {
  613. fos.close();
  614. }
  615. }
  616. return of;
  617. }
  618. /**
  619. * Try to merge filemodes. If only ours or theirs have changed the mode
  620. * (compared to base) we choose that one. If ours and theirs have equal
  621. * modes return that one. If also that is not the case the modes are not
  622. * mergeable. Return {@link FileMode#MISSING} int that case.
  623. *
  624. * @param modeB
  625. * filemode found in BASE
  626. * @param modeO
  627. * filemode found in OURS
  628. * @param modeT
  629. * filemode found in THEIRS
  630. *
  631. * @return the merged filemode or {@link FileMode#MISSING} in case of a
  632. * conflict
  633. */
  634. private int mergeFileModes(int modeB, int modeO, int modeT) {
  635. if (modeO == modeT)
  636. return modeO;
  637. if (modeB == modeO)
  638. // Base equal to Ours -> chooses Theirs if that is not missing
  639. return (modeT == FileMode.MISSING.getBits()) ? modeO : modeT;
  640. if (modeB == modeT)
  641. // Base equal to Theirs -> chooses Ours if that is not missing
  642. return (modeO == FileMode.MISSING.getBits()) ? modeT : modeO;
  643. return FileMode.MISSING.getBits();
  644. }
  645. private static RawText getRawText(ObjectId id, Repository db)
  646. throws IOException {
  647. if (id.equals(ObjectId.zeroId()))
  648. return new RawText(new byte[] {});
  649. return new RawText(db.open(id, Constants.OBJ_BLOB).getCachedBytes());
  650. }
  651. private static boolean nonTree(final int mode) {
  652. return mode != 0 && !FileMode.TREE.equals(mode);
  653. }
  654. @Override
  655. public ObjectId getResultTreeId() {
  656. return (resultTree == null) ? null : resultTree.toObjectId();
  657. }
  658. /**
  659. * @param commitNames
  660. * the names of the commits as they would appear in conflict
  661. * markers
  662. */
  663. public void setCommitNames(String[] commitNames) {
  664. this.commitNames = commitNames;
  665. }
  666. /**
  667. * @return the names of the commits as they would appear in conflict
  668. * markers.
  669. */
  670. public String[] getCommitNames() {
  671. return commitNames;
  672. }
  673. /**
  674. * @return the paths with conflicts. This is a subset of the files listed
  675. * by {@link #getModifiedFiles()}
  676. */
  677. public List<String> getUnmergedPaths() {
  678. return unmergedPaths;
  679. }
  680. /**
  681. * @return the paths of files which have been modified by this merge. A
  682. * file will be modified if a content-merge works on this path or if
  683. * the merge algorithm decides to take the theirs-version. This is a
  684. * superset of the files listed by {@link #getUnmergedPaths()}.
  685. */
  686. public List<String> getModifiedFiles() {
  687. return modifiedFiles;
  688. }
  689. /**
  690. * @return a map which maps the paths of files which have to be checked out
  691. * because the merge created new fully-merged content for this file
  692. * into the index. This means: the merge wrote a new stage 0 entry
  693. * for this path.
  694. */
  695. public Map<String, DirCacheEntry> getToBeCheckedOut() {
  696. return toBeCheckedOut;
  697. }
  698. /**
  699. * @return the mergeResults
  700. */
  701. public Map<String, MergeResult<? extends Sequence>> getMergeResults() {
  702. return mergeResults;
  703. }
  704. /**
  705. * @return lists paths causing this merge to fail (not stopped because of a
  706. * conflict). <code>null</code> is returned if this merge didn't
  707. * fail.
  708. */
  709. public Map<String, MergeFailureReason> getFailingPaths() {
  710. return (failingPaths.size() == 0) ? null : failingPaths;
  711. }
  712. /**
  713. * Returns whether this merge failed (i.e. not stopped because of a
  714. * conflict)
  715. *
  716. * @return <code>true</code> if a failure occurred, <code>false</code>
  717. * otherwise
  718. */
  719. public boolean failed() {
  720. return failingPaths.size() > 0;
  721. }
  722. /**
  723. * Sets the DirCache which shall be used by this merger. If the DirCache is
  724. * not set explicitly this merger will implicitly get and lock a default
  725. * DirCache. If the DirCache is explicitly set the caller is responsible to
  726. * lock it in advance. Finally the merger will call
  727. * {@link DirCache#commit()} which requires that the DirCache is locked. If
  728. * the {@link #mergeImpl()} returns without throwing an exception the lock
  729. * will be released. In case of exceptions the caller is responsible to
  730. * release the lock.
  731. *
  732. * @param dc
  733. * the DirCache to set
  734. */
  735. public void setDirCache(DirCache dc) {
  736. this.dircache = dc;
  737. }
  738. /**
  739. * Sets the WorkingTreeIterator to be used by this merger. If no
  740. * WorkingTreeIterator is set this merger will ignore the working tree and
  741. * fail if a content merge is necessary.
  742. * <p>
  743. * TODO: enhance WorkingTreeIterator to support write operations. Then this
  744. * merger will be able to merge with a different working tree abstraction.
  745. *
  746. * @param workingTreeIterator
  747. * the workingTreeIt to set
  748. */
  749. public void setWorkingTreeIterator(WorkingTreeIterator workingTreeIterator) {
  750. this.workingTreeIterator = workingTreeIterator;
  751. }
  752. }