ObjectId WriteWorkingDirectoryTree(RevTree headTree, DirCache index) { DirCache dc = DirCache.NewInCore(); DirCacheBuilder cb = dc.Builder(); ObjectInserter oi = _repo.NewObjectInserter(); try { TreeWalk tw = new TreeWalk(_repo); tw.Reset(); tw.AddTree(new FileTreeIterator(_repo)); tw.AddTree(headTree); tw.AddTree(new DirCacheIterator(index)); while (tw.Next()) { // Ignore untracked files if (tw.IsSubtree) { tw.EnterSubtree(); } else if (tw.GetFileMode(0) != NGit.FileMode.MISSING && (tw.GetFileMode(1) != NGit.FileMode.MISSING || tw.GetFileMode(2) != NGit.FileMode.MISSING)) { WorkingTreeIterator f = tw.GetTree <WorkingTreeIterator>(0); DirCacheIterator dcIter = tw.GetTree <DirCacheIterator>(2); DirCacheEntry currentEntry = dcIter.GetDirCacheEntry(); DirCacheEntry ce = new DirCacheEntry(tw.PathString); if (!f.IsModified(currentEntry, true)) { ce.SetLength(currentEntry.Length); ce.LastModified = currentEntry.LastModified; ce.FileMode = currentEntry.FileMode; ce.SetObjectId(currentEntry.GetObjectId()); } else { long sz = f.GetEntryLength(); ce.SetLength(sz); ce.LastModified = f.GetEntryLastModified(); ce.FileMode = f.EntryFileMode; var data = f.OpenEntryStream(); try { ce.SetObjectId(oi.Insert(Constants.OBJ_BLOB, sz, data)); } finally { data.Close(); } } cb.Add(ce); } } cb.Finish(); return(dc.WriteTree(oi)); } finally { oi.Release(); } }
/// <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 occured /// </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 modeI = tw.GetRawMode(T_INDEX); // Each index entry has to match ours, means: it has to be clean if (NonTree(modeI) && !(tw.IdEqual(T_INDEX, T_OURS) && modeO == modeI)) { failingPaths.Put(tw.PathString, ResolveMerger.MergeFailureReason.DIRTY_INDEX); return false; } int modeT = tw.GetRawMode(T_THEIRS); if (NonTree(modeO) && modeO == modeT && tw.IdEqual(T_OURS, T_THEIRS)) { // ours and theirs are equal: it doesn'nt matter // which one we choose. OURS is choosen here. Add(tw.RawPath, ours, DirCacheEntry.STAGE_0); // no checkout needed! return true; } int modeB = tw.GetRawMode(T_BASE); if (NonTree(modeO) && modeB == modeT && tw.IdEqual(T_BASE, T_THEIRS)) { // THEIRS was not changed compared to base. All changes must be in // OURS. Choose OURS. Add(tw.RawPath, ours, DirCacheEntry.STAGE_0); return true; } if (modeB == modeO && tw.IdEqual(T_BASE, T_OURS)) { // OURS was not changed compared to base. All changes must be in // THEIRS. Choose THEIRS. 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)) { if (!inCore) { // We are going to update the worktree. Make sure the worktree // is not modified if (work != null && (!NonTree(work.EntryRawMode) || work.IsModified(index.GetDirCacheEntry (), true))) { failingPaths.Put(tw.PathString, ResolveMerger.MergeFailureReason.DIRTY_WORKTREE); return false; } } if (!ContentMerge(@base, ours, theirs)) { unmergedPaths.AddItem(tw.PathString); } modifiedFiles.AddItem(tw.PathString); } return true; }
private GitFileStatus GetFileStatus(TreeWalk treeWalk) { AbstractTreeIterator treeIterator = treeWalk.GetTree <AbstractTreeIterator>(TREE); DirCacheIterator dirCacheIterator = treeWalk.GetTree <DirCacheIterator>(INDEX); WorkingTreeIterator workingTreeIterator = treeWalk.GetTree <WorkingTreeIterator>(WORKDIR); if (dirCacheIterator != null) { DirCacheEntry dirCacheEntry = dirCacheIterator.GetDirCacheEntry(); if (dirCacheEntry != null && dirCacheEntry.Stage > 0) { return(GitFileStatus.Conflict); } if (workingTreeIterator == null) { // in index, not in workdir => missing return(GitFileStatus.Deleted); } else { if (workingTreeIterator.IsModified(dirCacheIterator.GetDirCacheEntry(), true)) { // in index, in workdir, content differs => modified return(GitFileStatus.Modified); } } } if (treeIterator != null) { if (dirCacheIterator != null) { if (!treeIterator.IdEqual(dirCacheIterator) || treeIterator.EntryRawMode != dirCacheIterator.EntryRawMode) { // in repo, in index, content diff => changed return(GitFileStatus.Staged); } } else { return(GitFileStatus.Removed); } } else { if (dirCacheIterator != null) { // not in repo, in index => added return(GitFileStatus.Added); } else { // not in repo, not in index => untracked if (workingTreeIterator != null) { return(!workingTreeIterator.IsEntryIgnored() ? GitFileStatus.New : GitFileStatus.Ignored); } } } return(GitFileStatus.NotControlled); }
/// <exception cref="NGit.Errors.MissingObjectException"></exception> /// <exception cref="NGit.Errors.IncorrectObjectTypeException"></exception> /// <exception cref="System.IO.IOException"></exception> public override bool Include(TreeWalk tw) { int cnt = tw.TreeCount; int wm = tw.GetRawMode(workingTree); string path = tw.PathString; if (!tw.PostOrderTraversal) { // detect untracked Folders // Whenever we enter a folder in the workingtree assume it will // contain only untracked files and add it to // untrackedParentFolders. If we later find tracked files we will // remove it from this list if (FileMode.TREE.Equals(wm)) { // Clean untrackedParentFolders. This potentially moves entries // from untrackedParentFolders to untrackedFolders CopyUntrackedFolders(path); // add the folder we just entered to untrackedParentFolders untrackedParentFolders.AddFirst(path); } // detect untracked Folders // Whenever we see a tracked file we know that all of its parent // folders do not belong into untrackedParentFolders anymore. Clean // it. for (int i = 0; i < cnt; i++) { int rmode = tw.GetRawMode(i); if (i != workingTree && rmode != 0 && FileMode.TREE.Equals(rmode)) { untrackedParentFolders.Clear(); break; } } } // If the working tree file doesn't exist, it does exist for at least // one other so include this difference. if (wm == 0) { return(true); } // If the path does not appear in the DirCache and its ignored // we can avoid returning a result here, but only if its not in any // other tree. int dm = tw.GetRawMode(dirCache); WorkingTreeIterator wi = WorkingTree(tw); if (dm == 0) { if (honorIgnores && wi.IsEntryIgnored()) { ignoredPaths.AddItem(wi.EntryPathString); int i = 0; for (; i < cnt; i++) { if (i == dirCache || i == workingTree) { continue; } if (tw.GetRawMode(i) != 0) { break; } } // If i is cnt then the path does not appear in any other tree, // and this working tree entry can be safely ignored. return(i == cnt ? false : true); } else { // In working tree and not ignored, and not in DirCache. return(true); } } // Always include subtrees as WorkingTreeIterator cannot provide // efficient elimination of unmodified subtrees. if (tw.IsSubtree) { return(true); } // Try the inexpensive comparisons between index and all real trees // first. Only if we don't find a diff here we have to bother with // the working tree for (int i_1 = 0; i_1 < cnt; i_1++) { if (i_1 == dirCache || i_1 == workingTree) { continue; } if (tw.GetRawMode(i_1) != dm || !tw.IdEqual(i_1, dirCache)) { return(true); } } // Only one chance left to detect a diff: between index and working // tree. Make use of the WorkingTreeIterator#isModified() method to // avoid computing SHA1 on filesystem content if not really needed. DirCacheIterator di = tw.GetTree <DirCacheIterator>(dirCache); return(wi.IsModified(di.GetDirCacheEntry(), true)); }
/// <summary> /// Processing an entry in the context of /// <see cref="PrescanOneTree()">PrescanOneTree()</see> /// when only /// one tree is given /// </summary> /// <param name="m">the tree to merge</param> /// <param name="i">the index</param> /// <param name="f">the working tree</param> /// <exception cref="System.IO.IOException">System.IO.IOException</exception> internal virtual void ProcessEntry(CanonicalTreeParser m, DirCacheBuildIterator i , WorkingTreeIterator f) { if (m != null) { if (!IsValidPath(m)) { throw new InvalidPathException(m.EntryPathString); } // There is an entry in the merge commit. Means: we want to update // what's currently in the index and working-tree to that one if (i == null) { // The index entry is missing if (f != null && !FileMode.TREE.Equals(f.EntryFileMode) && !f.IsEntryIgnored()) { // don't overwrite an untracked and not ignored file conflicts.AddItem(walk.PathString); } else { Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } } else { if (f == null || !m.IdEqual(i)) { // The working tree file is missing or the merge content differs // from index content Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } else { if (i.GetDirCacheEntry() != null) { // The index contains a file (and not a folder) if (f.IsModified(i.GetDirCacheEntry(), true) || i.GetDirCacheEntry().Stage != 0) { // The working tree file is dirty or the index contains a // conflict Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } else { // update the timestamp of the index with the one from the // file if not set, as we are sure to be in sync here. DirCacheEntry entry = i.GetDirCacheEntry(); if (entry.LastModified == 0) { entry.LastModified = f.GetEntryLastModified(); } Keep(entry); } } else { // The index contains a folder Keep(i.GetDirCacheEntry()); } } } } else { // There is no entry in the merge commit. Means: we want to delete // what's currently in the index and working tree if (f != null) { // There is a file/folder for that path in the working tree if (walk.IsDirectoryFileConflict()) { conflicts.AddItem(walk.PathString); } else { // No file/folder conflict exists. All entries are files or // all entries are folders if (i != null) { // ... and the working tree contained a file or folder // -> add it to the removed set and remove it from // conflicts set Remove(i.EntryPathString); conflicts.Remove(i.EntryPathString); } } } } }
/// <summary>Here the main work is done.</summary> /// <remarks> /// Here the main work is done. This method is called for each existing path /// in head, index and merge. This method decides what to do with the /// corresponding index entry: keep it, update it, remove it or mark a /// conflict. /// </remarks> /// <param name="h">the entry for the head</param> /// <param name="m">the entry for the merge</param> /// <param name="i">the entry for the index</param> /// <param name="f">the file in the working tree</param> /// <exception cref="System.IO.IOException">System.IO.IOException</exception> internal virtual void ProcessEntry(CanonicalTreeParser h, CanonicalTreeParser m, DirCacheBuildIterator i, WorkingTreeIterator f) { DirCacheEntry dce = i != null ? i.GetDirCacheEntry() : null; string name = walk.PathString; if (m != null && !IsValidPath(m)) { throw new InvalidPathException(m.EntryPathString); } if (i == null && m == null && h == null) { // File/Directory conflict case #20 if (walk.IsDirectoryFileConflict()) { // TODO: check whether it is always correct to report a conflict here Conflict(name, null, null, null); } // file only exists in working tree -> ignore it return; } ObjectId iId = (i == null ? null : i.EntryObjectId); ObjectId mId = (m == null ? null : m.EntryObjectId); ObjectId hId = (h == null ? null : h.EntryObjectId); FileMode iMode = (i == null ? null : i.EntryFileMode); FileMode mMode = (m == null ? null : m.EntryFileMode); FileMode hMode = (h == null ? null : h.EntryFileMode); // The information whether head,index,merge iterators are currently // pointing to file/folder/non-existing is encoded into this variable. // // To decode write down ffMask in hexadecimal form. The last digit // represents the state for the merge iterator, the second last the // state for the index iterator and the third last represents the state // for the head iterator. The hexadecimal constant "F" stands for // "file", // an "D" stands for "directory" (tree), and a "0" stands for // non-existing // // Examples: // ffMask == 0xFFD -> Head=File, Index=File, Merge=Tree // ffMask == 0xDD0 -> Head=Tree, Index=Tree, Merge=Non-Existing int ffMask = 0; if (h != null) { ffMask = FileMode.TREE.Equals(hMode) ? unchecked((int)(0xD00)) : unchecked((int)( 0xF00)); } if (i != null) { ffMask |= FileMode.TREE.Equals(iMode) ? unchecked((int)(0x0D0)) : unchecked((int) (0x0F0)); } if (m != null) { ffMask |= FileMode.TREE.Equals(mMode) ? unchecked((int)(0x00D)) : unchecked((int) (0x00F)); } // Check whether we have a possible file/folder conflict. Therefore we // need a least one file and one folder. if (((ffMask & unchecked((int)(0x222))) != unchecked((int)(0x000))) && (((ffMask & unchecked((int)(0x00F))) == unchecked((int)(0x00D))) || ((ffMask & unchecked(( int)(0x0F0))) == unchecked((int)(0x0D0))) || ((ffMask & unchecked((int)(0xF00))) == unchecked((int)(0xD00))))) { switch (ffMask) { case unchecked((int)(0xDDF)): { // There are 3*3*3=27 possible combinations of file/folder // conflicts. Some of them are not-relevant because // they represent no conflict, e.g. 0xFFF, 0xDDD, ... The following // switch processes all relevant cases. // 1 2 if (IsModified(name)) { Conflict(name, dce, h, m); } else { // 1 Update(name, mId, mMode); } // 2 break; } case unchecked((int)(0xDFD)): { // 3 4 // CAUTION: I put it into removed instead of updated, because // that's what our tests expect // updated.put(name, mId); Remove(name); break; } case unchecked((int)(0xF0D)): { // 18 Remove(name); break; } case unchecked((int)(0xDFF)): case unchecked((int)(0xFDD)): { // 5 6 // 10 11 // TODO: make use of tree extension as soon as available in jgit // we would like to do something like // if (!equalIdAndMode(iId, iMode, mId, mMode) // conflict(name, i.getDirCacheEntry(), h, m); // But since we don't know the id of a tree in the index we do // nothing here and wait that conflicts between index and merge // are found later break; } case unchecked((int)(0xD0F)): { // 19 Update(name, mId, mMode); break; } case unchecked((int)(0xDF0)): case unchecked((int)(0x0FD)): { // conflict without a rule // 15 Conflict(name, dce, h, m); break; } case unchecked((int)(0xFDF)): { // 7 8 9 if (EqualIdAndMode(hId, hMode, mId, mMode)) { if (IsModified(name)) { Conflict(name, dce, h, m); } else { // 8 Update(name, mId, mMode); } } else { // 7 if (!IsModified(name)) { Update(name, mId, mMode); } else { // 9 // To be confirmed - this case is not in the table. Conflict(name, dce, h, m); } } break; } case unchecked((int)(0xFD0)): { // keep without a rule Keep(dce); break; } case unchecked((int)(0xFFD)): { // 12 13 14 if (EqualIdAndMode(hId, hMode, iId, iMode)) { if (f == null || f.IsModified(dce, true)) { Conflict(name, dce, h, m); } else { Remove(name); } } else { Conflict(name, dce, h, m); } break; } case unchecked((int)(0x0DF)): { // 16 17 if (!IsModified(name)) { Update(name, mId, mMode); } else { Conflict(name, dce, h, m); } break; } default: { Keep(dce); break; } } return; } // if we have no file at all then there is nothing to do if ((ffMask & unchecked((int)(0x222))) == 0) { return; } if ((ffMask == unchecked((int)(0x00F))) && f != null && FileMode.TREE.Equals(f.EntryFileMode )) { // File/Directory conflict case #20 Conflict(name, null, h, m); } if (i == null) { // make sure not to overwrite untracked files if (f != null) { // A submodule is not a file. We should ignore it if (!FileMode.GITLINK.Equals(mMode)) { // a dirty worktree: the index is empty but we have a // workingtree-file if (mId == null || !EqualIdAndMode(mId, mMode, f.EntryObjectId, f.EntryFileMode)) { Conflict(name, null, h, m); return; } } } if (h == null) { Update(name, mId, mMode); } else { // 1 if (m == null) { Remove(name); } else { // 2 Update(name, mId, mMode); } } } else { // 3 if (h == null) { if (m == null || EqualIdAndMode(mId, mMode, iId, iMode)) { if (m == null && walk.IsDirectoryFileConflict()) { if (dce != null && (f == null || f.IsModified(dce, true))) { Conflict(name, dce, h, m); } else { Remove(name); } } else { Keep(dce); } } else { Conflict(name, dce, h, m); } } else { if (m == null) { if (iMode == FileMode.GITLINK) { // Submodules that disappear from the checkout must // be removed from the index, but not deleted from disk. Remove(name); } else { if (EqualIdAndMode(hId, hMode, iId, iMode)) { if (f == null || f.IsModified(dce, true)) { Conflict(name, dce, h, m); } else { Remove(name); } } else { Conflict(name, dce, h, m); } } } else { if (!EqualIdAndMode(hId, hMode, mId, mMode) && !EqualIdAndMode(hId, hMode, iId, iMode ) && !EqualIdAndMode(mId, mMode, iId, iMode)) { Conflict(name, dce, h, m); } else { if (EqualIdAndMode(hId, hMode, iId, iMode) && !EqualIdAndMode(mId, mMode, iId, iMode )) { // For submodules just update the index with the new SHA-1 if (dce != null && FileMode.GITLINK.Equals(dce.FileMode)) { Update(name, mId, mMode); } else { if (dce != null && (f == null || f.IsModified(dce, true))) { Conflict(name, dce, h, m); } else { Update(name, mId, mMode); } } } else { Keep(dce); } } } } } }
/// <summary> /// Processing an entry in the context of /// <see cref="PrescanOneTree()">PrescanOneTree()</see> /// when only /// one tree is given /// </summary> /// <param name="m">the tree to merge</param> /// <param name="i">the index</param> /// <param name="f">the working tree</param> /// <exception cref="System.IO.IOException">System.IO.IOException</exception> internal virtual void ProcessEntry(CanonicalTreeParser m, DirCacheBuildIterator i , WorkingTreeIterator f) { if (m != null) { // There is an entry in the merge commit. Means: we want to update // what's currently in the index and working-tree to that one if (i == null) { // The index entry is missing if (f != null && !FileMode.TREE.Equals(f.EntryFileMode) && !f.IsEntryIgnored()) { // don't overwrite an untracked and not ignored file conflicts.AddItem(walk.PathString); } else { Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } } else { if (f == null || !m.IdEqual(i)) { // The working tree file is missing or the merge content differs // from index content Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } else { if (i.GetDirCacheEntry() != null) { // The index contains a file (and not a folder) if (f.IsModified(i.GetDirCacheEntry(), true) || i.GetDirCacheEntry().Stage != 0) { // The working tree file is dirty or the index contains a // conflict Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } else { Keep(i.GetDirCacheEntry()); } } else { // The index contains a folder Keep(i.GetDirCacheEntry()); } } } } else { // There is no entry in the merge commit. Means: we want to delete // what's currently in the index and working tree if (f != null) { // There is a file/folder for that path in the working tree if (walk.IsDirectoryFileConflict()) { conflicts.AddItem(walk.PathString); } else { // No file/folder conflict exists. All entries are files or // all entries are folders if (i != null) { // ... and the working tree contained a file or folder // -> add it to the removed set and remove it from // conflicts set Remove(i.EntryPathString); conflicts.Remove(i.EntryPathString); } } } else { // untracked file, neither contained in tree to merge // nor in index // There is no file/folder for that path in the working tree. // The only entry we have is the index entry. If that entry is a // conflict simply remove it. Otherwise keep that entry in the // index if (i.GetDirCacheEntry().Stage == 0) { Keep(i.GetDirCacheEntry()); } } } }
/// <summary> /// Processing an entry in the context of /// <see cref="PrescanOneTree()">PrescanOneTree()</see> /// when only /// one tree is given /// </summary> /// <param name="m">the tree to merge</param> /// <param name="i">the index</param> /// <param name="f">the working tree</param> internal virtual void ProcessEntry(CanonicalTreeParser m, DirCacheBuildIterator i , WorkingTreeIterator f) { if (m != null) { if (i == null || f == null || !m.IdEqual(i) || (i.GetDirCacheEntry() != null && ( f.IsModified(i.GetDirCacheEntry(), true) || i.GetDirCacheEntry().Stage != 0))) { Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } else { Keep(i.GetDirCacheEntry()); } } else { if (f != null) { if (walk.IsDirectoryFileConflict()) { conflicts.AddItem(walk.PathString); } else { if (i != null) { // ... and the working dir contained a file or folder -> // add it to the removed set and remove it from // conflicts set Remove(i.EntryPathString); conflicts.Remove(i.EntryPathString); } } } else { if (i.GetDirCacheEntry().Stage == 0) { Keep(i.GetDirCacheEntry()); } } } }
/// <summary>Run the diff operation.</summary> /// <remarks> /// Run the diff operation. Until this is called, all lists will be empty. /// <p> /// The operation may be aborted by the progress monitor. In that event it /// will report what was found before the cancel operation was detected. /// Callers should ignore the result if monitor.isCancelled() is true. If a /// progress monitor is not needed, callers should use /// <see cref="Diff()">Diff()</see> /// instead. Progress reporting is crude and approximate and only intended /// for informing the user. /// </remarks> /// <param name="monitor">for reporting progress, may be null</param> /// <param name="estWorkTreeSize">number or estimated files in the working tree</param> /// <param name="estIndexSize">number of estimated entries in the cache</param> /// <param name="title"></param> /// <returns>if anything is different between index, tree, and workdir</returns> /// <exception cref="System.IO.IOException">System.IO.IOException</exception> public virtual bool Diff(ProgressMonitor monitor, int estWorkTreeSize, int estIndexSize , string title) { dirCache = repository.ReadDirCache(); TreeWalk treeWalk = new TreeWalk(repository); treeWalk.Recursive = true; // add the trees (tree, dirchache, workdir) if (tree != null) { treeWalk.AddTree(tree); } else { treeWalk.AddTree(new EmptyTreeIterator()); } treeWalk.AddTree(new DirCacheIterator(dirCache)); treeWalk.AddTree(initialWorkingTreeIterator); ICollection <TreeFilter> filters = new AList <TreeFilter>(4); if (monitor != null) { // Get the maximum size of the work tree and index // and add some (quite arbitrary) if (estIndexSize == 0) { estIndexSize = dirCache.GetEntryCount(); } int total = Math.Max(estIndexSize * 10 / 9, estWorkTreeSize * 10 / 9); monitor.BeginTask(title, total); filters.AddItem(new IndexDiff.ProgressReportingFilter(monitor, total)); } if (filter != null) { filters.AddItem(filter); } filters.AddItem(new SkipWorkTreeFilter(INDEX)); filters.AddItem(new IndexDiffFilter(INDEX, WORKDIR)); treeWalk.Filter = AndTreeFilter.Create(filters); while (treeWalk.Next()) { AbstractTreeIterator treeIterator = treeWalk.GetTree <AbstractTreeIterator>(TREE); DirCacheIterator dirCacheIterator = treeWalk.GetTree <DirCacheIterator>(INDEX); WorkingTreeIterator workingTreeIterator = treeWalk.GetTree <WorkingTreeIterator>(WORKDIR ); if (dirCacheIterator != null) { DirCacheEntry dirCacheEntry = dirCacheIterator.GetDirCacheEntry(); if (dirCacheEntry != null && dirCacheEntry.Stage > 0) { conflicts.AddItem(treeWalk.PathString); continue; } } if (treeIterator != null) { if (dirCacheIterator != null) { if (!treeIterator.IdEqual(dirCacheIterator) || treeIterator.EntryRawMode != dirCacheIterator .EntryRawMode) { // in repo, in index, content diff => changed changed.AddItem(treeWalk.PathString); } } else { // in repo, not in index => removed removed.AddItem(treeWalk.PathString); if (workingTreeIterator != null) { untracked.AddItem(treeWalk.PathString); } } } else { if (dirCacheIterator != null) { // not in repo, in index => added added.AddItem(treeWalk.PathString); } else { // not in repo, not in index => untracked if (workingTreeIterator != null && !workingTreeIterator.IsEntryIgnored()) { untracked.AddItem(treeWalk.PathString); } } } if (dirCacheIterator != null) { if (workingTreeIterator == null) { // in index, not in workdir => missing missing.AddItem(treeWalk.PathString); } else { if (workingTreeIterator.IsModified(dirCacheIterator.GetDirCacheEntry(), true)) { // in index, in workdir, content differs => modified modified.AddItem(treeWalk.PathString); } } } } // consume the remaining work if (monitor != null) { monitor.EndTask(); } if (added.IsEmpty() && changed.IsEmpty() && removed.IsEmpty() && missing.IsEmpty( ) && modified.IsEmpty() && untracked.IsEmpty()) { return(false); } else { return(true); } }
/// <exception cref="NGit.Errors.MissingObjectException"></exception> /// <exception cref="NGit.Errors.IncorrectObjectTypeException"></exception> /// <exception cref="System.IO.IOException"></exception> public override bool Include(TreeWalk tw) { // If the working tree file doesn't exist, it does exist for at least // one other so include this difference. int wm = tw.GetRawMode(workingTree); if (wm == 0) { return(true); } // If the path does not appear in the DirCache and its ignored // we can avoid returning a result here, but only if its not in any // other tree. int cnt = tw.TreeCount; int dm = tw.GetRawMode(dirCache); if (dm == 0) { if (honorIgnores && WorkingTree(tw).IsEntryIgnored()) { int i = 0; for (; i < cnt; i++) { if (i == dirCache || i == workingTree) { continue; } if (tw.GetRawMode(i) != 0) { break; } } // If i is cnt then the path does not appear in any other tree, // and this working tree entry can be safely ignored. return(i == cnt ? false : true); } else { // In working tree and not ignored, and not in DirCache. return(true); } } // Always include subtrees as WorkingTreeIterator cannot provide // efficient elimination of unmodified subtrees. if (tw.IsSubtree) { return(true); } // Try the inexpensive comparisons between index and all real trees // first. Only if we don't find a diff here we have to bother with // the working tree for (int i_1 = 0; i_1 < cnt; i_1++) { if (i_1 == dirCache || i_1 == workingTree) { continue; } if (tw.GetRawMode(i_1) != dm || !tw.IdEqual(i_1, dirCache)) { return(true); } } // Only one chance left to detect a diff: between index and working // tree. Make use of the WorkingTreeIterator#isModified() method to // avoid computing SHA1 on filesystem content if not really needed. WorkingTreeIterator wi = WorkingTree(tw); DirCacheIterator di = tw.GetTree <DirCacheIterator>(dirCache); return(wi.IsModified(di.GetDirCacheEntry(), true)); }
/// <summary>Here the main work is done.</summary> /// <remarks> /// Here the main work is done. This method is called for each existing path /// in head, index and merge. This method decides what to do with the /// corresponding index entry: keep it, update it, remove it or mark a /// conflict. /// </remarks> /// <param name="h">the entry for the head</param> /// <param name="m">the entry for the merge</param> /// <param name="i">the entry for the index</param> /// <param name="f">the file in the working tree</param> /// <exception cref="System.IO.IOException">System.IO.IOException</exception> internal virtual void ProcessEntry(AbstractTreeIterator h, AbstractTreeIterator m , DirCacheBuildIterator i, WorkingTreeIterator f) { DirCacheEntry dce; string name = walk.PathString; if (i == null && m == null && h == null) { // File/Directory conflict case #20 if (walk.IsDirectoryFileConflict()) { // TODO: check whether it is always correct to report a conflict here Conflict(name, null, null, null); } // file only exists in working tree -> ignore it return; } ObjectId iId = (i == null ? null : i.EntryObjectId); ObjectId mId = (m == null ? null : m.EntryObjectId); ObjectId hId = (h == null ? null : h.EntryObjectId); // The information whether head,index,merge iterators are currently // pointing to file/folder/non-existing is encoded into this variable. // // To decode write down ffMask in hexadecimal form. The last digit // represents the state for the merge iterator, the second last the // state for the index iterator and the third last represents the state // for the head iterator. The hexadecimal constant "F" stands for // "file", // an "D" stands for "directory" (tree), and a "0" stands for // non-existing // // Examples: // ffMask == 0xFFD -> Head=File, Index=File, Merge=Tree // ffMask == 0xDD0 -> Head=Tree, Index=Tree, Merge=Non-Existing int ffMask = 0; if (h != null) { ffMask = FileMode.TREE.Equals(h.EntryFileMode) ? unchecked ((int)(0xD00)) : unchecked ( (int)(0xF00)); } if (i != null) { ffMask |= FileMode.TREE.Equals(i.EntryFileMode) ? unchecked ((int)(0x0D0)) : unchecked ( (int)(0x0F0)); } if (m != null) { ffMask |= FileMode.TREE.Equals(m.EntryFileMode) ? unchecked ((int)(0x00D)) : unchecked ( (int)(0x00F)); } // Check whether we have a possible file/folder conflict. Therefore we // need a least one file and one folder. if (((ffMask & unchecked ((int)(0x222))) != unchecked ((int)(0x000))) && (((ffMask & unchecked ((int)(0x00F))) == unchecked ((int)(0x00D))) || ((ffMask & unchecked (( int)(0x0F0))) == unchecked ((int)(0x0D0))) || ((ffMask & unchecked ((int)(0xF00))) == unchecked ((int)(0xD00))))) { switch (ffMask) { case unchecked ((int)(0xDDF)): { // There are 3*3*3=27 possible combinations of file/folder // conflicts. Some of them are not-relevant because // they represent no conflict, e.g. 0xFFF, 0xDDD, ... The following // switch processes all relevant cases. // 1 2 if (IsModified(name)) { Conflict(name, i.GetDirCacheEntry(), h, m); } else { // 1 Update(name, m.EntryObjectId, m.EntryFileMode); } // 2 break; } case unchecked ((int)(0xDFD)): { // 3 4 // CAUTION: I put it into removed instead of updated, because // that's what our tests expect // updated.put(name, mId); Remove(name); break; } case unchecked ((int)(0xF0D)): { // 18 Remove(name); break; } case unchecked ((int)(0xDFF)): case unchecked ((int)(0xFDD)): { // 5 6 // 10 11 // TODO: make use of tree extension as soon as available in jgit // we would like to do something like // if (!iId.equals(mId)) // conflict(name, i.getDirCacheEntry(), h, m); // But since we don't know the id of a tree in the index we do // nothing here and wait that conflicts between index and merge // are found later break; } case unchecked ((int)(0xD0F)): { // 19 Update(name, mId, m.EntryFileMode); break; } case unchecked ((int)(0xDF0)): case unchecked ((int)(0x0FD)): { // conflict without a rule // 15 Conflict(name, (i != null) ? i.GetDirCacheEntry() : null, h, m); break; } case unchecked ((int)(0xFDF)): { // 7 8 9 if (hId.Equals(mId)) { if (IsModified(name)) { Conflict(name, i.GetDirCacheEntry(), h, m); } else { // 8 Update(name, mId, m.EntryFileMode); } } else { // 7 if (!IsModified(name)) { Update(name, mId, m.EntryFileMode); } else { // 9 // To be confirmed - this case is not in the table. Conflict(name, i.GetDirCacheEntry(), h, m); } } break; } case unchecked ((int)(0xFD0)): { // keep without a rule Keep(i.GetDirCacheEntry()); break; } case unchecked ((int)(0xFFD)): { // 12 13 14 if (hId.Equals(iId)) { dce = i.GetDirCacheEntry(); if (f == null || f.IsModified(dce, true)) { Conflict(name, i.GetDirCacheEntry(), h, m); } else { Remove(name); } } else { Conflict(name, i.GetDirCacheEntry(), h, m); } break; } case unchecked ((int)(0x0DF)): { // 16 17 if (!IsModified(name)) { Update(name, mId, m.EntryFileMode); } else { Conflict(name, i.GetDirCacheEntry(), h, m); } break; } default: { Keep(i.GetDirCacheEntry()); break; } } return; } // if we have no file at all then there is nothing to do if ((ffMask & unchecked ((int)(0x222))) == 0) { return; } if ((ffMask == unchecked ((int)(0x00F))) && f != null && FileMode.TREE.Equals(f.EntryFileMode )) { // File/Directory conflict case #20 Conflict(name, null, h, m); } if (i == null) { // make sure not to overwrite untracked files if (f != null) { // a dirty worktree: the index is empty but we have a // workingtree-file if (mId == null || !mId.Equals(f.EntryObjectId)) { Conflict(name, null, h, m); return; } } if (h == null) { Update(name, mId, m.EntryFileMode); } else { // 1 if (m == null) { Remove(name); } else { // 2 Update(name, mId, m.EntryFileMode); } } } else { // 3 dce = i.GetDirCacheEntry(); if (h == null) { if (m == null || mId.Equals(iId)) { if (m == null && walk.IsDirectoryFileConflict()) { if (dce != null && (f == null || f.IsModified(dce, true))) { Conflict(name, i.GetDirCacheEntry(), h, m); } else { Remove(name); } } else { Keep(i.GetDirCacheEntry()); } } else { Conflict(name, i.GetDirCacheEntry(), h, m); } } else { if (m == null) { if (hId.Equals(iId)) { if (f == null || f.IsModified(dce, true)) { Conflict(name, i.GetDirCacheEntry(), h, m); } else { Remove(name); } } else { Conflict(name, i.GetDirCacheEntry(), h, m); } } else { if (!hId.Equals(mId) && !hId.Equals(iId) && !mId.Equals(iId)) { Conflict(name, i.GetDirCacheEntry(), h, m); } else { if (hId.Equals(iId) && !mId.Equals(iId)) { if (dce != null && (f == null || f.IsModified(dce, true))) { Conflict(name, i.GetDirCacheEntry(), h, m); } else { Update(name, mId, m.EntryFileMode); } } else { Keep(i.GetDirCacheEntry()); } } } } } }
/// <summary> /// Processing an entry in the context of /// <see cref="PrescanOneTree()">PrescanOneTree()</see> /// when only /// one tree is given /// </summary> /// <param name="m">the tree to merge</param> /// <param name="i">the index</param> /// <param name="f">the working tree</param> /// <exception cref="System.IO.IOException">System.IO.IOException</exception> internal virtual void ProcessEntry(CanonicalTreeParser m, DirCacheBuildIterator i , WorkingTreeIterator f) { if (m != null) { // There is an entry in the merge commit. Means: we want to update // what's currently in the index and working-tree to that one if (i == null) { // The index entry is missing if (f != null && !FileMode.TREE.Equals(f.EntryFileMode) && !f.IsEntryIgnored()) { // don't overwrite an untracked and not ignored file conflicts.AddItem(walk.PathString); } else { Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } } else { if (f == null || !m.IdEqual(i)) { // The working tree file is missing or the merge content differs // from index content Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } else { if (i.GetDirCacheEntry() != null) { // The index contains a file (and not a folder) if (f.IsModified(i.GetDirCacheEntry(), true) || i.GetDirCacheEntry().Stage != 0) { // The working tree file is dirty or the index contains a // conflict Update(m.EntryPathString, m.EntryObjectId, m.EntryFileMode); } else { Keep(i.GetDirCacheEntry()); } } else { // The index contains a folder Keep(i.GetDirCacheEntry()); } } } } else { // There is no entry in the merge commit. Means: we want to delete // what's currently in the index and working tree if (f != null) { // There is a file/folder for that path in the working tree if (walk.IsDirectoryFileConflict()) { conflicts.AddItem(walk.PathString); } else { // No file/folder conflict exists. All entries are files or // all entries are folders if (i != null) { // ... and the working tree contained a file or folder // -> add it to the removed set and remove it from // conflicts set Remove(i.EntryPathString); conflicts.Remove(i.EntryPathString); } } } else { // untracked file, neither contained in tree to merge // nor in index // There is no file/folder for that path in the working tree. // The only entry we have is the index entry. If that entry is a // conflict simply remove it. Otherwise keep that entry in the // index if (i.GetDirCacheEntry().Stage == 0) { Keep(i.GetDirCacheEntry()); } } } }
/// <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 occured /// </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 modeI = tw.GetRawMode(T_INDEX); // Each index entry has to match ours, means: it has to be clean if (NonTree(modeI) && !(tw.IdEqual(T_INDEX, T_OURS) && modeO == modeI)) { failingPaths.Put(tw.PathString, ResolveMerger.MergeFailureReason.DIRTY_INDEX); return(false); } int modeT = tw.GetRawMode(T_THEIRS); if (NonTree(modeO) && modeO == modeT && tw.IdEqual(T_OURS, T_THEIRS)) { // ours and theirs are equal: it doesn'nt matter // which one we choose. OURS is choosen here. Add(tw.RawPath, ours, DirCacheEntry.STAGE_0); // no checkout needed! return(true); } int modeB = tw.GetRawMode(T_BASE); if (NonTree(modeO) && modeB == modeT && tw.IdEqual(T_BASE, T_THEIRS)) { // THEIRS was not changed compared to base. All changes must be in // OURS. Choose OURS. Add(tw.RawPath, ours, DirCacheEntry.STAGE_0); return(true); } if (modeB == modeO && tw.IdEqual(T_BASE, T_OURS)) { // OURS was not changed compared to base. All changes must be in // THEIRS. Choose THEIRS. 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)) { if (!inCore) { // We are going to update the worktree. Make sure the worktree // is not modified if (work != null && (!NonTree(work.EntryRawMode) || work.IsModified(index.GetDirCacheEntry (), true))) { failingPaths.Put(tw.PathString, ResolveMerger.MergeFailureReason.DIRTY_WORKTREE); return(false); } } if (!ContentMerge(@base, ours, theirs)) { unmergedPaths.AddItem(tw.PathString); } modifiedFiles.AddItem(tw.PathString); } return(true); }