private void ReplayRevision(VssPathMapper pathMapper, Revision revision, LinkedList <Revision> labels) { 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 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()) { vcsWrapper.RemoveDir(targetPath, true); } else { // git doesn't care about directories with no files vcsWrapper.RemoveEmptyDir(targetPath); } } } 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 { vcsWrapper.RemoveFile(targetPath); } } } } } 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, vcsWrapper.Move); } else { CaseSensitiveRename(sourcePath, targetPath, vcsWrapper.MoveEmptyDir); } } 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 isInside = pathMapper.IsInRoot(moveFromAction.OriginalProject); if (isInside) { // MoveFrom -> inside scope: handle actual move in VCS logger.WriteLine("start MoveFrom -> inside " + moveFromAction.OriginalProject + " (move in VCS)"); var sourcePath = pathMapper.GetProjectPath(target.PhysicalName); if (sourcePath != null && sourcePath.Equals(targetPath)) { logger.WriteLine("***** warning: move with source path equal to target path " + sourcePath); } itemInfo = pathMapper.MoveProjectFrom( project, target, moveFromAction.OriginalProject); if (sourcePath != null && Directory.Exists(sourcePath)) { if (((VssProjectInfo)itemInfo).ContainsFiles()) { vcsWrapper.Move(sourcePath, targetPath); } else { vcsWrapper.MoveEmptyDir(sourcePath, targetPath); } } else { logger.WriteLine("***** warning: inside move with non existing source " + moveFromAction.OriginalProject); // project was moved from a now-destroyed project writeProjectPhysicalName = target.PhysicalName; } } else { // MoveFrom -> outside scope: recover logger.WriteLine("start MoveFrom -> outside " + moveFromAction.OriginalProject + " (recover)"); itemInfo = pathMapper.RecoverItem(project, target); isAddAction = 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 isInside = pathMapper.IsInRoot(moveToAction.NewProject); if (isInside) { // MoveTo -> inside scope: do nothing - paired with corresponding MoveFrom that handles actual move logger.WriteLine("start MoveTo -> inside " + moveToAction.NewProject + " (do nothing)"); } else { // MoveTo -> outside scope: delete - no matching MoveFrom available logger.WriteLine("start MoveTo -> outside " + moveToAction.NewProject + " (delete)"); itemInfo = pathMapper.DeleteItem(project, target); if (targetPath != null && target.IsProject) { logger.WriteLine("MoveTo delete"); // project was moved to a now-destroyed project; remove the directory if (((VssProjectInfo)itemInfo).ContainsFiles()) { vcsWrapper.RemoveDir(targetPath, true); } else { vcsWrapper.RemoveEmptyDir(targetPath); } if (Directory.Exists(targetPath)) { Directory.Delete(targetPath, true); } logger.WriteLine("MoveTo delete (done)"); } } } 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)) { Directory.CreateDirectory(targetPath); // create all contained subdirectories foreach (var projectInfo in pathMapper.GetAllProjects(writeProjectPhysicalName)) { logger.WriteLine("{0}: Creating subdirectory {1}", projectDesc, projectInfo.LogicalName); Directory.CreateDirectory(projectInfo.GetPath()); } vcsWrapper.AddDir(targetPath); // write current rev of all contained files foreach (var fileInfo in pathMapper.GetAllFiles(writeProjectPhysicalName)) { WriteRevision(pathMapper, actionType, fileInfo.PhysicalName, fileInfo.Version, writeProjectPhysicalName); } } 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 vcs operations vcsWrapper.Add(targetPath); } } } } // 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); vcsWrapper.SetNeedsCommit(); } }