Example #1
0
 /**
  * Try to merge filemodes. If only ours or theirs have changed the mode (compared to base) we
  * choose that one. If ours and theirs have equal modes return that one. If also that is not the
  * case the modes are not mergeable. Return {@link FileMode#MISSING} int that case.
  *
  * @param modeB filemode found in BASE
  * @param modeO filemode found in OURS
  * @param modeT filemode found in THEIRS
  * @return the merged filemode or {@link FileMode#MISSING} in case of a conflict
  */
 private int mergeFileModes(int modeB, int modeO, int modeT) {
   if (modeO == modeT) return modeO;
   if (modeB == modeO)
     // Base equal to Ours -> chooses Theirs if that is not missing
     return (modeT == FileMode.MISSING.getBits()) ? modeO : modeT;
   if (modeB == modeT)
     // Base equal to Theirs -> chooses Ours if that is not missing
     return (modeO == FileMode.MISSING.getBits()) ? modeT : modeO;
   return FileMode.MISSING.getBits();
 }
Example #2
0
  /**
   * Updates the index after a content merge has happened. If no conflict has occurred this includes
   * persisting the merged content to the object database. In case of conflicts this method takes
   * care to write the correct stages to the index.
   *
   * @param base
   * @param ours
   * @param theirs
   * @param result
   * @throws FileNotFoundException
   * @throws IOException
   */
  private void updateIndex(
      CanonicalTreeParser base,
      CanonicalTreeParser ours,
      CanonicalTreeParser theirs,
      MergeResult<RawText> result)
      throws FileNotFoundException, IOException {
    File mergedFile = !inCore ? writeMergedFile(result) : null;
    if (result.containsConflicts()) {
      // A conflict occurred, the file will contain conflict markers
      // the index will be populated with the three stages and the
      // workdir (if used) contains the halfway merged content.
      add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
      add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
      add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
      mergeResults.put(tw.getPathString(), result);
      return;
    }

    // No conflict occurred, the file will contain fully merged content.
    // The index will be populated with the new merged version.
    DirCacheEntry dce = new DirCacheEntry(tw.getPathString());

    // Set the mode for the new content. Fall back to REGULAR_FILE if
    // we can't merge modes of OURS and THEIRS.
    int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1), tw.getRawMode(2));
    dce.setFileMode(
        newMode == FileMode.MISSING.getBits() ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode));
    if (mergedFile != null) {
      long len = mergedFile.length();
      dce.setLastModified(mergedFile.lastModified());
      dce.setLength((int) len);
      InputStream is = new FileInputStream(mergedFile);
      try {
        dce.setObjectId(getObjectInserter().insert(OBJ_BLOB, len, is));
      } finally {
        is.close();
      }
    } else dce.setObjectId(insertMergeResult(result));
    builder.add(dce);
  }
Example #3
0
  /**
   * Processes one path and tries to merge. This method will do all do all trivial (not content)
   * merges and will also detect if a merge will fail. The merge will fail when one of the following
   * is true
   *
   * <ul>
   *   <li>the index entry does not match the entry in ours. When merging one branch into the
   *       current HEAD, ours will point to HEAD and theirs will point to the other branch. It is
   *       assumed that the index matches the HEAD because it will only not match HEAD if it was
   *       populated before the merge operation. But the merge commit should not accidentally
   *       contain modifications done before the merge. Check the <a href=
   *       "http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html#_3_way_merge" >git
   *       read-tree</a> documentation for further explanations.
   *   <li>A conflict was detected and the working-tree file is dirty. When a conflict is detected
   *       the content-merge algorithm will try to write a merged version into the working-tree. If
   *       the file is dirty we would override unsaved data.
   * </ul>
   *
   * @param base the common base for ours and theirs
   * @param ours the ours side of the merge. When merging a branch into the HEAD ours will point to
   *     HEAD
   * @param theirs the theirs side of the merge. When merging a branch into the current HEAD theirs
   *     will point to the branch which is merged into HEAD.
   * @param index the index entry
   * @param work the file in the working tree
   * @param ignoreConflicts see {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree,
   *     RevTree, boolean)}
   * @return <code>false</code> if the merge will fail because the index entry didn't match ours or
   *     the working-dir file was dirty and a conflict occurred
   * @throws MissingObjectException
   * @throws IncorrectObjectTypeException
   * @throws CorruptObjectException
   * @throws IOException
   * @since 3.5
   */
  protected boolean processEntry(
      CanonicalTreeParser base,
      CanonicalTreeParser ours,
      CanonicalTreeParser theirs,
      DirCacheBuildIterator index,
      WorkingTreeIterator work,
      boolean ignoreConflicts)
      throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException,
          IOException {
    enterSubtree = true;
    final int modeO = tw.getRawMode(T_OURS);
    final int modeT = tw.getRawMode(T_THEIRS);
    final int modeB = tw.getRawMode(T_BASE);

    if (modeO == 0 && modeT == 0 && modeB == 0)
      // File is either untracked or new, staged but uncommitted
      return true;

    if (isIndexDirty()) return false;

    DirCacheEntry ourDce = null;

    if (index == null || index.getDirCacheEntry() == null) {
      // create a fake DCE, but only if ours is valid. ours is kept only
      // in case it is valid, so a null ourDce is ok in all other cases.
      if (nonTree(modeO)) {
        ourDce = new DirCacheEntry(tw.getRawPath());
        ourDce.setObjectId(tw.getObjectId(T_OURS));
        ourDce.setFileMode(tw.getFileMode(T_OURS));
      }
    } else {
      ourDce = index.getDirCacheEntry();
    }

    if (nonTree(modeO) && nonTree(modeT) && tw.idEqual(T_OURS, T_THEIRS)) {
      // OURS and THEIRS have equal content. Check the file mode
      if (modeO == modeT) {
        // content and mode of OURS and THEIRS are equal: it doesn't
        // matter which one we choose. OURS is chosen. Since the index
        // is clean (the index matches already OURS) we can keep the existing one
        keep(ourDce);
        // no checkout needed!
        return true;
      } else {
        // same content but different mode on OURS and THEIRS.
        // Try to merge the mode and report an error if this is
        // not possible.
        int newMode = mergeFileModes(modeB, modeO, modeT);
        if (newMode != FileMode.MISSING.getBits()) {
          if (newMode == modeO)
            // ours version is preferred
            keep(ourDce);
          else {
            // the preferred version THEIRS has a different mode
            // than ours. Check it out!
            if (isWorktreeDirty(work, ourDce)) return false;
            // we know about length and lastMod only after we have written the new content.
            // This will happen later. Set these values to 0 for know.
            DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_0, 0, 0);
            toBeCheckedOut.put(tw.getPathString(), e);
          }
          return true;
        } else {
          // FileModes are not mergeable. We found a conflict on modes.
          // For conflicting entries we don't know lastModified and length.
          add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
          add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
          add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
          unmergedPaths.add(tw.getPathString());
          mergeResults.put(
              tw.getPathString(), new MergeResult<RawText>(Collections.<RawText>emptyList()));
        }
        return true;
      }
    }

    if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
      // THEIRS was not changed compared to BASE. All changes must be in
      // OURS. OURS is chosen. We can keep the existing entry.
      if (ourDce != null) keep(ourDce);
      // no checkout needed!
      return true;
    }

    if (modeB == modeO && tw.idEqual(T_BASE, T_OURS)) {
      // OURS was not changed compared to BASE. All changes must be in
      // THEIRS. THEIRS is chosen.

      // Check worktree before checking out THEIRS
      if (isWorktreeDirty(work, ourDce)) return false;
      if (nonTree(modeT)) {
        // we know about length and lastMod only after we have written
        // the new content.
        // This will happen later. Set these values to 0 for know.
        DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_0, 0, 0);
        if (e != null) toBeCheckedOut.put(tw.getPathString(), e);
        return true;
      } else {
        // we want THEIRS ... but THEIRS contains a folder or the
        // deletion of the path. Delete what's in the workingtree (the
        // workingtree is clean) but do not complain if the file is
        // already deleted locally. This complements the test in
        // isWorktreeDirty() for the same case.
        if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) return true;
        toBeDeleted.add(tw.getPathString());
        return true;
      }
    }

    if (tw.isSubtree()) {
      // file/folder conflicts: here I want to detect only file/folder
      // conflict between ours and theirs. file/folder conflicts between
      // base/index/workingTree and something else are not relevant or
      // detected later
      if (nonTree(modeO) && !nonTree(modeT)) {
        if (nonTree(modeB)) add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
        add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
        unmergedPaths.add(tw.getPathString());
        enterSubtree = false;
        return true;
      }
      if (nonTree(modeT) && !nonTree(modeO)) {
        if (nonTree(modeB)) add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
        add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
        unmergedPaths.add(tw.getPathString());
        enterSubtree = false;
        return true;
      }

      // ours and theirs are both folders or both files (and treewalk
      // tells us we are in a subtree because of index or working-dir).
      // If they are both folders no content-merge is required - we can
      // return here.
      if (!nonTree(modeO)) return true;

      // ours and theirs are both files, just fall out of the if block
      // and do the content merge
    }

    if (nonTree(modeO) && nonTree(modeT)) {
      // Check worktree before modifying files
      if (isWorktreeDirty(work, ourDce)) return false;

      // Don't attempt to resolve submodule link conflicts
      if (isGitLink(modeO) || isGitLink(modeT)) {
        add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
        add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
        add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
        unmergedPaths.add(tw.getPathString());
        return true;
      }

      MergeResult<RawText> result = contentMerge(base, ours, theirs);
      if (ignoreConflicts) result.setContainsConflicts(false);
      updateIndex(base, ours, theirs, result);
      if (result.containsConflicts() && !ignoreConflicts) unmergedPaths.add(tw.getPathString());
      modifiedFiles.add(tw.getPathString());
    } else if (modeO != modeT) {
      // OURS or THEIRS has been deleted
      if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS))
          || (modeT != 0 && !tw.idEqual(T_BASE, T_THEIRS)))) {

        add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
        add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
        DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);

        // OURS was deleted checkout THEIRS
        if (modeO == 0) {
          // Check worktree before checking out THEIRS
          if (isWorktreeDirty(work, ourDce)) return false;
          if (nonTree(modeT)) {
            if (e != null) toBeCheckedOut.put(tw.getPathString(), e);
          }
        }

        unmergedPaths.add(tw.getPathString());

        // generate a MergeResult for the deleted file
        mergeResults.put(tw.getPathString(), contentMerge(base, ours, theirs));
      }
    }
    return true;
  }