diff --git a/src/GitTfs.VsCommon/Wrappers.cs b/src/GitTfs.VsCommon/Wrappers.cs index 2b724469..a3b8b8c8 100644 --- a/src/GitTfs.VsCommon/Wrappers.cs +++ b/src/GitTfs.VsCommon/Wrappers.cs @@ -40,8 +40,9 @@ namespace GitTfs.VsCommon new ChangesetVersionSpec(changesetNumber), DeletedState.NonDeleted, ItemType.Any, - true - ); + // do not load the loading info + false); + return _bridge.Wrap(itemSet.Items); } @@ -468,6 +469,12 @@ namespace GitTfs.VsCommon Retry.Do(() => DoUntilNoFailures(() => _workspace.Get(new ChangesetVersionSpec(changeset), GetOptions.Overwrite | GetOptions.GetAll))); } + public void GetSpecificVersion(int changesetId, IEnumerable items) + { + var version = new ChangesetVersionSpec(changesetId); + GetRequests(items.Select(e => new GetRequest(new ItemSpec(e.ServerItem, RecursionType.Full), version))); + } + public void GetSpecificVersion(IChangeset changeset) { GetSpecificVersion(changeset.ChangesetId, changeset.Changes); @@ -475,15 +482,7 @@ namespace GitTfs.VsCommon public void GetSpecificVersion(int changesetId, IEnumerable changes) { - Retry.Do(() => - { - var requests = from change in changes - select - new GetRequest( - new ItemSpec(change.Item.ServerItem, RecursionType.None, change.Item.DeletionId), - changesetId); - DoUntilNoFailures(() => _workspace.Get(requests.ToArray(), GetOptions.Overwrite)); - }); + GetRequests(changes.Select(change => new GetRequest(new ItemSpec(change.Item.ServerItem, RecursionType.None, change.Item.DeletionId), changesetId))); } public string GetLocalItemForServerItem(string serverItem) @@ -536,6 +535,27 @@ namespace GitTfs.VsCommon throw new GitTfsGatedCheckinException(gatedException.ShelvesetName, gatedException.AffectedBuildDefinitions, gatedException.CheckInTicket); } } + + public void GetRequests(IEnumerable source, int batchSize = 20) + { + source.ToBatch(batchSize).DoParallel(batch => + { + var items = batch; + Retry.Do(() => + { + while (items.Length > 0) + { + var status = _workspace.Get(items.ToArray(), GetOptions.Overwrite | GetOptions.GetAll); + if (status.NumFailures == 0) + { + break; + } + + items = status.GetFailures().Join(items, e => e.ServerItem, e => e.ItemSpec.Item, (failure, request) => request).ToArray(); + } + }); + }); + } } public class WrapperForBranchObject : WrapperFor, IBranchObject diff --git a/src/GitTfs.VsFake/TfsHelper.VsFake.cs b/src/GitTfs.VsFake/TfsHelper.VsFake.cs index 1f938d12..464d8440 100644 --- a/src/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/src/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -258,6 +258,11 @@ namespace GitTfs.VsFake _repositoryRoot = repositoryRoot; } + public void GetSpecificVersion(int changesetId, IEnumerable items) + { + throw new NotImplementedException(); + } + public void GetSpecificVersion(IChangeset changeset) { GetSpecificVersion(changeset.ChangesetId, changeset.Changes); diff --git a/src/GitTfs/Core/Ext.cs b/src/GitTfs/Core/Ext.cs index 5a66d903..355e8dbf 100644 --- a/src/GitTfs/Core/Ext.cs +++ b/src/GitTfs/Core/Ext.cs @@ -11,6 +11,9 @@ using StructureMap; namespace GitTfs.Core { + using System.Collections.Concurrent; + using System.Threading; + public static class Ext { public static T Tap(this T o, Action block) @@ -159,5 +162,190 @@ namespace GitTfs.Core } } } + + /// + /// Translate the to the sequence of array's items + /// + /// The source item type + /// The output item type + /// The source collection + /// The delegate to use to translate + /// the of item in the batch array + /// The with arrays of items sized by + public static IEnumerable ToBatch(this IEnumerable source, Func selector, int batchSize) + { + var batch = new List(batchSize); + + foreach (var item in source) + { + if (batch.Count >= batchSize) + { + yield return batch.ToArray(); + batch.Clear(); + } + + batch.Add(selector(item)); + } + + if (batch.Count > 0) + { + yield return batch.ToArray(); + } + } + + /// + /// Translate the to the sequence of array's items + /// + /// The source item type + /// The source collection + /// the of item in the batch array + /// The with arrays of items sized by + [DebuggerStepThrough] + public static IEnumerable ToBatch(this IEnumerable source, int batchSize) + { + return ToBatch(source, e => e, batchSize); + } + + /// + /// Executes the for each item in the simultaneously. + /// + /// The source sequence. + /// The action to execute. + /// The source item type + public static void DoParallel(this IEnumerable source, Action action) + { +#if DEBUG && NO_PARALLEL + foreach (var item in source) + { + action(item); + } +#else + (source as ParallelQuery ?? source.AsParallel()).ForAll(action); +#endif + } + + /// + /// TRuns the on each item in the in parallel + /// + /// + /// The source collection of items. + /// + /// + /// The action to process of the item. + /// + /// + /// The delay between retries. + /// + /// The type of items in the . + /// + /// Returns true when all items processed successfully. + public static void DoParallelRetry( + this IEnumerable source, + Action action, + TimeSpan retryInterval) + { + DoParallelRetry(source, action, 10, retryInterval); + } + + /// + /// TRuns the on each item in the in parallel + /// + /// + /// The source collection of items. + /// + /// + /// The action to process of the item. + /// + /// The type of items in the . + /// + /// Returns true when all items processed successfully. + public static void DoParallelRetry(this IEnumerable source, Action action) + { + DoParallelRetry(source, action, 10, TimeSpan.FromSeconds(1)); + } + + /// + /// TRuns the on each item in the in parallel + /// + /// + /// The source collection of items. + /// + /// + /// The action to process of the item. + /// + /// + /// TThe number of retries. + /// + /// The type of items in the . + /// + /// Returns true when all items processed successfully. + public static void DoParallelRetry(this IEnumerable source, Action action, int retryCount) + { + DoParallelRetry(source, action, retryCount, TimeSpan.FromSeconds(1)); + } + + /// + /// TRuns the on each item in the in parallel + /// + /// + /// The source collection of items. + /// + /// + /// The action to process of the item. + /// + /// + /// TThe number of retries. + /// + /// + /// The delay between retries. + /// + /// The type of items in the . + /// + /// Returns true when all items processed successfully. + public static void DoParallelRetry(this IEnumerable source, Action action, int retryCount, TimeSpan retryInterval) + { + var fails = new ConcurrentBag(); + + source.DoParallel( + item => + { + List exceptions = null; + + for (var i = 0; i < retryCount; i++) + { + if (i != 0) + { + Thread.Sleep(retryInterval); + } + + try + { + action(item); + return; + } + catch (Exception e) + { + Trace.TraceError("The action is failing: {0}", e.Message); + + if (exceptions == null) + { + exceptions = new List(); + } + + exceptions.Add(e); + } + } + + if (exceptions != null) + { + fails.Add(new AggregateException(exceptions)); + } + }); + + if (fails.Count > 0) + { + throw new AggregateException(fails); + } + } } } diff --git a/src/GitTfs/Core/ITfsWorkspace.cs b/src/GitTfs/Core/ITfsWorkspace.cs index 64d0ad20..eb86edd2 100644 --- a/src/GitTfs/Core/ITfsWorkspace.cs +++ b/src/GitTfs/Core/ITfsWorkspace.cs @@ -34,6 +34,12 @@ namespace GitTfs.Core /// Populates the workspace with a snapshot, as of the given changeset. /// void Get(int changesetId); + + /// + /// Populates the workspace with specified items, as of the given changeset. + /// + void Get(int changesetId, IEnumerable items); + /// /// Gets the files changed in a given changeset. /// diff --git a/src/GitTfs/Core/TfsChangeset.cs b/src/GitTfs/Core/TfsChangeset.cs index a4a00006..fd114949 100644 --- a/src/GitTfs/Core/TfsChangeset.cs +++ b/src/GitTfs/Core/TfsChangeset.cs @@ -120,7 +120,7 @@ namespace GitTfs.Core } else { - workspace.Get(_changeset.ChangesetId); + workspace.Get(_changeset.ChangesetId, tfsTreeEntries.Select(e => e.Item)); foreach (var entry in tfsTreeEntries) { Add(entry.Item, entry.FullName, treeBuilder, workspace); diff --git a/src/GitTfs/Core/TfsInterop/IWorkspace.cs b/src/GitTfs/Core/TfsInterop/IWorkspace.cs index 932e49ff..1fb29332 100644 --- a/src/GitTfs/Core/TfsInterop/IWorkspace.cs +++ b/src/GitTfs/Core/TfsInterop/IWorkspace.cs @@ -14,6 +14,7 @@ namespace GitTfs.Core.TfsInterop int PendRename(string pathFrom, string pathTo); void ForceGetFile(string path, int changeset); void GetSpecificVersion(int changeset); + void GetSpecificVersion(int changeset, IEnumerable items); void GetSpecificVersion(IChangeset changeset); void GetSpecificVersion(int changeset, IEnumerable changes); string GetLocalItemForServerItem(string serverItem); diff --git a/src/GitTfs/Core/TfsWorkspace.cs b/src/GitTfs/Core/TfsWorkspace.cs index af930e80..7f1c5e79 100644 --- a/src/GitTfs/Core/TfsWorkspace.cs +++ b/src/GitTfs/Core/TfsWorkspace.cs @@ -235,6 +235,11 @@ namespace GitTfs.Core _workspace.GetSpecificVersion(changesetId); } + public void Get(int changesetId, IEnumerable items) + { + _workspace.GetSpecificVersion(changesetId, items); + } + public void Get(IChangeset changeset) { _workspace.GetSpecificVersion(changeset);