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 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())
{
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
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))
{
// 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
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);
}