public override void Init(Changeset changeset, string repoPath) { if ((!object.ReferenceEquals(changeset, null)) && (!string.IsNullOrEmpty(gitIgnoreInfo))) { string[] data = gitIgnoreInfo.Trim().Trim('|').Split('|'); if (data.Length == 5) { bool addFirstCommit = false; if (!string.IsNullOrWhiteSpace(data[0])) { string myIgnoreFile = Path.Combine(data[0], gitIgnoreFile); if (!File.Exists(myIgnoreFile)) { myIgnoreFile = data[0]; } if (File.Exists(myIgnoreFile)) { File.Copy(myIgnoreFile, Path.Combine(repoPath, gitIgnoreFile), true); addFirstCommit = true; //DoAdd(gitIgnoreFile); } } if (!string.IsNullOrWhiteSpace(data[1])) { string myAttrFile = Path.Combine(data[1], gitAttributesFile); if (!File.Exists(myAttrFile)) { myAttrFile = data[1]; } if (File.Exists(myAttrFile)) { File.Copy(myAttrFile, Path.Combine(repoPath, gitAttributesFile), true); addFirstCommit = true; //DoAdd(gitAttributesFile); } } if (addFirstCommit) { AddAll(); Commit(data[2], data[3], data[4], changeset.DateTime.AddHours(-2)); } } } }
private bool CommitChangeset(GitWrapper git, Changeset changeset) { string comment = changeset.Comment ?? this.DefaultComment; if (string.IsNullOrEmpty(comment)) { comment = "** " + GetComment(changeset, 155); } var result = false; AbortRetryIgnore(delegate { result = git.AddAll() && git.Commit(changeset.User, GetEmail(changeset.User), comment ?? DefaultComment, changeset.DateTime); }); return(result); }
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 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 CommitChangeset(Changeset changeset) { var result = false; AbortRetryIgnore(delegate { string comment = changeset.Comment; if (string.IsNullOrWhiteSpace(comment)) { if (this.tryGenerateCommitMessage) { comment = (ChangesetCommentBuilder.GetComment(changeset) ?? DefaultComment); } else { comment = DefaultComment; } } result = vcsWrapper.AddAll() && vcsWrapper.Commit(GetUsername(changeset.User), GetEmail(changeset.User), comment, changeset.DateTime); }); return(result); }
public static string GetComment(Changeset changeset) { if (object.ReferenceEquals(changeset, null)) { return(null); } ChangesetCommentBuilder cb = new ChangesetCommentBuilder(); foreach (Revision revision in changeset.Revisions) { if (revision.Item.IsProject) { cb.Add(revision.Action.Type); } } if (cb.Count > 0) { return(cb.ToString()); } else { return(null); } }
public void BuildChangesets() { workQueue.AddLast(delegate(object work) { logger.WriteSectionSeparator(); LogStatus(work, "Building changesets"); var stopwatch = Stopwatch.StartNew(); var pendingChangesByUser = new Dictionary<string, Changeset>(); var hasDelete = false; foreach (var dateEntry in revisionAnalyzer.SortedRevisions) { var dateTime = dateEntry.Key; foreach (Revision revision in dateEntry.Value) { // 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 = null; 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 (timeDiff > anyCommentThreshold) { if (HasSameComment(revision, change.Revisions.Last.Value)) { string message; if (timeDiff < sameCommentThreshold) { message = "Using same-comment threshold"; } else { message = "Same comment but exceeded threshold"; flush = true; } logger.WriteLine("NOTE: {0} ({1} second gap):", message, timeDiff.TotalSeconds); } else { flush = true; } } else if (!nonconflicting && change.TargetFiles.Contains(targetFile)) { logger.WriteLine("NOTE: Splitting changeset due to file conflict on {0}:", targetFile); flush = true; } else if (hasDelete && actionType == VssActionType.Rename) { var renameAction = revision.Action as VssRenameAction; if (renameAction != null && renameAction.Name.IsProject) { // split the change set if a rename of a directory follows a delete // otherwise a git error occurs logger.WriteLine("NOTE: Splitting changeset due to rename after delete in {0}:", targetFile); flush = true; } } if (flush) { AddChangeset(change); if (flushedUsers == null) { flushedUsers = new LinkedList<string>(); } flushedUsers.AddLast(user); hasDelete = false; } else if (user == pendingUser) { pendingChange = change; } } if (flushedUsers != null) { 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); hasDelete |= actionType == VssActionType.Delete || actionType == VssActionType.Destroy; // 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 void AddChangeset(Changeset change) { changesets.AddLast(change); int changesetId = changesets.Count; DumpChangeset(change, changesetId); }
public abstract void Init(Changeset changeset, string repoPath);
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; }
public void BuildChangesets() { workQueue.AddLast(delegate(object work) { logger.WriteSectionSeparator(); LogStatus(work, "Building changesets"); var stopwatch = Stopwatch.StartNew(); var pendingChangesByUser = new Dictionary <string, Changeset>(); var hasDelete = false; foreach (var dateEntry in revisionAnalyzer.SortedRevisions) { var dateTime = dateEntry.Key; foreach (Revision revision in dateEntry.Value) { // 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 = null; 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 (timeDiff > anyCommentThreshold) { if (HasSameComment(revision, change.Revisions.Last.Value)) { string message; if (timeDiff < sameCommentThreshold) { message = "Using same-comment threshold"; } else { message = "Same comment but exceeded threshold"; flush = true; } logger.WriteLine("NOTE: {0} ({1} second gap):", message, timeDiff.TotalSeconds); } else { flush = true; } } else if (!nonconflicting && change.TargetFiles.Contains(targetFile)) { logger.WriteLine("NOTE: Splitting changeset due to file conflict on {0}:", targetFile); flush = true; } else if (hasDelete && actionType == VssActionType.Rename) { // don't mix deletes with renames logger.WriteLine("NOTE: Splitting changeset due to rename after delete in {0}:", targetFile); flush = true; } else if (hasDelete && actionType == VssActionType.Add) { // don't mix deletes with adds logger.WriteLine("NOTE: Splitting changeset due to an add after delete in {0}:", targetFile); flush = true; } if (flush) { AddChangeset(change); if (flushedUsers == null) { flushedUsers = new LinkedList <string>(); } flushedUsers.AddLast(user); hasDelete = false; } else if (user == pendingUser) { pendingChange = change; } } if (flushedUsers != null) { 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); hasDelete |= actionType == VssActionType.Delete || actionType == VssActionType.Destroy; // 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 void SkipChangeset(VssPathMapper pathMapper, Changeset changeset) { foreach (Revision revision in changeset.Revisions) { if (workQueue.IsAborting) { break; } SkipRevision(pathMapper, revision); } }
private void ReplayChangeset(VssPathMapper pathMapper, Changeset changeset, LinkedList<Revision> labels) { foreach (Revision revision in changeset.Revisions) { if (workQueue.IsAborting) { break; } AbortRetryIgnore(delegate { ReplayRevision(pathMapper, revision, labels); }); } }
private bool CommitChangeset(Changeset changeset) { var result = false; AbortRetryIgnore(delegate { result = vcsWrapper.AddAll() && vcsWrapper.Commit(GetUsername(changeset.User), GetEmail(changeset.User), changeset.Comment ?? DefaultComment, changeset.DateTime); }); return result; }
public override void Init(Changeset changeset, string repoPath) { }
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; }
private bool ReplayRevision(VssPathMapper pathMapper, Revision revision, GitWrapper git, LinkedList<Revision> labels, bool pendingCommit, Changeset changeset) { var needCommit = false; var actionType = revision.Action.Type; Action inlineCommit = () => { if (pendingCommit) { AddNewItems(git); //LogStatus(work, "Committing During a move"); if (CommitChangeset(git, changeset)) { pathsToAdd.Clear(); } } }; if (revision.Item.IsProject) { // note that project path (and therefore target path) can be // null if a project was moved and its original location was // subsequently destroyed var project = revision.Item; var projectName = project.LogicalName; var projectPath = pathMapper.GetProjectPath(project.PhysicalName); var projectDesc = projectPath; if (projectPath == null) { projectDesc = revision.Item.ToString(); logger.WriteLine("NOTE: {0} is currently unmapped", project); } VssItemName target = null; string targetPath = null; var namedAction = revision.Action as VssNamedAction; if (namedAction != null) { target = namedAction.Name; if (projectPath != null) { targetPath = Path.Combine(projectPath, target.LogicalName); } } bool isAddAction = false; bool writeFile = false; string writeProjectPhysicalName = null; VssItemInfo itemInfo = null; switch (actionType) { case VssActionType.Label: // defer tagging until after commit labels.AddLast(revision); break; case VssActionType.Create: // ignored; items are actually created when added to a project break; case VssActionType.Add: case VssActionType.Share: logger.WriteLine("{0}: {1} {2}", projectDesc, actionType, target.LogicalName); itemInfo = pathMapper.AddItem(project, target); isAddAction = true; break; case VssActionType.Recover: logger.WriteLine("{0}: {1} {2}", projectDesc, actionType, target.LogicalName); itemInfo = pathMapper.RecoverItem(project, target); isAddAction = true; break; case VssActionType.Delete: case VssActionType.Destroy: { logger.WriteLine("{0}: {1} {2}", projectDesc, actionType, target.LogicalName); itemInfo = pathMapper.DeleteItem(project, target); if (targetPath != null && !itemInfo.Destroyed) { if (target.IsProject) { if (Directory.Exists(targetPath)) { string successor = pathMapper.TryToGetPhysicalNameContainedInProject(project, target); if (successor != null) { // we already have another project with the same logical name logger.WriteLine("NOTE: {0} contains another directory named {1}; not deleting directory", projectDesc, target.LogicalName); writeProjectPhysicalName = successor; // rewrite this project because it gets deleted below } if (((VssProjectInfo)itemInfo).ContainsFiles()) { git.Remove(targetPath, true); needCommit = true; } else { // git doesn't care about directories with no files Directory.Delete(targetPath, true); } } } else { if (File.Exists(targetPath)) { // not sure how it can happen, but a project can evidently // contain another file with the same logical name, so check // that this is not the case before deleting the file if (pathMapper.TryToGetPhysicalNameContainedInProject(project, target) != null) { logger.WriteLine("NOTE: {0} contains another file named {1}; not deleting file", projectDesc, target.LogicalName); } else { File.Delete(targetPath); needCommit = true; } } } } } break; case VssActionType.Rename: { var renameAction = (VssRenameAction)revision.Action; logger.WriteLine("{0}: {1} {2} to {3}", projectDesc, actionType, renameAction.OriginalName, target.LogicalName); itemInfo = pathMapper.RenameItem(target); if (targetPath != null && !itemInfo.Destroyed) { var sourcePath = Path.Combine(projectPath, renameAction.OriginalName); if (target.IsProject ? Directory.Exists(sourcePath) : File.Exists(sourcePath)) { // renaming a file or a project that contains files? var projectInfo = itemInfo as VssProjectInfo; if (projectInfo == null || projectInfo.ContainsFiles()) { foreach (var item in pathsToAdd.Where(item => item.StartsWith(sourcePath, StringComparison.OrdinalIgnoreCase)).ToList()) { pathsToAdd.Remove(item); } inlineCommit(); CaseSensitiveRename(sourcePath, targetPath, git.Move); needCommit = true; } else { // git doesn't care about directories with no files inlineCommit(); CaseSensitiveRename(sourcePath, targetPath, Directory.Move); } } else { logger.WriteLine("NOTE: Skipping rename because {0} does not exist", sourcePath); } } } break; case VssActionType.MoveFrom: // if both MoveFrom & MoveTo are present (e.g. // one of them has not been destroyed), only one // can succeed, so check that the source exists { var moveFromAction = (VssMoveFromAction)revision.Action; logger.WriteLine("{0}: Move from {1} to {2}", projectDesc, moveFromAction.OriginalProject, targetPath ?? target.LogicalName); var sourcePath = pathMapper.GetProjectPath(target.PhysicalName); var projectInfo = pathMapper.MoveProjectFrom( project, target, moveFromAction.OriginalProject); if (targetPath != null && !projectInfo.Destroyed) { if (sourcePath != null && Directory.Exists(sourcePath)) { if (projectInfo.ContainsFiles()) { foreach (var item in pathsToAdd.Where(item => item.StartsWith(sourcePath, StringComparison.OrdinalIgnoreCase)).ToList()) { pathsToAdd.Remove(item); } inlineCommit(); git.Move(sourcePath, targetPath); needCommit = true; } else { // git doesn't care about directories with no files Directory.Move(sourcePath, targetPath); } } else { // project was moved from a now-destroyed project writeProjectPhysicalName = target.PhysicalName; } } } break; case VssActionType.MoveTo: { // handle actual moves in MoveFrom; this just does cleanup of destroyed projects var moveToAction = (VssMoveToAction)revision.Action; logger.WriteLine("{0}: Move to {1} from {2}", projectDesc, moveToAction.NewProject, targetPath ?? target.LogicalName); var projectInfo = pathMapper.MoveProjectTo( project, target, moveToAction.NewProject); if (projectInfo.Destroyed && targetPath != null && Directory.Exists(targetPath)) { inlineCommit(); // project was moved to a now-destroyed project; remove empty directory Directory.Delete(targetPath, true); } } break; case VssActionType.Pin: { var pinAction = (VssPinAction)revision.Action; if (pinAction.Pinned) { logger.WriteLine("{0}: Pin {1}", projectDesc, target.LogicalName); itemInfo = pathMapper.PinItem(project, target); } else { logger.WriteLine("{0}: Unpin {1}", projectDesc, target.LogicalName); itemInfo = pathMapper.UnpinItem(project, target); writeFile = !itemInfo.Destroyed; } } break; case VssActionType.Branch: { var branchAction = (VssBranchAction)revision.Action; logger.WriteLine("{0}: {1} {2}", projectDesc, actionType, target.LogicalName); itemInfo = pathMapper.BranchFile(project, target, branchAction.Source); // branching within the project might happen after branching of the file writeFile = true; } break; case VssActionType.Archive: // currently ignored { var archiveAction = (VssArchiveAction)revision.Action; logger.WriteLine("{0}: Archive {1} to {2} (ignored)", projectDesc, target.LogicalName, archiveAction.ArchivePath); } break; case VssActionType.Restore: { var restoreAction = (VssRestoreAction)revision.Action; logger.WriteLine("{0}: Restore {1} from archive {2}", projectDesc, target.LogicalName, restoreAction.ArchivePath); itemInfo = pathMapper.AddItem(project, target); isAddAction = true; } break; } if (targetPath != null) { if (isAddAction) { if (revisionAnalyzer.IsDestroyed(target.PhysicalName) && !database.ItemExists(target.PhysicalName)) { logger.WriteLine("NOTE: Skipping destroyed file: {0}", targetPath); itemInfo.Destroyed = true; } else if (target.IsProject) { Directory.CreateDirectory(targetPath); writeProjectPhysicalName = target.PhysicalName; } else { writeFile = true; } } if (writeProjectPhysicalName != null && pathMapper.IsProjectRooted(writeProjectPhysicalName)) { // create all contained subdirectories foreach (var projectInfo in pathMapper.GetAllProjects(writeProjectPhysicalName)) { logger.WriteLine("{0}: Creating subdirectory {1}", projectDesc, projectInfo.LogicalName); Directory.CreateDirectory(projectInfo.GetPath()); } // write current rev of all contained files foreach (var fileInfo in pathMapper.GetAllFiles(writeProjectPhysicalName)) { if (WriteRevision(pathMapper, actionType, fileInfo.PhysicalName, fileInfo.Version, writeProjectPhysicalName, git)) { // one or more files were written needCommit = true; } } } else if (writeFile) { // write current rev to working path int version = pathMapper.GetFileVersion(target.PhysicalName); if (WriteRevisionTo(target.PhysicalName, version, targetPath)) { // add file explicitly, so it is visible to subsequent git operations pathsToAdd.Add(targetPath); //git.Add(targetPath); needCommit = true; } } } } // item is a file, not a project else if (actionType == VssActionType.Edit || actionType == VssActionType.Branch) { // if the action is Branch, the following code is necessary only if the item // was branched from a file that is not part of the migration subset; it will // make sure we start with the correct revision instead of the first revision var target = revision.Item; // update current rev pathMapper.SetFileVersion(target, revision.Version); // write current rev to all sharing projects WriteRevision(pathMapper, actionType, target.PhysicalName, revision.Version, null, git); needCommit = true; } return needCommit; }