/// <summary>Updates the index after a content merge has happened.</summary> /// <remarks> /// 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. /// </remarks> /// <param name="base"></param> /// <param name="ours"></param> /// <param name="theirs"></param> /// <param name="result"></param> /// <param name="of"></param> /// <exception cref="System.IO.FileNotFoundException">System.IO.FileNotFoundException /// </exception> /// <exception cref="System.IO.IOException">System.IO.IOException</exception> private void UpdateIndex(CanonicalTreeParser @base, CanonicalTreeParser ours, CanonicalTreeParser theirs, MergeResult <RawText> result, FilePath of) { if (result.ContainsConflicts()) { // a conflict occurred, the file will contain conflict markers // the index will be populated with the three stages and only the // workdir (if used) contains the halfways merged content Add(tw.RawPath, @base, DirCacheEntry.STAGE_1, 0, 0); Add(tw.RawPath, ours, DirCacheEntry.STAGE_2, 0, 0); Add(tw.RawPath, theirs, DirCacheEntry.STAGE_3, 0, 0); mergeResults.Put(tw.PathString, result.Upcast()); } else { // 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.PathString); int newMode = MergeFileModes(tw.GetRawMode(0), tw.GetRawMode(1), tw.GetRawMode(2) ); // set the mode for the new content. Fall back to REGULAR_FILE if // you can't merge modes of OURS and THEIRS dce.FileMode = (newMode == FileMode.MISSING.GetBits()) ? FileMode.REGULAR_FILE : FileMode.FromBits(newMode); dce.LastModified = of.LastModified(); dce.SetLength((int)of.Length()); InputStream @is = new FileInputStream(of); try { dce.SetObjectId(GetObjectInserter().Insert(Constants.OBJ_BLOB, of.Length(), @is)); } finally { @is.Close(); if (inCore) { FileUtils.Delete(of); } } builder.Add(dce); } }
/// <exception cref="System.IO.FileNotFoundException"></exception> /// <exception cref="System.InvalidOperationException"></exception> /// <exception cref="System.IO.IOException"></exception> private bool ContentMerge(CanonicalTreeParser @base, CanonicalTreeParser ours, CanonicalTreeParser theirs) { MergeFormatter fmt = new MergeFormatter(); RawText baseText = @base == null ? RawText.EMPTY_TEXT : GetRawText(@base.EntryObjectId , db); // do the merge MergeResult <RawText> result = mergeAlgorithm.Merge(RawTextComparator.DEFAULT, baseText , GetRawText(ours.EntryObjectId, db), GetRawText(theirs.EntryObjectId, db)); FilePath of = null; FileOutputStream fos; if (!inCore) { FilePath workTree = db.WorkTree; if (workTree == null) { // TODO: This should be handled by WorkingTreeIterators which // support write operations throw new NotSupportedException(); } of = new FilePath(workTree, tw.PathString); fos = new FileOutputStream(of); try { fmt.FormatMerge(fos, result, Arrays.AsList(commitNames), Constants.CHARACTER_ENCODING ); } finally { fos.Close(); } } else { if (!result.ContainsConflicts()) { // When working inCore, only trivial merges can be handled, // so we generate objects only in conflict free cases of = FilePath.CreateTempFile("merge_", "_temp", null); fos = new FileOutputStream(of); try { fmt.FormatMerge(fos, result, Arrays.AsList(commitNames), Constants.CHARACTER_ENCODING ); } finally { fos.Close(); } } } if (result.ContainsConflicts()) { // a conflict occurred, the file will contain conflict markers // the index will be populated with the three stages and only the // workdir (if used) contains the halfways merged content Add(tw.RawPath, @base, DirCacheEntry.STAGE_1); Add(tw.RawPath, ours, DirCacheEntry.STAGE_2); Add(tw.RawPath, theirs, DirCacheEntry.STAGE_3); mergeResults.Put(tw.PathString, result.Upcast()); return(false); } else { // 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.PathString); dce.FileMode = tw.GetFileMode(0); dce.LastModified = of.LastModified(); dce.SetLength((int)of.Length()); InputStream @is = new FileInputStream(of); try { dce.SetObjectId(oi.Insert(Constants.OBJ_BLOB, of.Length(), @is)); } finally { @is.Close(); if (inCore) { FileUtils.Delete(of); } } builder.Add(dce); return(true); } }
/// <summary>Processes one path and tries to merge.</summary> /// <remarks> /// 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> /// <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.</li> /// </remarks> /// <param name="base">the common base for ours and theirs</param> /// <param name="ours"> /// the ours side of the merge. When merging a branch into the /// HEAD ours will point to HEAD /// </param> /// <param name="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> /// <param name="index">the index entry</param> /// <param name="work">the file in the working tree</param> /// <returns> /// <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 /// </returns> /// <exception cref="NGit.Errors.MissingObjectException">NGit.Errors.MissingObjectException /// </exception> /// <exception cref="NGit.Errors.IncorrectObjectTypeException">NGit.Errors.IncorrectObjectTypeException /// </exception> /// <exception cref="NGit.Errors.CorruptObjectException">NGit.Errors.CorruptObjectException /// </exception> /// <exception cref="System.IO.IOException">System.IO.IOException</exception> private bool ProcessEntry(CanonicalTreeParser @base, CanonicalTreeParser ours, CanonicalTreeParser theirs, DirCacheBuildIterator index, WorkingTreeIterator work) { enterSubtree = true; int modeO = tw.GetRawMode(T_OURS); int modeT = tw.GetRawMode(T_THEIRS); 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); } if (NonTree(modeO) && modeO == modeT && tw.IdEqual(T_OURS, T_THEIRS)) { // OURS and THEIRS are equal: it doesn't matter which one we choose. // OURS is chosen. Add(tw.RawPath, ours, DirCacheEntry.STAGE_0); // no checkout needed! return(true); } if (NonTree(modeO) && modeB == modeT && tw.IdEqual(T_BASE, T_THEIRS)) { // THEIRS was not changed compared to BASE. All changes must be in // OURS. OURS is chosen. Add(tw.RawPath, ours, DirCacheEntry.STAGE_0); // 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()) { return(false); } if (NonTree(modeT)) { DirCacheEntry e = Add(tw.RawPath, theirs, DirCacheEntry.STAGE_0); if (e != null) { toBeCheckedOut.Put(tw.PathString, e); } return(true); } else { if (modeT == 0 && modeB != 0) { // we want THEIRS ... but THEIRS contains the deletion of the // file toBeCheckedOut.Put(tw.PathString, null); 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.RawPath, @base, DirCacheEntry.STAGE_1); } Add(tw.RawPath, ours, DirCacheEntry.STAGE_2); unmergedPaths.AddItem(tw.PathString); enterSubtree = false; return(true); } if (NonTree(modeT) && !NonTree(modeO)) { if (NonTree(modeB)) { Add(tw.RawPath, @base, DirCacheEntry.STAGE_1); } Add(tw.RawPath, theirs, DirCacheEntry.STAGE_3); unmergedPaths.AddItem(tw.PathString); 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()) { return(false); } if (!ContentMerge(@base, ours, theirs)) { unmergedPaths.AddItem(tw.PathString); } modifiedFiles.AddItem(tw.PathString); } 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.RawPath, @base, DirCacheEntry.STAGE_1); Add(tw.RawPath, ours, DirCacheEntry.STAGE_2); DirCacheEntry e = Add(tw.RawPath, theirs, DirCacheEntry.STAGE_3); // OURS was deleted checkout THEIRS if (modeO == 0) { // Check worktree before checking out THEIRS if (IsWorktreeDirty()) { return(false); } if (NonTree(modeT)) { if (e != null) { toBeCheckedOut.Put(tw.PathString, e); } } } unmergedPaths.AddItem(tw.PathString); // generate a MergeResult for the deleted file RawText baseText = @base == null ? RawText.EMPTY_TEXT : GetRawText(@base.EntryObjectId , db); RawText ourText = ours == null ? RawText.EMPTY_TEXT : GetRawText(ours.EntryObjectId , db); RawText theirsText = theirs == null ? RawText.EMPTY_TEXT : GetRawText(theirs.EntryObjectId , db); MergeResult <RawText> result = mergeAlgorithm.Merge(RawTextComparator.DEFAULT, baseText , ourText, theirsText); mergeResults.Put(tw.PathString, result.Upcast()); } } } return(true); }