private bool WriteRevision(VssPathMapper pathMapper, VssActionType actionType, string physicalName, int version, string underProject, SvnWrapper svn) { var needCommit = false; var paths = pathMapper.GetFilePaths(physicalName, underProject); foreach (string path in paths) { logger.WriteLine("{0}: {1} revision {2}", path, actionType, version); if (WriteRevisionTo(physicalName, version, path)) { // add file explicitly, so it is visible to subsequent git operations svn.Add(path); needCommit = true; } } return(needCommit); }
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); }
public void ExportToSvn(string workingCopyPath, bool inheritProjectDir, bool useSvnStandardDirStructure) { workQueue.AddLast(delegate(object work) { var stopwatch = Stopwatch.StartNew(); logger.WriteSectionSeparator(); LogStatus(work, "Initializing SVN repository"); // check repository var trunkPath = Path.Combine(workingCopyPath, "trunk"); if (!RetryCancel(delegate { if (!Directory.Exists(workingCopyPath) || (useSvnStandardDirStructure && !Directory.Exists(trunkPath))) { throw new DirectoryNotFoundException("SVN working copy is missing"); } if (!SvnWrapper.ValidateWorkingCopy(workingCopyPath)) { throw new ArgumentException("SVN working copy is invalid"); } })) { stopwatch.Stop(); return; } var svn = new SvnWrapper(workingCopyPath, useSvnStandardDirStructure, logger, parentWindow); svn.CommitEncoding = commitEncoding; /* * while (!svn.FindExecutable()) * { * var button = MessageBox.Show("Svn not found in PATH. " + * "If you need to modify your PATH variable, please " + * "restart the program for the changes to take effect.", * "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); * if (button == DialogResult.Cancel) * { * workQueue.Abort(); * return; * } * } * * * if (!RetryCancel(delegate { svn.Init(); })) * { * return; * } * * * if (commitEncoding.WebName != "utf-8") * { * AbortRetryIgnore(delegate * { * svn.SetConfig("i18n.commitencoding", commitEncoding.WebName); * }); * } */ var pathMapper = new VssPathMapper(); // create mappings for root projects foreach (var rootProject in revisionAnalyzer.RootProjects) { var rootPath = inheritProjectDir ? VssPathMapper.GetWorkingPath(useSvnStandardDirStructure?trunkPath:workingCopyPath, rootProject.Path) : useSvnStandardDirStructure ? trunkPath : workingCopyPath; pathMapper.SetProjectPath(rootProject.PhysicalName, rootPath, rootProject.Path); } // replay each changeset var changesetId = 1; var changesets = changesetBuilder.Changesets; var commitCount = 0; var tagCount = 0; var replayStopwatch = new Stopwatch(); var labels = new LinkedList <Revision>(); tagsUsed.Clear(); foreach (var changeset in changesets) { var changesetDesc = string.Format(CultureInfo.InvariantCulture, "changeset {0} from {1}", changesetId, changeset.DateTime); // replay each revision in changeset LogStatus(work, "Replaying " + changesetDesc); labels.Clear(); replayStopwatch.Start(); bool needCommit; try { needCommit = ReplayChangeset(pathMapper, changeset, svn, labels); } finally { replayStopwatch.Stop(); } if (workQueue.IsAborting) { stopwatch.Stop(); return; } // commit changes if (needCommit) { LogStatus(work, string.Format("Committing {0} on behalf of {1} with comment \"{2}\"", changesetDesc, GetSvnUserFromVssUser(changeset.User), changeset.Comment)); if (CommitChangeset(svn, changeset)) { ++commitCount; } } if (workQueue.IsAborting) { stopwatch.Stop(); return; } // create tags for any labels in the changeset if (labels.Count > 0) { foreach (Revision label in labels) { var labelName = ((VssLabelAction)label.Action).Label; if (string.IsNullOrEmpty(labelName)) { logger.WriteLine("NOTE: Ignoring empty label"); } else if (commitCount == 0) { logger.WriteLine("NOTE: Ignoring label '{0}' before initial commit", labelName); } else { var tagName = GetSvnTagFromVssLabel(labelName); var tagMessage = "Creating tag " + tagName; if (tagName != labelName) { tagMessage += " for label '" + labelName + "'"; } LogStatus(work, tagMessage); // annotated tags require (and are implied by) a tag message; // tools like Mercurial's git converter only import annotated tags var tagComment = label.Comment; if (string.IsNullOrEmpty(tagComment)) { // use the original VSS label as the tag message if none was provided tagComment = forceAnnotatedTags?labelName:DefaultComment; } // prepare tag path var taggedPath = label.Item.IsProject?pathMapper.GetProjectPath(label.Item.PhysicalName): null; if (!label.Item.IsProject) { ; } if (AbortRetryIgnore( delegate { svn.VssLabel(tagName, label.User, GetSvnUserFromVssUser(label.User), tagComment, label.DateTime, taggedPath); })) { ++tagCount; } } } } ++changesetId; } stopwatch.Stop(); logger.WriteSectionSeparator(); logger.WriteLine("SVN export complete in {0:HH:mm:ss}", new DateTime(stopwatch.ElapsedTicks)); logger.WriteLine("Replay time: {0:HH:mm:ss}", new DateTime(replayStopwatch.ElapsedTicks)); logger.WriteLine("SVN time: {0:HH:mm:ss}", new DateTime(svn.ElapsedTime.Ticks)); logger.WriteLine("SVN commits: {0}", commitCount); logger.WriteLine("SVN tags: {0}", tagCount); }); }
private bool ReplayRevision(VssPathMapper pathMapper, Revision revision, SvnWrapper svn, LinkedList <Revision> labels) { var needCommit = false; var actionType = revision.Action.Type; 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 writeProject = false; bool writeFile = false; VssItemInfo itemInfo = null; switch (actionType) { case VssActionType.Label: // defer tagging until after commit labels.AddLast(revision); break; case VssActionType.Create: //logger.WriteLine("{0}: {1} {2}", projectDesc, actionType, target.LogicalName); // 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)) { if (((VssProjectInfo)itemInfo).ContainsFiles()) { svn.Remove(targetPath, true); needCommit = true; } else { /* * // git doesn't care about directories with no files * Directory.Delete(targetPath, true); */ // svn cares about empty directories svn.Remove(targetPath, true); needCommit = 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.ProjectContainsLogicalName(project, target)) { logger.WriteLine("NOTE: {0} contains another file named {1}; not deleting file", projectDesc, target.LogicalName); } else { //File.Delete(targetPath); svn.Remove(targetPath, false); 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()) { //CaseSensitiveRename(sourcePath, targetPath, svn.Move); svn.Move(sourcePath, targetPath); needCommit = true; } else { /* * // git doesn't care about directories with no files * CaseSensitiveRename(sourcePath, targetPath, Directory.Move); */ // svn cares about directories, even if empty //CaseSensitiveRename(sourcePath, targetPath, svn.Move); svn.Move(sourcePath, targetPath); needCommit = true; } } 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()) { svn.Move(sourcePath, targetPath); needCommit = true; } else { /* * // git doesn't care about directories with no files * Directory.Move(sourcePath, targetPath); */ // svn cares about empty directories svn.Move(sourcePath, targetPath); needCommit = true; } } else { // project was moved from a now-destroyed project writeProject = true; } } } 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)) { // project was moved to a now-destroyed project; remove empty directory svn.Remove(targetPath, true); //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) { logger.WriteLine("{0}: Creating directory {1}", projectDesc, target.LogicalName); Directory.CreateDirectory(targetPath); svn.Add(targetPath); writeProject = true; needCommit = true; } else { writeFile = true; } } if (writeProject && pathMapper.IsProjectRooted(target.PhysicalName)) { // create all contained subdirectories foreach (var projectInfo in pathMapper.GetAllProjects(target.PhysicalName)) { logger.WriteLine("{0}: Creating subdirectory {1}", projectDesc, projectInfo.LogicalName); Directory.CreateDirectory(projectInfo.GetPath()); svn.Add(projectInfo.GetPath()); needCommit = true; } // write current rev of all contained files foreach (var fileInfo in pathMapper.GetAllFiles(target.PhysicalName)) { if (WriteRevision(pathMapper, actionType, fileInfo.PhysicalName, fileInfo.Version, target.PhysicalName, svn)) { // 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 svn.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, svn); needCommit = true; } return(needCommit); }
public void ExportToGit(string repoPath, bool inheritProjectDir) { workQueue.AddLast(delegate(object work) { var stopwatch = Stopwatch.StartNew(); logger.WriteSectionSeparator(); LogStatus(work, "Initializing Git repository"); // create repository directory if it does not exist if (!Directory.Exists(repoPath)) { Directory.CreateDirectory(repoPath); } var git = new GitWrapper(repoPath, logger); git.CommitEncoding = commitEncoding; while (!git.FindExecutable()) { var button = MessageBox.Show("Git not found in PATH. " + "If you need to modify your PATH variable, please " + "restart the program for the changes to take effect.", "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); if (button == DialogResult.Cancel) { workQueue.Abort(); return; } } if (!RetryCancel(delegate { git.Init(); })) { return; } if (commitEncoding.WebName != "utf-8") { AbortRetryIgnore(delegate { git.SetConfig("i18n.commitencoding", commitEncoding.WebName); }); } var pathMapper = new VssPathMapper(); // create mappings for root projects foreach (var rootProject in revisionAnalyzer.RootProjects) { var rootPath = inheritProjectDir?VssPathMapper.GetWorkingPath(repoPath, rootProject.Path):repoPath; pathMapper.SetProjectPath(rootProject.PhysicalName, rootPath, rootProject.Path); } // replay each changeset var changesetId = 1; var changesets = changesetBuilder.Changesets; var commitCount = 0; var tagCount = 0; var replayStopwatch = new Stopwatch(); var labels = new LinkedList<Revision>(); tagsUsed.Clear(); foreach (var changeset in changesets) { var changesetDesc = string.Format(CultureInfo.InvariantCulture, "changeset {0} from {1}", changesetId, changeset.DateTime); // replay each revision in changeset LogStatus(work, "Replaying " + changesetDesc); labels.Clear(); replayStopwatch.Start(); bool needCommit; try { needCommit = ReplayChangeset(pathMapper, changeset, git, labels); } finally { replayStopwatch.Stop(); } if (workQueue.IsAborting) { return; } // commit changes if (needCommit) { LogStatus(work, "Committing " + changesetDesc); if (CommitChangeset(git, changeset)) { ++commitCount; } } if (workQueue.IsAborting) { return; } // create tags for any labels in the changeset if (labels.Count > 0) { foreach (Revision label in labels) { var labelName = ((VssLabelAction)label.Action).Label; if (string.IsNullOrEmpty(labelName)) { logger.WriteLine("NOTE: Ignoring empty label"); } else if (commitCount == 0) { logger.WriteLine("NOTE: Ignoring label '{0}' before initial commit", labelName); } else { var tagName = GetTagFromLabel(labelName); var tagMessage = "Creating tag " + tagName; if (tagName != labelName) { tagMessage += " for label '" + labelName + "'"; } LogStatus(work, tagMessage); // annotated tags require (and are implied by) a tag message; // tools like Mercurial's git converter only import annotated tags var tagComment = label.Comment; if (string.IsNullOrEmpty(tagComment) && forceAnnotatedTags) { // use the original VSS label as the tag message if none was provided tagComment = labelName; } if (AbortRetryIgnore( delegate { git.Tag(tagName, label.User, GetEmail(label.User), tagComment, label.DateTime); })) { ++tagCount; } } } } ++changesetId; } stopwatch.Stop(); logger.WriteSectionSeparator(); logger.WriteLine("Git export complete in {0:HH:mm:ss}", new DateTime(stopwatch.ElapsedTicks)); logger.WriteLine("Replay time: {0:HH:mm:ss}", new DateTime(replayStopwatch.ElapsedTicks)); logger.WriteLine("Git time: {0:HH:mm:ss}", new DateTime(git.ElapsedTime.Ticks)); logger.WriteLine("Git commits: {0}", commitCount); logger.WriteLine("Git tags: {0}", tagCount); }); }
private bool WriteRevision(VssPathMapper pathMapper, VssActionType actionType, string physicalName, int version, string underProject, GitWrapper git) { var needCommit = false; var paths = pathMapper.GetFilePaths(physicalName, underProject); foreach (string path in paths) { logger.WriteLine("{0}: {1} revision {2}", path, actionType, version); if (WriteRevisionTo(physicalName, version, path)) { // add file explicitly, so it is visible to subsequent git operations git.Add(path); needCommit = true; } } return needCommit; }
private bool ReplayRevision(VssPathMapper pathMapper, Revision revision, GitWrapper git, LinkedList<Revision> labels) { var needCommit = false; var actionType = revision.Action.Type; 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 writeProject = false; bool writeFile = false; 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)) { 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.ProjectContainsLogicalName(project, target)) { 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()) { CaseSensitiveRename(sourcePath, targetPath, git.Move); needCommit = true; } else { // git doesn't care about directories with no files 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()) { 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 writeProject = true; } } } 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)) { // 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); writeProject = true; } else { writeFile = true; } } if (writeProject && pathMapper.IsProjectRooted(target.PhysicalName)) { // create all contained subdirectories foreach (var projectInfo in pathMapper.GetAllProjects(target.PhysicalName)) { 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(target.PhysicalName)) { if (WriteRevision(pathMapper, actionType, fileInfo.PhysicalName, fileInfo.Version, target.PhysicalName, 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 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; }
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 ExportToGit(string repoPath, bool inheritProjectDir) { workQueue.AddLast(delegate(object work) { var stopwatch = Stopwatch.StartNew(); logger.WriteSectionSeparator(); LogStatus(work, "Initializing Git repository"); // create repository directory if it does not exist if (!Directory.Exists(repoPath)) { Directory.CreateDirectory(repoPath); } var git = new GitWrapper(repoPath, logger); git.CommitEncoding = commitEncoding; while (!git.FindExecutable()) { var button = MessageBox.Show("Git not found in PATH. " + "If you need to modify your PATH variable, please " + "restart the program for the changes to take effect.", "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); if (button == DialogResult.Cancel) { workQueue.Abort(); return; } } if (!RetryCancel(delegate { git.Init(); })) { return; } if (commitEncoding.WebName != "utf-8") { AbortRetryIgnore(delegate { git.SetConfig("i18n.commitencoding", commitEncoding.WebName); }); } var pathMapper = new VssPathMapper(); // create mappings for root projects foreach (var rootProject in revisionAnalyzer.RootProjects) { var rootPath = inheritProjectDir?VssPathMapper.GetWorkingPath(repoPath, rootProject.Path):repoPath; pathMapper.SetProjectPath(rootProject.PhysicalName, rootPath, rootProject.Path); } // replay each changeset var changesetId = 1; var changesets = changesetBuilder.Changesets; var commitCount = 0; var tagCount = 0; var replayStopwatch = new Stopwatch(); var labels = new LinkedList <Revision>(); tagsUsed.Clear(); foreach (var changeset in changesets) { var changesetDesc = string.Format(CultureInfo.InvariantCulture, "changeset {0} from {1}", changesetId, changeset.DateTime); // replay each revision in changeset LogStatus(work, "Replaying " + changesetDesc); labels.Clear(); replayStopwatch.Start(); bool needCommit; try { needCommit = ReplayChangeset(pathMapper, changeset, git, labels); } finally { replayStopwatch.Stop(); } if (workQueue.IsAborting) { return; } // commit changes if (needCommit) { LogStatus(work, "Committing " + changesetDesc); if (CommitChangeset(git, changeset)) { ++commitCount; } } if (workQueue.IsAborting) { return; } // create tags for any labels in the changeset if (labels.Count > 0) { foreach (Revision label in labels) { var labelName = ((VssLabelAction)label.Action).Label; if (string.IsNullOrEmpty(labelName)) { logger.WriteLine("NOTE: Ignoring empty label"); } else if (commitCount == 0) { logger.WriteLine("NOTE: Ignoring label '{0}' before initial commit", labelName); } else { var tagName = GetTagFromLabel(labelName); var tagMessage = "Creating tag " + tagName; if (tagName != labelName) { tagMessage += " for label '" + labelName + "'"; } LogStatus(work, tagMessage); // annotated tags require (and are implied by) a tag message; // tools like Mercurial's git converter only import annotated tags var tagComment = label.Comment; if (string.IsNullOrEmpty(tagComment) && forceAnnotatedTags) { // use the original VSS label as the tag message if none was provided tagComment = labelName; } if (AbortRetryIgnore( delegate { git.Tag(tagName, label.User, GetEmail(label.User), tagComment, label.DateTime); })) { ++tagCount; } } } } ++changesetId; } stopwatch.Stop(); logger.WriteSectionSeparator(); logger.WriteLine("Git export complete in {0:HH:mm:ss}", new DateTime(stopwatch.ElapsedTicks)); logger.WriteLine("Replay time: {0:HH:mm:ss}", new DateTime(replayStopwatch.ElapsedTicks)); logger.WriteLine("Git time: {0:HH:mm:ss}", new DateTime(git.ElapsedTime.Ticks)); logger.WriteLine("Git commits: {0}", commitCount); logger.WriteLine("Git tags: {0}", tagCount); }); }
public void ExportToSvn(string workingCopyPath, bool inheritProjectDir, bool useSvnStandardDirStructure) { workQueue.AddLast(delegate (object work) { var stopwatch = Stopwatch.StartNew(); logger.WriteSectionSeparator(); LogStatus(work, "Initializing SVN repository"); // check repository var trunkPath = Path.Combine(workingCopyPath, "trunk"); if (!RetryCancel(delegate { if (!Directory.Exists(workingCopyPath) || (useSvnStandardDirStructure && !Directory.Exists(trunkPath))) throw new DirectoryNotFoundException("SVN working copy is missing"); if (!SvnWrapper.ValidateWorkingCopy(workingCopyPath)) throw new ArgumentException("SVN working copy is invalid"); })) { stopwatch.Stop(); return; } var svn = new SvnWrapper(workingCopyPath, useSvnStandardDirStructure, logger, parentWindow); svn.CommitEncoding = commitEncoding; /* while (!svn.FindExecutable()) { var button = MessageBox.Show("Svn not found in PATH. " + "If you need to modify your PATH variable, please " + "restart the program for the changes to take effect.", "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); if (button == DialogResult.Cancel) { workQueue.Abort(); return; } } if (!RetryCancel(delegate { svn.Init(); })) { return; } if (commitEncoding.WebName != "utf-8") { AbortRetryIgnore(delegate { svn.SetConfig("i18n.commitencoding", commitEncoding.WebName); }); } */ var pathMapper = new VssPathMapper(); // create mappings for root projects foreach (var rootProject in revisionAnalyzer.RootProjects) { var rootPath = inheritProjectDir ? VssPathMapper.GetWorkingPath(useSvnStandardDirStructure?trunkPath:workingCopyPath, rootProject.Path) : useSvnStandardDirStructure ? trunkPath : workingCopyPath; pathMapper.SetProjectPath(rootProject.PhysicalName, rootPath, rootProject.Path); } // replay each changeset var changesetId = 1; var changesets = changesetBuilder.Changesets; var commitCount = 0; var tagCount = 0; var replayStopwatch = new Stopwatch(); var labels = new LinkedList<Revision>(); tagsUsed.Clear(); foreach (var changeset in changesets) { var changesetDesc = string.Format(CultureInfo.InvariantCulture, "changeset {0} from {1}", changesetId, changeset.DateTime); // replay each revision in changeset LogStatus(work, "Replaying " + changesetDesc); labels.Clear(); replayStopwatch.Start(); bool needCommit; try { needCommit = ReplayChangeset(pathMapper, changeset, svn, labels); } finally { replayStopwatch.Stop(); } if (workQueue.IsAborting) { stopwatch.Stop(); return; } // commit changes if (needCommit) { LogStatus(work, string.Format("Committing {0} on behalf of {1} with comment \"{2}\"", changesetDesc, GetSvnUserFromVssUser(changeset.User), changeset.Comment)); if (CommitChangeset(svn, changeset)) { ++commitCount; } } if (workQueue.IsAborting) { stopwatch.Stop(); return; } // create tags for any labels in the changeset if (labels.Count > 0) { foreach (Revision label in labels) { var labelName = ((VssLabelAction)label.Action).Label; if (string.IsNullOrEmpty(labelName)) { logger.WriteLine("NOTE: Ignoring empty label"); } else if (commitCount == 0) { logger.WriteLine("NOTE: Ignoring label '{0}' before initial commit", labelName); } else { var tagName = GetSvnTagFromVssLabel(labelName); var tagMessage = "Creating tag " + tagName; if (tagName != labelName) { tagMessage += " for label '" + labelName + "'"; } LogStatus(work, tagMessage); // annotated tags require (and are implied by) a tag message; // tools like Mercurial's git converter only import annotated tags var tagComment = label.Comment; if (string.IsNullOrEmpty(tagComment)) { // use the original VSS label as the tag message if none was provided tagComment = forceAnnotatedTags?labelName:DefaultComment; } // prepare tag path var taggedPath = label.Item.IsProject?pathMapper.GetProjectPath(label.Item.PhysicalName): null; if (!label.Item.IsProject) { ; } if (AbortRetryIgnore( delegate { svn.VssLabel(tagName, label.User, GetSvnUserFromVssUser(label.User), tagComment, label.DateTime, taggedPath); })) { ++tagCount; } } } } ++changesetId; } stopwatch.Stop(); logger.WriteSectionSeparator(); logger.WriteLine("SVN export complete in {0:HH:mm:ss}", new DateTime(stopwatch.ElapsedTicks)); logger.WriteLine("Replay time: {0:HH:mm:ss}", new DateTime(replayStopwatch.ElapsedTicks)); logger.WriteLine("SVN time: {0:HH:mm:ss}", new DateTime(svn.ElapsedTime.Ticks)); logger.WriteLine("SVN commits: {0}", commitCount); logger.WriteLine("SVN tags: {0}", tagCount); }); }