Exemplo n.º 1
0
        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;
        }
Exemplo n.º 2
0
        private void ProcessItem(VssItem item, string path, PathMatcher exclusionMatcher)
        {
            try
            {
                foreach (VssRevision vssRevision in item.Revisions)
                {
                    var actionType = vssRevision.Action.Type;
                    var namedAction = vssRevision.Action as VssNamedAction;
                    if (namedAction != null)
                    {
                        if (actionType == VssActionType.Destroy)
                        {
                            // track destroyed files so missing history can be anticipated
                            // (note that Destroy actions on shared files simply delete
                            // that copy, so destroyed files can't be completely ignored)
                            destroyedFiles.Add(namedAction.Name.PhysicalName);
                        }

                        var targetPath = path + VssDatabase.ProjectSeparator + namedAction.Name.LogicalName;
                        if (exclusionMatcher != null && exclusionMatcher.Matches(targetPath))
                        {
                            // project action targets an excluded file
                            continue;
                        }
                    }

                    Revision revision = new Revision(vssRevision.DateTime,
                        vssRevision.User, item.ItemName, vssRevision.Version,
                        vssRevision.Comment, vssRevision.Action);

                    LinkedList<Revision> revisionSet;
                    if (!sortedRevisions.TryGetValue(vssRevision.DateTime, out revisionSet))
                    {
                        revisionSet = new LinkedList<Revision>();
                        sortedRevisions[vssRevision.DateTime] = revisionSet;
                    }
                    ((ICollection<Revision>)revisionSet).Add(revision);
                    ++revisionCount;
                }
            }
            catch (RecordException e)
            {
                var message = string.Format("Failed to read revisions for {0} ({1}): {2}",
                    path, item.PhysicalName, ExceptionFormatter.Format(e));
                LogException(e, message);
                ReportError(message);
            }
        }
Exemplo n.º 3
0
        private Revision FindCorrespondingAction(Revision rev, VssActionType actionType)
        {
            if (rev.Action.Type != VssActionType.Edit && rev.Action.Type != VssActionType.Label)
            {
                var action = rev.Action as VssNamedAction;


                for (var node = revisionAnalyzer.SortedRevisions[rev.DateTime].Last; node != revisionAnalyzer.SortedRevisions[rev.DateTime].First.Previous; node = node.Previous)
                {
                    var revision = node.Value;
                    if (revision.Action.Type != VssActionType.Edit && revision.Action.Type != VssActionType.Label)
                    {
                        var targetaction = revision.Action as VssNamedAction;
                        if (action.Name.PhysicalName == targetaction.Name.PhysicalName && targetaction.Type == actionType && revision.Comment != null)
                        {
                            return(revision);
                        }
                    }
                }

                // look into sortedrevisions for the comment
                var foundPrevKey = false;
                var foundNextKey = false;
                var prevKey      = (DateTime)DateTime.MinValue;
                var nextKey      = (DateTime)DateTime.MinValue;

                foreach (var key in revisionAnalyzer.SortedRevisions.Keys)
                {
                    if (key == rev.DateTime)
                    {
                        foundPrevKey = true;
                    }
                    else if (foundPrevKey)
                    {
                        foundNextKey = true;
                        nextKey      = key;
                        break;
                    }

                    if (!foundPrevKey)
                    {
                        prevKey = key;
                    }
                }


                if (foundPrevKey && rev.DateTime - prevKey < TimeSpan.FromMinutes(15))
                {
                    for (var node = revisionAnalyzer.SortedRevisions[prevKey].Last; node != revisionAnalyzer.SortedRevisions[prevKey].First.Previous; node = node.Previous)
                    {
                        var revision = node.Value;
                        if (revision.Action.Type != VssActionType.Edit && revision.Action.Type != VssActionType.Label)
                        {
                            var targetaction = revision.Action as VssNamedAction;
                            if (action.Name.PhysicalName == targetaction.Name.PhysicalName && targetaction.Type == actionType && revision.Comment != null)
                            {
                                return(revision);
                            }
                        }
                    }
                }

                if (foundNextKey && nextKey - rev.DateTime < TimeSpan.FromMinutes(15))
                {
                    for (var node = revisionAnalyzer.SortedRevisions[nextKey].First; node != revisionAnalyzer.SortedRevisions[nextKey].Last.Next; node = node.Next)
                    {
                        var revision = node.Value;
                        if (revision.Action.Type != VssActionType.Edit && revision.Action.Type != VssActionType.Label)
                        {
                            var targetaction = revision.Action as VssNamedAction;
                            if (action.Name.PhysicalName == targetaction.Name.PhysicalName && targetaction.Type == actionType && revision.Comment != null)
                            {
                                return(revision);
                            }
                        }
                    }
                }
            }


            return(null);
        }
Exemplo n.º 4
0
        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));
            });
        }
Exemplo n.º 5
0
 private bool IsCommentMissing(Revision r)
 {
     return(r.Comment == null);
 }
Exemplo n.º 6
0
 private bool HasSameComment(Revision rev1, Revision rev2)
 {
     return((rev1.Comment ?? "").Trim() == (rev2.Comment ?? "").Trim());
 }
Exemplo n.º 7
0
        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));
            });
        }
Exemplo n.º 8
0
 private bool HasSameUser(Revision rev1, Revision rev2)
 {
     return((rev1.User ?? "").Trim() == (rev2.User ?? "").Trim());
 }
Exemplo n.º 9
0
 private bool IsCommentMissing(Revision r)
 {
     return r.Comment == null;
 }
Exemplo n.º 10
0
 private bool HasSameUser(Revision rev1, Revision rev2)
 {
     return (rev1.User ?? "").Trim() == (rev2.User ?? "").Trim();
 }
Exemplo n.º 11
0
 private bool HasSameComment(Revision rev1, Revision rev2)
 {
     return (rev1.Comment ?? "").Trim() == (rev2.Comment ?? "").Trim();
 }
Exemplo n.º 12
0
        private Revision FindCorrespondingAction(Revision rev, VssActionType actionType)
        {
            if (rev.Action.Type != VssActionType.Edit && rev.Action.Type != VssActionType.Label)
            {
                var action = rev.Action as VssNamedAction;

                for (var node = revisionAnalyzer.SortedRevisions[rev.DateTime].Last; node != revisionAnalyzer.SortedRevisions[rev.DateTime].First.Previous; node = node.Previous)
                {
                    var revision = node.Value;
                    if (revision.Action.Type != VssActionType.Edit && revision.Action.Type != VssActionType.Label)
                    {
                        var targetaction = revision.Action as VssNamedAction;
                        if (action.Name.PhysicalName == targetaction.Name.PhysicalName && targetaction.Type == actionType && revision.Comment != null)
                            return revision;
                    }
                }

                // look into sortedrevisions for the comment
                var foundPrevKey = false;
                var foundNextKey = false;
                var prevKey = (DateTime)DateTime.MinValue;
                var nextKey = (DateTime)DateTime.MinValue;

                foreach (var key in revisionAnalyzer.SortedRevisions.Keys)
                {

                    if (key == rev.DateTime)
                    {
                        foundPrevKey = true;
                    }
                    else if (foundPrevKey)
                    {
                        foundNextKey = true;
                        nextKey = key;
                        break;
                    }

                    if (!foundPrevKey)
                        prevKey = key;
                }

                if (foundPrevKey && rev.DateTime - prevKey < TimeSpan.FromMinutes(15))
                    for (var node = revisionAnalyzer.SortedRevisions[prevKey].Last; node != revisionAnalyzer.SortedRevisions[prevKey].First.Previous; node = node.Previous)
                    {
                        var revision = node.Value;
                        if (revision.Action.Type != VssActionType.Edit && revision.Action.Type != VssActionType.Label)
                        {
                            var targetaction = revision.Action as VssNamedAction;
                            if (action.Name.PhysicalName == targetaction.Name.PhysicalName && targetaction.Type == actionType && revision.Comment != null)
                                return revision;
                        }
                    }

                if (foundNextKey && nextKey - rev.DateTime < TimeSpan.FromMinutes(15))
                    for (var node = revisionAnalyzer.SortedRevisions[nextKey].First; node != revisionAnalyzer.SortedRevisions[nextKey].Last.Next; node = node.Next)
                    {
                        var revision = node.Value;
                        if (revision.Action.Type != VssActionType.Edit && revision.Action.Type != VssActionType.Label)
                        {
                            var targetaction = revision.Action as VssNamedAction;
                            if (action.Name.PhysicalName == targetaction.Name.PhysicalName && targetaction.Type == actionType && revision.Comment != null)
                                return revision;
                        }
                    }
            }

            return null;
        }
Exemplo n.º 13
0
        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);
        }