diff --git a/GitTfs/Commands/Checkout.cs b/GitTfs/Commands/Checkout.cs index 0ca4c7df0..c09ed1ed5 100644 --- a/GitTfs/Commands/Checkout.cs +++ b/GitTfs/Commands/Checkout.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Linq; using NDesk.Options; using Sep.Git.Tfs.Core; using StructureMap; @@ -43,10 +44,15 @@ public int Run(string id) long changesetId; if(!long.TryParse(id, out changesetId)) throw new GitTfsException("error: wrong format for changeset id..."); - //TODO - var sha = _globals.Repository.FindCommitHashByChangesetId(changesetId, "TODO"); - if (string.IsNullOrEmpty(sha)) - throw new GitTfsException("error: commit not found for this changeset id..."); + + var shas = _globals.Repository.FindCommitHashesByChangesetId(changesetId); + if (shas.Count == 0) + throw new GitTfsException("error: commit not found for " + changesetId.ToString() + " changeset id..."); + if (shas.Count > 1) + throw new GitTfsException("error: found more than one commit for " + changesetId.ToString() + " changeset id..."); + + var sha = shas.Single(); + if (ReturnShaOnly) { _stdout.Write(sha); diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index 90fd6ae96..dc62b9702 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -44,10 +44,18 @@ public GitCommit Commit(LogEntry logEntry) logEntry.Tree, parents, false); - changesetsCache[logEntry.ChangesetId] = commit.Sha; + AddToChangesetCache(logEntry.ChangesetId, commit.Sha); return new GitCommit(commit); } + private void AddToChangesetCache(long changesetId, string sha) + { + IList shas; + if (!changesetsCache.TryGetValue(changesetId, out shas)) + changesetsCache[changesetId] = shas = new List(); + shas.Add(sha); + } + public void UpdateRef(string gitRefName, string shaCommit, string message = null) { if (message == null) @@ -341,7 +349,7 @@ private void FindTfsParentCommits(List changesets, Commit comm public TfsChangesetInfo GetTfsChangesetById(string remoteRef, long changesetId, string tfsPath) { - var commit = FindCommitByChangesetId(changesetId, tfsPath, remoteRef); + var commit = FindCommitByChangesetIdAndPath(changesetId, tfsPath, remoteRef); if (commit == null) return null; return TryParseChangesetInfo(commit.Message, commit.Sha); @@ -520,18 +528,23 @@ public bool CreateBranch(string gitBranchName, string target) return reference != null; } - private readonly Dictionary changesetsCache = new Dictionary(); + private readonly Dictionary> changesetsCache = new Dictionary>(); private bool cacheIsFull = false; public string FindCommitHashByChangesetId(long changesetId, string tfsPath) { - var commit = FindCommitByChangesetId(changesetId, tfsPath); + var commit = FindCommitByChangesetIdAndPath(changesetId, tfsPath); if (commit == null) return null; return commit.Sha; } + public ICollection FindCommitHashesByChangesetId(long changesetId) + { + return FindCommitsByChangesetId(changesetId).Select(x => x.Sha).ToList(); + } + private static readonly Regex tfsIdRegex = new Regex("^git-tfs-id: .*;C([0-9]+)\r?$", RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.RightToLeft); public static bool TryParseChangesetId(string commitMessage, out long changesetId) @@ -547,15 +560,20 @@ public static bool TryParseChangesetId(string commitMessage, out long changesetI return false; } - private Commit FindCommitByChangesetId(long changesetId, string tfsPath, string remoteRef = null) + private Commit FindCommitByChangesetIdAndPath(long changesetId, string tfsPath, string remoteRef = null) { - Trace.WriteLine("Looking for changeset " + changesetId + " in git repository..."); + Trace.WriteLine("Looking for changeset " + changesetId.ToString() + " in git repository..."); if (remoteRef == null) { - string sha; - if (changesetsCache.TryGetValue(changesetId, out sha)) - return _repository.Lookup(sha); + IList shas; + if (changesetsCache.TryGetValue(changesetId, out shas)) + { + var commitFromCache = shas.Select(sha => _repository.Lookup(sha)).FirstOrDefault(c => c.Message.Contains(tfsPath)); + if (commitFromCache != null) + return commitFromCache; + } + if (cacheIsFull) return null; } @@ -568,6 +586,7 @@ private Commit FindCommitByChangesetId(long changesetId, string tfsPath, string if (remoteRef != null) reachableFromRemoteBranches.Since = _repository.Branches.Where(p => p.IsRemote && p.CanonicalName.EndsWith(remoteRef)); + var commitsFromRemoteBranches = _repository.Commits.QueryBy(reachableFromRemoteBranches); Commit commit = null; @@ -576,7 +595,8 @@ private Commit FindCommitByChangesetId(long changesetId, string tfsPath, string long id; if (TryParseChangesetId(c.Message, out id)) { - changesetsCache[changesetId] = c.Sha; + AddToChangesetCache(changesetId, c.Sha); + if (id == changesetId && c.Message.Contains(tfsPath)) { commit = c; @@ -584,12 +604,68 @@ private Commit FindCommitByChangesetId(long changesetId, string tfsPath, string } } } + if (remoteRef == null && commit == null) cacheIsFull = true; // repository fully scanned - Trace.WriteLine((commit == null) ? " => Commit not found!" : " => Commit found! hash: " + commit.Sha); + + if (commit == null) + Trace.WriteLine(" => Commit not found!"); + else + Trace.WriteLine(" => Commit found! hash: " + commit.Sha); + return commit; } + private IEnumerable FindCommitsByChangesetId(long changesetId, string remoteRef = null) + { + Trace.WriteLine("Looking for changeset " + changesetId.ToString() + " in git repository..."); + + if (remoteRef == null) + { + IList shas; + if (changesetsCache.TryGetValue(changesetId, out shas)) + return shas.Select(sha => _repository.Lookup(sha)); + + if (cacheIsFull) + return Enumerable.Empty(); + } + + var reachableFromRemoteBranches = new CommitFilter + { + Since = _repository.Branches.Where(p => p.IsRemote), + SortBy = CommitSortStrategies.Time + }; + + if (remoteRef != null) + reachableFromRemoteBranches.Since = _repository.Branches.Where(p => p.IsRemote && p.CanonicalName.EndsWith(remoteRef)); + var commitsFromRemoteBranches = _repository.Commits.QueryBy(reachableFromRemoteBranches); + + var commits = new List(); + foreach (var c in commitsFromRemoteBranches) + { + long id; + if (TryParseChangesetId(c.Message, out id)) + { + AddToChangesetCache(changesetId, c.Sha); + + if (id == changesetId) + commits.Add(c); + } + } + + if (remoteRef == null && commits.Count == 0) + cacheIsFull = true; // repository fully scanned + + if (commits.Count == 0) + Trace.WriteLine(" => Commit not found!"); + else if (commits.Count == 1) + Trace.WriteLine(" => Commit found! hash: " + commits.First().Sha); + else + Trace.WriteLine(" => Multiple commits found! hashes: " + string.Join(", ", commits.Select(c => c.Sha))); + + return commits; + } + public void CreateTag(string name, string sha, string comment, string Owner, string emailOwner, System.DateTime creationDate) { if (_repository.Tags[name] == null) diff --git a/GitTfs/Core/IGitRepository.cs b/GitTfs/Core/IGitRepository.cs index a6438487a..4ddfca4e5 100644 --- a/GitTfs/Core/IGitRepository.cs +++ b/GitTfs/Core/IGitRepository.cs @@ -42,6 +42,7 @@ public interface IGitRepository : IGitHelpers bool CreateBranch(string gitBranchName, string target); Branch RenameBranch(string oldName, string newName); string FindCommitHashByChangesetId(long changesetId, string tfsPath); + ICollection FindCommitHashesByChangesetId(long changesetId); void CreateTag(string name, string sha, string comment, string Owner, string emailOwner, System.DateTime creationDate); void CreateNote(string sha, string content, string owner, string emailOwner, DateTime creationDate); void MoveRemote(string oldRemoteName, string newRemoteName);