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 29KB

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