diff options
author | Alan McGovern <alan.mcgovern@gmail.com> | 2012-04-26 23:42:46 +0400 |
---|---|---|
committer | Alan McGovern <alan.mcgovern@gmail.com> | 2012-04-27 21:29:35 +0400 |
commit | 75a7a23da86bf77a51f8c31cebd60c73bc725c3b (patch) | |
tree | 90c44e41b831005f366242c0434fa6d7f02dce91 /main/src/addins/VersionControl | |
parent | e0d6b290dd28ce95fa493fd94a7620fe11bfbcad (diff) |
[VersionControl] Fix issues with git log not following renamed files
Remove a bit of hacky code which was used to generate the fiels which
were changed in a given commit and use some built in tools NGit offers
nowadays. This allows us to follow renames pretty trivially and also
cleans up some custom code.
I've added some new unit tests covering the changes.
Fixes bug #4655
Diffstat (limited to 'main/src/addins/VersionControl')
5 files changed, 51 insertions, 284 deletions
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/Makefile.am b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/Makefile.am index 0aa1d85293..aac89b1279 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/Makefile.am +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/Makefile.am @@ -38,7 +38,6 @@ FILES = \ gtk-gui/MonoDevelop.VersionControl.Git.PushDialog.cs \ gtk-gui/MonoDevelop.VersionControl.Git.StashManagerDialog.cs \ gtk-gui/MonoDevelop.VersionControl.Git.UserInfoConflictDialog.cs \ - MonoDevelop.VersionControl.Git/Change.cs \ MonoDevelop.VersionControl.Git/Commands.cs \ MonoDevelop.VersionControl.Git/ConflictResolutionDialog.cs \ MonoDevelop.VersionControl.Git/CredentialsDialog.cs \ diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj index 962386397d..4377ecb178 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj @@ -70,7 +70,6 @@ <Compile Include="MonoDevelop.VersionControl.Git\EditRemoteDialog.cs" /> <Compile Include="gtk-gui\MonoDevelop.VersionControl.Git.EditRemoteDialog.cs" /> <Compile Include="gtk-gui\MonoDevelop.VersionControl.Git.MergeDialog.cs" /> - <Compile Include="MonoDevelop.VersionControl.Git\Change.cs" /> <Compile Include="MonoDevelop.VersionControl.Git\MergeDialog.cs" /> <Compile Include="MonoDevelop.VersionControl.Git\Stash.cs" /> <Compile Include="MonoDevelop.VersionControl.Git\GitUtil.cs" /> diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/Change.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/Change.cs deleted file mode 100644 index 4ffedc4df4..0000000000 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/Change.cs +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2009-2010, Henon <meinrad.recheis@gmail.com> - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Git Development Community nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using NGit; -using NGit.Revwalk; - -namespace MonoDevelop.VersionControl.Git -{ - - public enum ChangeType - { - Added, Deleted, Modified, TypeChanged, Renamed, Copied - } - - /// <summary> - /// Represents a change of a single file between two commits. Use Commit.Diff to get a list of Change objects. - /// </summary> - public class Change - { - - /// <summary> - /// The commit that serves as reference for this comparison. The change reflects the difference of the other commit against this ReferenceCommit. - /// </summary> - public RevCommit ReferenceCommit - { - get; - internal set; - } - - /// <summary> - /// The commit which is compared against the ReferenceCommit. - /// </summary> - public RevCommit ComparedCommit - { - get; - internal set; - } - - /// <summary> - /// The kind of change (Added, Modified, Deleted, etc. ) - /// </summary> - public ChangeType ChangeType { get; internal set; } - - /// <summary> - /// The revision of the file from the ReferenceCommit. It may be null in some cases i.e. for ChangeType.Added - /// </summary> - public ObjectId ReferenceObject { get; internal set; } - - /// <summary> - /// The revision of the file from the ComparedCommit. It may be null in some cases i.e. for ChangeType.Removed - /// </summary> - public ObjectId ComparedObject { get; internal set; } - - /// <summary> - /// The file (i.e. Blob) this Change is according to. - /// Always returns a non-null revision of the file, no matter what kind of change. It normally returns the ComparedCommit's version of the changed - /// object except for ChangeType.Removed where it returns the ReferenceCommit's version of the object. - /// - /// This property is designed to release the calling code from null checking and revision selection and may be especially useful for GUI bindings. - /// </summary> - public ObjectId ChangedObject - { - get - { - if (ComparedObject != null) - return ComparedObject; - else - return ReferenceObject; - } - } - - /// <summary> - /// The filepath of the ChangedObject - /// </summary> - public string Path { get; internal set; } - - /// <summary> - /// The filename of the ChangedObject - /// </summary> - public string Name { get; internal set; } - - /// <summary> - /// Unix file permissions of the ReferenceCommit's version of the object - /// </summary> - public int ReferencePermissions - { - get; - internal set; - } - - /// <summary> - /// Unix file permissions of the ComparedCommit's version of the object - /// </summary> - public int ComparedPermissions { get; internal set; } - - /// <summary> - /// Returns ReferenceCommit and ComparedCommit as array - /// </summary> - public RevCommit[] Commits - { - get - { - return new RevCommit[] { ReferenceCommit, ComparedCommit }; - } - } - - /// <summary> - /// Returns ReferenceObject and ComparedObject as array - /// </summary> - public ObjectId[] Objects - { - get - { - return new ObjectId[] { ReferenceObject, ComparedObject }; - } - } - - /// <summary> - /// Returns ReferenceObject's and ComparedObject's permissions as array - /// </summary> - public int[] Permissions - { - get - { - return new int[] { ReferencePermissions, ComparedPermissions }; - } - } - - public override string ToString() - { - return string.Format("{0} [{1}]", ChangeType, Path); - } - - } - -} diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs index 33283273e7..0fb237b239 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs @@ -183,27 +183,15 @@ namespace MonoDevelop.VersionControl.Git GitRevision rev = (GitRevision) revision; if (rev.Commit == null) return new RevisionPath [0]; - + List<RevisionPath> paths = new List<RevisionPath> (); - - foreach (Change change in GitUtil.GetCommitChanges (repo, rev.Commit)) { - FilePath cpath = FromGitPath (change.Path); - if (!rev.FileForChanges.IsNull && cpath != rev.FileForChanges && !cpath.IsChildPathOf (rev.FileForChanges)) - continue; - RevisionAction ra; - switch (change.ChangeType) { - case ChangeType.Added: - ra = RevisionAction.Add; - break; - case ChangeType.Deleted: - ra = RevisionAction.Delete; - break; - default: - ra = RevisionAction.Modify; - break; - } - RevisionPath p = new RevisionPath (cpath, ra, null); - paths.Add (p); + foreach (var entry in GitUtil.GetCommitChanges (repo, rev.Commit)) { + if (entry.GetChangeType () == DiffEntry.ChangeType.ADD) + paths.Add (new RevisionPath (FromGitPath (entry.GetNewPath ()), RevisionAction.Add, null)); + if (entry.GetChangeType () == DiffEntry.ChangeType.DELETE) + paths.Add (new RevisionPath (FromGitPath (entry.GetOldPath ()), RevisionAction.Delete, null)); + if (entry.GetChangeType () == DiffEntry.ChangeType.MODIFY) + paths.Add (new RevisionPath (FromGitPath (entry.GetNewPath ()), RevisionAction.Modify, null)); } return paths.ToArray (); } @@ -359,7 +347,7 @@ namespace MonoDevelop.VersionControl.Git public override void Update (FilePath[] localPaths, bool recurse, IProgressMonitor monitor) { - IEnumerable<Change> statusList = null; + IEnumerable<DiffEntry> statusList = null; monitor.BeginTask (GettextCatalog.GetString ("Updating"), 5); @@ -475,7 +463,7 @@ namespace MonoDevelop.VersionControl.Git public void Merge (string branch, bool saveLocalChanges, IProgressMonitor monitor) { - IEnumerable<Change> statusList = null; + IEnumerable<DiffEntry> statusList = null; Stash stash = null; StashCollection stashes = new StashCollection (repo); monitor.BeginTask (null, 4); @@ -1087,7 +1075,7 @@ namespace MonoDevelop.VersionControl.Git monitor.BeginTask (GettextCatalog.GetString ("Switching to branch {0}", branch), GitService.StashUnstashWhenSwitchingBranches ? 4 : 2); // Get a list of files that are different in the target branch - IEnumerable<Change> statusList = GitUtil.GetChangedFiles (repo, branch); + IEnumerable<DiffEntry> statusList = GitUtil.GetChangedFiles (repo, branch); StashCollection stashes = null; Stash stash = null; @@ -1157,13 +1145,13 @@ namespace MonoDevelop.VersionControl.Git monitor.EndTask (); } - void NotifyFileChanges (IProgressMonitor monitor, IEnumerable<Change> statusList) + void NotifyFileChanges (IProgressMonitor monitor, IEnumerable<DiffEntry> statusList) { - List<Change> changes = new List<Change> (statusList); + List<DiffEntry> changes = new List<DiffEntry> (statusList); // Files added to source branch not present to target branch. - var removed = changes.Where (c => c.ChangeType == ChangeType.Added).Select (c => FromGitPath (c.Path)).ToList (); - var modified = changes.Where (c => c.ChangeType != ChangeType.Added).Select (c => FromGitPath (c.Path)).ToList (); + var removed = changes.Where (c => c.GetChangeType () == DiffEntry.ChangeType.ADD).Select (c => FromGitPath (c.GetNewPath ())).ToList (); + var modified = changes.Where (c => c.GetChangeType () != DiffEntry.ChangeType.ADD).Select (c => FromGitPath (c.GetNewPath ())).ToList (); monitor.BeginTask (GettextCatalog.GetString ("Updating solution"), removed.Count + modified.Count); @@ -1208,20 +1196,20 @@ namespace MonoDevelop.VersionControl.Git RevCommit c1 = rw.ParseCommit (cid1); RevCommit c2 = rw.ParseCommit (cid2); - foreach (Change change in GitUtil.CompareCommits (repo, c2, c1)) { + foreach (var change in GitUtil.CompareCommits (repo, c2, c1)) { VersionStatus status; - switch (change.ChangeType) { - case ChangeType.Added: + switch (change.GetChangeType ()) { + case DiffEntry.ChangeType.ADD: status = VersionStatus.ScheduledAdd; break; - case ChangeType.Deleted: + case DiffEntry.ChangeType.DELETE: status = VersionStatus.ScheduledDelete; break; default: status = VersionStatus.Modified; break; } - VersionInfo vi = new VersionInfo (FromGitPath (change.Path), "", false, status | VersionStatus.Versioned, null, VersionStatus.Versioned, null); + VersionInfo vi = new VersionInfo (FromGitPath (change.GetNewPath ()), "", false, status | VersionStatus.Versioned, null, VersionStatus.Versioned, null); cset.AddFile (vi); } return cset; @@ -1242,20 +1230,20 @@ namespace MonoDevelop.VersionControl.Git RevCommit c2 = rw.ParseCommit (cid2); List<DiffInfo> diffs = new List<DiffInfo> (); - foreach (Change change in GitUtil.CompareCommits (repo, c1, c2)) { + foreach (var change in GitUtil.CompareCommits (repo, c1, c2)) { string diff; - switch (change.ChangeType) { - case ChangeType.Deleted: - diff = GenerateDiff (EmptyContent, GetCommitContent (c2, change.Path)); + switch (change.GetChangeType ()) { + case DiffEntry.ChangeType.DELETE: + diff = GenerateDiff (EmptyContent, GetCommitContent (c2, change.GetOldPath ())); break; - case ChangeType.Added: - diff = GenerateDiff (GetCommitContent (c1, change.Path), EmptyContent); + case DiffEntry.ChangeType.ADD: + diff = GenerateDiff (GetCommitContent (c1, change.GetNewPath ()), EmptyContent); break; default: - diff = GenerateDiff (GetCommitContent (c1, change.Path), GetCommitContent (c2, change.Path)); + diff = GenerateDiff (GetCommitContent (c1, change.GetNewPath ()), GetCommitContent (c2, change.GetNewPath ())); break; } - DiffInfo di = new DiffInfo (path, FromGitPath (change.Path), diff); + DiffInfo di = new DiffInfo (path, FromGitPath (change.GetNewPath ()), diff); diffs.Add (di); } return diffs.ToArray (); diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitUtil.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitUtil.cs index feda45582c..7315e8f006 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitUtil.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitUtil.cs @@ -84,86 +84,41 @@ namespace MonoDevelop.VersionControl.Git /// <summary> /// Compares two commits and returns a list of files that have changed /// </summary> - public static IEnumerable<Change> CompareCommits (NGit.Repository repo, RevCommit reference, RevCommit compared) + public static IEnumerable<DiffEntry> CompareCommits (NGit.Repository repo, RevCommit reference, RevCommit compared) { - var changes = new List<Change>(); + var changes = new List<DiffEntry>(); if (reference == null && compared == null) return changes; ObjectId refTree = (reference != null ? reference.Tree.Id : ObjectId.ZeroId); ObjectId comparedTree = (compared != null ? compared.Tree.Id : ObjectId.ZeroId); - var walk = new TreeWalk (repo); - if (reference == null || compared == null) - walk.Reset ((reference ?? compared).Tree.Id); - else - walk.Reset (new AnyObjectId[] {refTree, comparedTree}); - walk.Recursive = true; - walk.Filter = AndTreeFilter.Create(TreeFilter.ANY_DIFF, TreeFilter.ALL); - - return CalculateCommitDiff (repo, walk, new[] { reference, compared }); + return CompareCommits (repo, refTree, comparedTree); } /// <summary> /// Returns a list of files that have changed in a commit /// </summary> - public static IEnumerable<Change> GetCommitChanges (NGit.Repository repo, RevCommit commit) + public static IEnumerable<DiffEntry> GetCommitChanges (NGit.Repository repo, RevCommit commit) { - var treeIds = new[] { commit.Tree.Id }.Concat (commit.Parents.Select (c => c.Tree.Id)).ToArray (); - var walk = new TreeWalk (repo); - walk.Reset (treeIds); - walk.Recursive = true; - walk.Filter = AndTreeFilter.Create (AndTreeFilter.ANY_DIFF, AndTreeFilter.ALL); - - return CalculateCommitDiff (repo, walk, new[] { commit }.Concat (commit.Parents).ToArray ()); + var rev = commit.ToObjectId (); + var prev = repo.Resolve (commit.Name + "^") ?? ObjectId.ZeroId; + return CompareCommits (repo, rev, prev); } - - static IEnumerable<Change> CalculateCommitDiff (NGit.Repository repo, TreeWalk walk, RevCommit[] commits) + + public static IEnumerable<DiffEntry> CompareCommits (NGit.Repository repo, AnyObjectId reference, ObjectId compared) { - while (walk.Next ()) { - int m0 = walk.GetRawMode (0); - if (walk.TreeCount == 2) { - int m1 = walk.GetRawMode (1); - var change = new Change { ReferenceCommit = commits[0], ComparedCommit = commits[1], ReferencePermissions = walk.GetFileMode (0).GetBits (), ComparedPermissions = walk.GetFileMode (1).GetBits (), Name = walk.NameString, Path = walk.PathString }; - if (m0 != 0 && m1 == 0) { - change.ChangeType = ChangeType.Added; - change.ComparedObject = walk.GetObjectId (0); - } else if (m0 == 0 && m1 != 0) { - change.ChangeType = ChangeType.Deleted; - change.ReferenceObject = walk.GetObjectId (0); - } else if (m0 != m1 && walk.IdEqual (0, 1)) { - change.ChangeType = ChangeType.TypeChanged; - change.ReferenceObject = walk.GetObjectId (0); - change.ComparedObject = walk.GetObjectId (1); - } else { - change.ChangeType = ChangeType.Modified; - change.ReferenceObject = walk.GetObjectId (0); - change.ComparedObject = walk.GetObjectId (1); - } - yield return change; - } else { - var raw_modes = new int[walk.TreeCount - 1]; - for (int i = 0; i < walk.TreeCount - 1; i++) - raw_modes[i] = walk.GetRawMode (i + 1); - //ComparedCommit = compared, - var change = new Change { ReferenceCommit = commits[0], Name = walk.NameString, Path = walk.PathString }; - if (m0 != 0 && raw_modes.All (m1 => m1 == 0)) { - change.ChangeType = ChangeType.Added; - change.ComparedObject = walk.GetObjectId (0); - yield return change; - } else if (m0 == 0 && raw_modes.Any (m1 => m1 != 0)) { - change.ChangeType = ChangeType.Deleted; - yield return change; - // TODO: not sure if this condition suffices in some special cases. - } else if (raw_modes.Select ((m1, i) => new { Mode = m1, Index = i + 1 }).All (x => !walk.IdEqual (0, x.Index))) { - change.ChangeType = ChangeType.Modified; - change.ReferenceObject = walk.GetObjectId (0); - yield return change; - } else if (raw_modes.Select ((m1, i) => new { Mode = m1, Index = i + 1 }).Any (x => m0 != x.Mode && walk.IdEqual (0, x.Index))) { - change.ChangeType = ChangeType.TypeChanged; - change.ReferenceObject = walk.GetObjectId (0); - yield return change; - } - } - } + var diff = new NGit.Api.Git (repo).Diff (); + + var firstTree = new CanonicalTreeParser (); + firstTree.Reset (repo.NewObjectReader (), new RevWalk (repo).ParseTree (reference)); + + var secondTree = new CanonicalTreeParser (); + secondTree.Reset (repo.NewObjectReader (), new RevWalk (repo).ParseTree (compared)); + + diff.SetNewTree (firstTree); + if (compared != ObjectId.ZeroId) + diff.SetOldTree (secondTree); + + return diff.Call (); } public static ObjectId CreateCommit (NGit.Repository rep, string message, IList<ObjectId> parents, ObjectId indexTreeId, PersonIdent author, PersonIdent committer) @@ -249,7 +204,7 @@ namespace MonoDevelop.VersionControl.Git return new StashCollection (repo); } - public static IEnumerable<Change> GetChangedFiles (NGit.Repository repo, string refRev) + public static IEnumerable<DiffEntry> GetChangedFiles (NGit.Repository repo, string refRev) { // Get a list of files that are different in the target branch RevWalk rw = new RevWalk (repo); |