private bool ReplayChangeset(VssPathMapper pathMapper, Changeset changeset, SvnWrapper svn, LinkedList <Revision> labels) { var needCommit = false; foreach (Revision revision in changeset.Revisions) { if (workQueue.IsAborting) { break; } AbortRetryIgnore(delegate { needCommit |= ReplayRevision(pathMapper, revision, svn, labels); }); } return(needCommit); }
private void DumpChangeset(Changeset changeset, int changesetId) { var firstRevTime = changeset.Revisions.First.Value.DateTime; var changeDuration = changeset.DateTime - firstRevTime; logger.WriteSectionSeparator(); logger.WriteLine("Changeset {0} - {1} ({2} secs) {3} {4} files", changesetId, changeset.DateTime, changeDuration.TotalSeconds, changeset.User, changeset.Revisions.Count); if (!string.IsNullOrEmpty(changeset.Comment)) { logger.WriteLine(changeset.Comment); } logger.WriteLine(); foreach (var revision in changeset.Revisions) { logger.WriteLine(" {0} {1}@{2} {3}", revision.DateTime, revision.Item, revision.Version, revision.Action); } }
private bool ReplayChangeset(VssPathMapper pathMapper, Changeset changeset, GitWrapper git, LinkedList<Revision> labels) { var needCommit = false; foreach (Revision revision in changeset.Revisions) { if (workQueue.IsAborting) { break; } AbortRetryIgnore(delegate { needCommit |= ReplayRevision(pathMapper, revision, git, labels); }); } return needCommit; }
private bool CommitChangeset(GitWrapper git, Changeset changeset) { var result = false; AbortRetryIgnore(delegate { result = git.AddAll() && git.Commit(changeset.User, GetEmail(changeset.User), changeset.Comment ?? DefaultComment, changeset.DateTime); }); return result; }
public void BuildChangesets() { workQueue.AddLast(delegate(object work) { logger.WriteSectionSeparator(); LogStatus(work, "Building changesets"); var unnamedLabelCount = 0ul; var stopwatch = Stopwatch.StartNew(); var pendingChangesByUser = new Dictionary <string, Changeset>(); foreach (var dateEntry in revisionAnalyzer.SortedRevisions) { var dateTime = dateEntry.Key; //foreach (Revision revision in dateEntry.Value) for (var node = dateEntry.Value.First; node != dateEntry.Value.Last.Next; node = node.Next) { var revision = node.Value; // VSS Destroyed Items: skip all. This will enable merging revisions separated by destroyed items. if (excludeAllDestroyedItems) { if ((revision.Action as VssNamedAction)?.Name?.PhysicalName != null && revisionAnalyzer.IsDestroyed((revision.Action as VssNamedAction).Name.PhysicalName) && !revisionAnalyzer.Database.ItemExists((revision.Action as VssNamedAction).Name.PhysicalName)) { continue; } } // VSS Adds: fill in comments for add from create action if (revision.Action.Type == VssActionType.Add && revision.Comment == null) { node.Value = revision = new Revision(revision.DateTime, revision.User, revision.Item, revision.Version, FindCorrespondingAction(revision, VssActionType.Create)?.Comment, revision.Action); } // VSS Labels: fill in empty label names as svn cannot have empty tag path. if (revision.Action.Type == VssActionType.Label && string.IsNullOrEmpty(((VssLabelAction)revision.Action).Label)) { node.Value = revision = new Revision(revision.DateTime, revision.User, revision.Item, revision.Version, revision.Comment, new VssLabelAction(string.Format(unnamedLabelFormat, revision.DateTime, ++unnamedLabelCount))); } // VSS Creates: skip all and exclude from changeset as it increases history complexity unnecessarily and the info is not useful at all. not used in exporter if (revision.Action.Type == VssActionType.Create) { continue; } // determine target of project revisions var actionType = revision.Action.Type; var namedAction = revision.Action as VssNamedAction; var targetFile = revision.Item.PhysicalName; if (namedAction != null) { targetFile = namedAction.Name.PhysicalName; } // Create actions are only used to obtain initial item comments; // items are actually created when added to a project var creating = (actionType == VssActionType.Create || (actionType == VssActionType.Branch && !revision.Item.IsProject)); // Share actions are never conflict (which is important, // since Share always precedes Branch) var nonconflicting = creating || (actionType == VssActionType.Share); // look up the pending change for user of this revision // and flush changes past time threshold var pendingUser = revision.User; Changeset pendingChange = null; LinkedList <string> flushedUsers = new LinkedList <string>(); foreach (var userEntry in pendingChangesByUser) { var user = userEntry.Key; var change = userEntry.Value; // flush change if file conflict or past time threshold var flush = false; var timeDiff = revision.DateTime - change.DateTime; if (user == pendingUser) { // VSS Label: make labels on their own changeset if ((revision.Action.Type == VssActionType.Label && change.Revisions.Last.Value.Action.Type != VssActionType.Label) || (revision.Action.Type != VssActionType.Label && change.Revisions.Last.Value.Action.Type == VssActionType.Label)) { logger.WriteLine("NOTE: Splitting changeset due to label: {0}", change.Revisions.Last.Value.Action); flush = true; } // Cannot combine due to file conflict. must be recorded as separate changes else if (!nonconflicting && change.TargetFiles.Contains(targetFile)) { logger.WriteLine("NOTE: Splitting changeset due to file conflict on {0}:", targetFile); flush = true; } } else { } /* * if (!mergeAcrossDifferentUser && !HasSameUser(revision, change.Revisions.Last.Value)) * { * logger.WriteLine("NOTE: Splitting changeset due to different user: {0} != {1}", change.Revisions.Last.Value.User, revision.User); * flush = true; * } */ // additional check if not flushed above if (!flush) { if ((TimeSpan.Zero == anyCommentThreshold ? TimeSpan.FromSeconds(1) : TimeSpan.Zero) + timeDiff > anyCommentThreshold) { var lastRevision = FindLastRevisionWithNonEmptyComment(change); //if (HasSameComment(revision, change.Revisions.Last.Value)) if (HasSameComment(revision, lastRevision)) { string message; if ((TimeSpan.Zero == sameCommentThreshold ? TimeSpan.FromSeconds(1) : TimeSpan.Zero) + timeDiff < sameCommentThreshold) { message = "Using same-comment threshold"; } else { message = "Splitting changeset due to same comment but exceeded threshold"; logger.WriteLine("NOTE: {0} ({1} second gap):", message, timeDiff.TotalSeconds); flush = true; } } else { //logger.WriteLine("NOTE: Splitting changeset due to different comment: {0} != {1}:", change.Revisions.Last.Value.Comment, revision.Comment); logger.WriteLine("NOTE: Splitting changeset due to different comment: {0} != {1}:", lastRevision.Comment ?? "null", revision.Comment ?? "null"); flush = true; } } } if (flush) { AddChangeset(change); flushedUsers.AddLast(user); } else if (user == pendingUser) { pendingChange = change; } } foreach (string user in flushedUsers) { pendingChangesByUser.Remove(user); } // if no pending change for user, create a new one if (pendingChange == null) { pendingChange = new Changeset(); pendingChange.User = pendingUser; pendingChangesByUser[pendingUser] = pendingChange; } // update the time of the change based on the last revision pendingChange.DateTime = revision.DateTime; // add the revision to the change pendingChange.Revisions.AddLast(revision); // track target files in changeset to detect conflicting actions if (!nonconflicting) { pendingChange.TargetFiles.Add(targetFile); } // build up a concatenation of unique revision comments var revComment = revision.Comment; if (revComment != null) { revComment = revComment.Trim(); if (revComment.Length > 0) { if (string.IsNullOrEmpty(pendingChange.Comment)) { pendingChange.Comment = revComment; } else if (!pendingChange.Comment.Contains(revComment)) { pendingChange.Comment += "\n" + revComment; } } } } } // flush all remaining changes foreach (var change in pendingChangesByUser.Values) { AddChangeset(change); } stopwatch.Stop(); logger.WriteSectionSeparator(); logger.WriteLine("Found {0} changesets in {1:HH:mm:ss}", changesets.Count, new DateTime(stopwatch.ElapsedTicks)); }); }
public void BuildChangesets() { workQueue.AddLast(delegate (object work) { logger.WriteSectionSeparator(); LogStatus(work, "Building changesets"); var unnamedLabelCount = 0ul; var stopwatch = Stopwatch.StartNew(); var pendingChangesByUser = new Dictionary<string, Changeset>(); foreach (var dateEntry in revisionAnalyzer.SortedRevisions) { var dateTime = dateEntry.Key; //foreach (Revision revision in dateEntry.Value) for (var node = dateEntry.Value.First; node != dateEntry.Value.Last.Next; node = node.Next) { var revision = node.Value; // VSS Destroyed Items: skip all. This will enable merging revisions separated by destroyed items. if (excludeAllDestroyedItems) { if ((revision.Action as VssNamedAction)?.Name?.PhysicalName != null && revisionAnalyzer.IsDestroyed((revision.Action as VssNamedAction).Name.PhysicalName) && !revisionAnalyzer.Database.ItemExists((revision.Action as VssNamedAction).Name.PhysicalName)) { continue; } } // VSS Adds: fill in comments for add from create action if (revision.Action.Type == VssActionType.Add && revision.Comment == null) node.Value = revision = new Revision(revision.DateTime, revision.User, revision.Item, revision.Version, FindCorrespondingAction(revision, VssActionType.Create)?.Comment, revision.Action); // VSS Labels: fill in empty label names as svn cannot have empty tag path. if (revision.Action.Type == VssActionType.Label && string.IsNullOrEmpty(((VssLabelAction)revision.Action).Label)) node.Value = revision = new Revision(revision.DateTime, revision.User, revision.Item, revision.Version, revision.Comment, new VssLabelAction(string.Format(unnamedLabelFormat, revision.DateTime, ++unnamedLabelCount))); // VSS Creates: skip all and exclude from changeset as it increases history complexity unnecessarily and the info is not useful at all. not used in exporter if (revision.Action.Type == VssActionType.Create) continue; // determine target of project revisions var actionType = revision.Action.Type; var namedAction = revision.Action as VssNamedAction; var targetFile = revision.Item.PhysicalName; if (namedAction != null) { targetFile = namedAction.Name.PhysicalName; } // Create actions are only used to obtain initial item comments; // items are actually created when added to a project var creating = (actionType == VssActionType.Create || (actionType == VssActionType.Branch && !revision.Item.IsProject)); // Share actions are never conflict (which is important, // since Share always precedes Branch) var nonconflicting = creating || (actionType == VssActionType.Share); // look up the pending change for user of this revision // and flush changes past time threshold var pendingUser = revision.User; Changeset pendingChange = null; LinkedList<string> flushedUsers = new LinkedList<string>(); foreach (var userEntry in pendingChangesByUser) { var user = userEntry.Key; var change = userEntry.Value; // flush change if file conflict or past time threshold var flush = false; var timeDiff = revision.DateTime - change.DateTime; if (user == pendingUser) { // VSS Label: make labels on their own changeset if ((revision.Action.Type == VssActionType.Label && change.Revisions.Last.Value.Action.Type != VssActionType.Label) || (revision.Action.Type != VssActionType.Label && change.Revisions.Last.Value.Action.Type == VssActionType.Label)) { logger.WriteLine("NOTE: Splitting changeset due to label: {0}", change.Revisions.Last.Value.Action); flush = true; } // Cannot combine due to file conflict. must be recorded as separate changes else if (!nonconflicting && change.TargetFiles.Contains(targetFile)) { logger.WriteLine("NOTE: Splitting changeset due to file conflict on {0}:", targetFile); flush = true; } } else { } /* if (!mergeAcrossDifferentUser && !HasSameUser(revision, change.Revisions.Last.Value)) { logger.WriteLine("NOTE: Splitting changeset due to different user: {0} != {1}", change.Revisions.Last.Value.User, revision.User); flush = true; } */ // additional check if not flushed above if (!flush) { if ((TimeSpan.Zero == anyCommentThreshold ? TimeSpan.FromSeconds(1) : TimeSpan.Zero) + timeDiff > anyCommentThreshold) { var lastRevision = FindLastRevisionWithNonEmptyComment(change); //if (HasSameComment(revision, change.Revisions.Last.Value)) if (HasSameComment(revision, lastRevision)) { string message; if ((TimeSpan.Zero == sameCommentThreshold ? TimeSpan.FromSeconds(1) : TimeSpan.Zero) + timeDiff < sameCommentThreshold) { message = "Using same-comment threshold"; } else { message = "Splitting changeset due to same comment but exceeded threshold"; logger.WriteLine("NOTE: {0} ({1} second gap):", message, timeDiff.TotalSeconds); flush = true; } } else { //logger.WriteLine("NOTE: Splitting changeset due to different comment: {0} != {1}:", change.Revisions.Last.Value.Comment, revision.Comment); logger.WriteLine("NOTE: Splitting changeset due to different comment: {0} != {1}:", lastRevision.Comment ?? "null", revision.Comment ?? "null"); flush = true; } } } if (flush) { AddChangeset(change); flushedUsers.AddLast(user); } else if (user == pendingUser) { pendingChange = change; } } foreach (string user in flushedUsers) pendingChangesByUser.Remove(user); // if no pending change for user, create a new one if (pendingChange == null) { pendingChange = new Changeset(); pendingChange.User = pendingUser; pendingChangesByUser[pendingUser] = pendingChange; } // update the time of the change based on the last revision pendingChange.DateTime = revision.DateTime; // add the revision to the change pendingChange.Revisions.AddLast(revision); // track target files in changeset to detect conflicting actions if (!nonconflicting) { pendingChange.TargetFiles.Add(targetFile); } // build up a concatenation of unique revision comments var revComment = revision.Comment; if (revComment != null) { revComment = revComment.Trim(); if (revComment.Length > 0) { if (string.IsNullOrEmpty(pendingChange.Comment)) { pendingChange.Comment = revComment; } else if (!pendingChange.Comment.Contains(revComment)) { pendingChange.Comment += "\n" + revComment; } } } } } // flush all remaining changes foreach (var change in pendingChangesByUser.Values) { AddChangeset(change); } stopwatch.Stop(); logger.WriteSectionSeparator(); logger.WriteLine("Found {0} changesets in {1:HH:mm:ss}", changesets.Count, new DateTime(stopwatch.ElapsedTicks)); }); }
private Revision FindLastRevisionWithNonEmptyComment(Changeset change) { var node = (LinkedListNode<Revision>)null; for (node = change.Revisions.Last; node != change.Revisions.First.Previous && string.IsNullOrEmpty(node.Value.Comment); node = node.Previous) ; return node?.Value??change.Revisions.Last.Value; }
private void AddChangeset(Changeset change) { changesets.AddLast(change); int changesetId = changesets.Count; DumpChangeset(change, changesetId); }
private bool CommitChangeset(SvnWrapper svn, Changeset changeset) { var result = false; AbortRetryIgnore(delegate { result = svn.AddAll() && svn.Commit(changeset.User, GetSvnUserFromVssUser(changeset.User), changeset.Comment ?? DefaultComment, changeset.DateTime); }); return result; }