private ArgumentBuilder BuildArguments(RefFilterOptions refFilterOptions, string branchFilter, string revisionFilter, string pathFilter) { return(new GitArgumentBuilder("log") { "-z", branchFilter, $"--pretty=format:\"{FullFormat}\"", { refFilterOptions.HasFlag(RefFilterOptions.FirstParent), "--first-parent", new ArgumentBuilder { { refFilterOptions.HasFlag(RefFilterOptions.Reflogs), "--reflog" }, { AppSettings.SortByAuthorDate, "--author-date-order" }, { refFilterOptions.HasFlag(RefFilterOptions.All), "--all", new ArgumentBuilder { { refFilterOptions.HasFlag(RefFilterOptions.Branches) && !string.IsNullOrWhiteSpace(branchFilter) && branchFilter.IndexOfAny(new[] { '?', '*', '[' }) != -1, "--branches=" + branchFilter }, { refFilterOptions.HasFlag(RefFilterOptions.Remotes), "--remotes" }, { refFilterOptions.HasFlag(RefFilterOptions.Tags), "--tags" }, }.ToString() }, { refFilterOptions.HasFlag(RefFilterOptions.Boundary), "--boundary" }, { refFilterOptions.HasFlag(RefFilterOptions.ShowGitNotes), "--not --glob=notes --not" }, { refFilterOptions.HasFlag(RefFilterOptions.NoMerges), "--no-merges" }, { refFilterOptions.HasFlag(RefFilterOptions.SimplifyByDecoration), "--simplify-by-decoration" } }.ToString() }, revisionFilter, "--", pathFilter }); }
private async Task ExecuteAsync( GitModule module, IObserver <GitRevision> subject, RefFilterOptions refFilterOptions, string branchFilter, string revisionFilter, string pathFilter, [CanBeNull] Func <GitRevision, bool> revisionPredicate) { ThreadHelper.ThrowIfNotOnUIThread(); var token = _cancellationTokenSequence.Next(); RevisionCount = 0; await TaskScheduler.Default; subject.OnNext(null); token.ThrowIfCancellationRequested(); var branchName = module.IsValidGitWorkingDir() ? module.GetSelectedBranch() : ""; token.ThrowIfCancellationRequested(); LatestRefs = module.GetRefs(); UpdateSelectedRef(module, LatestRefs, branchName); var refsByObjectId = LatestRefs.ToLookup(head => head.Guid); token.ThrowIfCancellationRequested(); const string fullFormat = // These header entries can all be decoded from the bytes directly. // Each hash is 20 bytes long. There is always a /* Object ID */ "%H" + /* Tree ID */ "%T" + /* Parent IDs */ "%P%n" + /* Author date */ "%at%n" + /* Commit date */ "%ct%n" + /* Encoding */ "%e%n" + // Items below here must be decoded as strings to support non-ASCII /* Author name */ "%aN%n" + /* Author email */ "%aE%n" + /* Committer name */ "%cN%n" + /* Committer email */ "%cE%n" + /* Commit subject */ "%s%n%n" + /* Commit body */ "%b"; // TODO add AppBuilderExtensions support for flags enums, starting with RefFilterOptions, then use it in the below construction var arguments = BuildArguments(); var sw = Stopwatch.StartNew(); // This property is relatively expensive to call for every revision, so // cache it for the duration of the loop. var logOutputEncoding = module.LogOutputEncoding; using (var process = module.RunGitCmdDetached(arguments.ToString(), GitModule.LosslessEncoding)) { token.ThrowIfCancellationRequested(); // Pool string values likely to form a small set: encoding, authorname, authoremail, committername, committeremail var stringPool = new StringPool(); var buffer = new byte[4096]; foreach (var chunk in process.StandardOutput.BaseStream.ReadNullTerminatedChunks(ref buffer)) { token.ThrowIfCancellationRequested(); if (TryParseRevision(module, chunk, stringPool, logOutputEncoding, out var revision)) { if (revisionPredicate == null || revisionPredicate(revision)) { // Remove full commit message to reduce memory consumption (28% for a repo with 69K commits) // Full commit message is used in InMemFilter but later it's not needed revision.Body = null; // Look up any refs associate with this revision revision.Refs = refsByObjectId[revision.Guid].AsReadOnlyList(); RevisionCount++; subject.OnNext(revision); } } } Trace.WriteLine($"**** [{nameof(RevisionReader)}] Emitted {RevisionCount} revisions in {sw.Elapsed.TotalMilliseconds:#,##0.#} ms. bufferSize={buffer.Length} poolCount={stringPool.Count}"); } await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(token); if (!token.IsCancellationRequested) { subject.OnCompleted(); } ArgumentBuilder BuildArguments() { return(new ArgumentBuilder { "log", "-z", $"--pretty=format:\"{fullFormat}\"", { AppSettings.OrderRevisionByDate, "--date-order", "--topo-order" }, { AppSettings.ShowReflogReferences, "--reflog" }, { refFilterOptions.HasFlag(RefFilterOptions.All), "--all", new ArgumentBuilder { { refFilterOptions.HasFlag(RefFilterOptions.Branches) && !branchFilter.IsNullOrWhiteSpace() && branchFilter.IndexOfAny(new[] { '?', '*', '[' }) != -1, "--branches=" + branchFilter }, { refFilterOptions.HasFlag(RefFilterOptions.Remotes), "--remotes" }, { refFilterOptions.HasFlag(RefFilterOptions.Tags), "--tags" }, }.ToString() }, { refFilterOptions.HasFlag(RefFilterOptions.Boundary), "--boundary" }, { refFilterOptions.HasFlag(RefFilterOptions.ShowGitNotes), "--not --glob=notes --not" }, { refFilterOptions.HasFlag(RefFilterOptions.NoMerges), "--no-merges" }, { refFilterOptions.HasFlag(RefFilterOptions.FirstParent), "--first-parent" }, { refFilterOptions.HasFlag(RefFilterOptions.SimplifyByDecoration), "--simplify-by-decoration" }, revisionFilter, "--", pathFilter }); } }
private async Task ExecuteAsync( GitModule module, IReadOnlyList <IGitRef> refs, IObserver <GitRevision> subject, RefFilterOptions refFilterOptions, string branchFilter, string revisionFilter, string pathFilter, [CanBeNull] Func <GitRevision, bool> revisionPredicate) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var token = _cancellationTokenSequence.Next(); var revisionCount = 0; await TaskScheduler.Default; token.ThrowIfCancellationRequested(); var branchName = module.IsValidGitWorkingDir() ? module.GetSelectedBranch() : ""; token.ThrowIfCancellationRequested(); UpdateSelectedRef(module, refs, branchName); var refsByObjectId = refs.ToLookup(head => head.ObjectId); token.ThrowIfCancellationRequested(); const string fullFormat = // These header entries can all be decoded from the bytes directly. // Each hash is 20 bytes long. /* Object ID */ "%H" + /* Tree ID */ "%T" + /* Parent IDs */ "%P%n" + /* Author date */ "%at%n" + /* Commit date */ "%ct%n" + /* Encoding */ "%e%n" + // Items below here must be decoded as strings to support non-ASCII. /* Author name */ "%aN%n" + /* Author email */ "%aE%n" + /* Committer name */ "%cN%n" + /* Committer email */ "%cE%n" + /* Commit subject */ "%s%n%n" + /* Commit body */ "%b"; var arguments = BuildArguments(); var sw = Stopwatch.StartNew(); // This property is relatively expensive to call for every revision, so // cache it for the duration of the loop. var logOutputEncoding = module.LogOutputEncoding; using (var process = module.RunGitCmdDetached(arguments, redirectOutput: true, outputEncoding: GitModule.LosslessEncoding)) { token.ThrowIfCancellationRequested(); // Pool string values likely to form a small set: encoding, authorname, authoremail, committername, committeremail var stringPool = new StringPool(); var buffer = new byte[4096]; foreach (var chunk in process.StandardOutput.BaseStream.ReadNullTerminatedChunks(ref buffer)) { token.ThrowIfCancellationRequested(); if (TryParseRevision(module, chunk, stringPool, logOutputEncoding, out var revision)) { if (revisionPredicate == null || revisionPredicate(revision)) { // The full commit message body is used initially in InMemFilter, after which it isn't // strictly needed and can be re-populated asynchronously. // // We keep full multiline message bodies within the last six months. // Commits earlier than that have their properties set to null and the // memory will be GCd. if (DateTime.Now - revision.AuthorDate > TimeSpan.FromDays(30 * 6)) { revision.Body = null; } // Look up any refs associated with this revision revision.Refs = refsByObjectId[revision.ObjectId].AsReadOnlyList(); revisionCount++; subject.OnNext(revision); } } } Trace.WriteLine($"**** [{nameof(RevisionReader)}] Emitted {revisionCount} revisions in {sw.Elapsed.TotalMilliseconds:#,##0.#} ms. bufferSize={buffer.Length} poolCount={stringPool.Count}"); } if (!token.IsCancellationRequested) { subject.OnCompleted(); } ArgumentBuilder BuildArguments() { return(new GitArgumentBuilder("log") { "-z", $"--pretty=format:\"{fullFormat}\"", { AppSettings.ShowReflogReferences, "--reflog" }, { refFilterOptions.HasFlag(RefFilterOptions.All), "--all", new ArgumentBuilder { { refFilterOptions.HasFlag(RefFilterOptions.Branches) && !branchFilter.IsNullOrWhiteSpace() && branchFilter.IndexOfAny(new[] { '?', '*', '[' }) != -1, "--branches=" + branchFilter }, { refFilterOptions.HasFlag(RefFilterOptions.Remotes), "--remotes" }, { refFilterOptions.HasFlag(RefFilterOptions.Tags), "--tags" }, }.ToString() }, { refFilterOptions.HasFlag(RefFilterOptions.Boundary), "--boundary" }, { refFilterOptions.HasFlag(RefFilterOptions.ShowGitNotes), "--not --glob=notes --not" }, { refFilterOptions.HasFlag(RefFilterOptions.NoMerges), "--no-merges" }, { refFilterOptions.HasFlag(RefFilterOptions.FirstParent), "--first-parent" }, { refFilterOptions.HasFlag(RefFilterOptions.SimplifyByDecoration), "--simplify-by-decoration" }, revisionFilter, "--", pathFilter }); } }