Example #1
0
        /// <summary>
        /// Main driver for the code review submission tool.
        /// </summary>
        /// <param name="context"> The database context. </param>
        /// <param name="sd"> Source control client. </param>
        /// <param name="sourceControlInstanceId"> The ID of source control instance to use.
        ///     This is an ID of a record in the database that is unique for a given CL namespace.</param>
        /// <param name="changeList"> CL. </param>
        /// <param name="reviewers"> The list of people to who to send the code review request. </param>
        /// <param name="invitees"> The list of people who are invited to participate in the code review
        /// (but need to positively acknowledge the invitation by choosing to review the code). </param>
        /// <param name="link"> Optionally, a link to a file or a web page to be displayed in CL page. </param>
        /// <param name="linkDescr"> An optional description of the said link. </param>
        /// <param name="description"> An optional description of the changelist, overrides any description
        ///                             from the source control tool. </param>
        /// <param name="bugTracker">The server for accessing bugs.</param>
        /// <param name="bugIds">List of bugs to associate with review page.</param>
        /// <param name="force"> If branched files are included, confirms the submission even if there
        /// are too many files. </param>
        /// <param name="includeBranchedFiles"> </param>
        /// <param name="preview">If true, do not commit changes.</param>
        private static void ProcessCodeReview(
            string databaseServer,
            ISourceControl sd,
            int sourceControlInstanceId,
            string changeList,
            List<string> reviewers,
            List<string> invitees,
            string link,
            string linkDescr,
            string description,
            IBugServer bugServer,
            List<string> bugIds,
            bool force,
            bool includeBranchedFiles,
            bool preview,
            string impersonateUserName)
        {
            Change change = sd.GetChange(changeList, includeBranchedFiles);
            if (change == null)
                return;

            changeList = change.ChangeListFriendlyName ?? changeList;

            if (change == null)
                return;

            if (includeBranchedFiles && !force)
            {
                int branchedFiles = 0;
                foreach (SourceControl.ChangeFile file in change.Files)
                {
                    if (file.IsText && (file.Action == SourceControl.ChangeFile.SourceControlAction.BRANCH ||
                        file.Action == SourceControl.ChangeFile.SourceControlAction.INTEGRATE))
                        ++branchedFiles;
                }

                if (branchedFiles > MaximumIntegratedFiles)
                {
                    Console.WriteLine("There are {0} branched/integrated files in this change.", branchedFiles);
                    Console.WriteLine("Including the full text of so many files in review may increase the size");
                    Console.WriteLine("of the review database considerably.");
                    Console.Write("Are you sure you want to proceed (Yes/No)? ");
                    string response = Console.ReadLine();
                    Console.WriteLine("NOTE: In the future you can override this check by specifying --force");
                    Console.WriteLine("on the command line.");
                    if (response[0] != 'y' && response[0] != 'Y')
                        return;
                }
            }

            if (!VerifyDiffIntegrity(change))
                return;

            CodeReviewDataContext context = new CodeReviewDataContext("Data Source=" + databaseServer +
                ";Initial Catalog=CodeReview;Integrated Security=True");

            var existingReviewQuery = from rv in context.ChangeLists
                                      where rv.CL == changeList && rv.SourceControlId == sourceControlInstanceId
                                      select rv;

            // is this a new review, or a refresh of an existing one?
            bool isNewReview = (existingReviewQuery.Count() == 0);

            int? changeId = null;
            if (description == null)
                description = change.Description;

            context.Connection.Open();
            using (context.Connection)
            using (context.Transaction = context.Connection.BeginTransaction(System.Data.IsolationLevel.Snapshot))
            {
                // This more like "GetOrAddChangeList", as it returns the id of any pre-existing changelist
                // matching 'changeList'.
                if (impersonateUserName == null)
                {
                    context.AddChangeList(
                        sourceControlInstanceId, change.SdClientName, changeList, description,
                        change.TimeStamp.ToUniversalTime(), ref changeId);
                }
                else
                {
                    var changeListDb = (from c in context.ChangeLists
                                        where c.SourceControlId == sourceControlInstanceId &&
                                              c.UserName == impersonateUserName &&
                                              c.UserClient == change.SdClientName &&
                                              c.CL == changeList
                                        select c).FirstOrDefault();

                    if (changeListDb == null)
                    {
                        changeListDb = new ChangeList()
                        {
                            SourceControlId = sourceControlInstanceId,
                            UserName = impersonateUserName,
                            UserClient = change.SdClientName,
                            CL = changeList,
                            Description = description,
                            TimeStamp = change.TimeStamp.ToUniversalTime(),
                            Stage = 0
                        };

                        context.ChangeLists.InsertOnSubmit(changeListDb);
                        context.SubmitChanges(); // Not actually submitted until transaction completes.
                    }

                    changeId = changeListDb.Id;
                }

                // Get the list of files corresponding to this changelist already on the server.
                var dbChangeFiles = (from fl in context.ChangeFiles
                                     where fl.ChangeListId == changeId && fl.IsActive
                                     select fl)
                                     .OrderBy(file => file.ServerFileName)
                                     .GetEnumerator();

                var inChangeFiles = (from fl in change.Files
                                     select fl)
                                     .OrderBy(file => file.ServerFileName)
                                     .GetEnumerator();

                bool dbChangeFilesValid = dbChangeFiles.MoveNext();
                bool inChangeFilesValid = inChangeFiles.MoveNext();

                // Uses bitwise OR to ensure that both MoveNext methods are invoked.
                FileExistsIn existsIn = FileExistsIn.Neither;
                while (dbChangeFilesValid || inChangeFilesValid)
                {
                    int comp;
                    if (!dbChangeFilesValid) // No more files in database
                        comp = 1;
                    else if (!inChangeFilesValid) // No more files in change list.
                        comp = -1;
                    else
                        comp = string.Compare(dbChangeFiles.Current.ServerFileName,
                                              inChangeFiles.Current.ServerFileName);

                    if (comp < 0) // We have a file in DB, but not in source control. Delete it from DB.
                    {
                        Console.WriteLine("File {0} has been dropped from the change list.",
                                          dbChangeFiles.Current.ServerFileName);
                        context.RemoveFile(dbChangeFiles.Current.Id);

                        dbChangeFilesValid = dbChangeFiles.MoveNext();
                        existsIn = FileExistsIn.Database;
                        continue;
                    }

                    SourceControl.ChangeFile file = inChangeFiles.Current;

                    int? fid = null;
                    if (comp > 0) // File in source control, but not in DB
                    {
                        Console.WriteLine("Adding file {0}", file.ServerFileName);
                        context.AddFile(changeId, file.LocalFileName, file.ServerFileName, ref fid);
                        existsIn = FileExistsIn.Change;
                    }
                    else // Both files are here. Need to check the versions.
                    {
                        fid = dbChangeFiles.Current.Id;
                        existsIn = FileExistsIn.Both;
                    }

                    bool haveBase = (from bv in context.FileVersions
                                     where bv.FileId == fid && bv.Revision == file.Revision && bv.IsRevisionBase
                                     select bv).Count() > 0;

                    var versionQuery = from fv in context.FileVersions
                                       where fv.FileId == fid && fv.Revision == file.Revision
                                       orderby fv.Id descending
                                       select fv;

                    var version = versionQuery.FirstOrDefault();
                    bool haveVersion = false;
                    if (version != null && version.Action == (int)file.Action &&
                        BodiesEqual(NormalizeLineEndings(file.Data), NormalizeLineEndings(version.Text)))
                        haveVersion = true;

                    int? vid = null;
                    if (!haveBase && file.IsText &&
                        (file.Action == SourceControl.ChangeFile.SourceControlAction.EDIT ||
                         (file.Action == SourceControl.ChangeFile.SourceControlAction.INTEGRATE &&
                          includeBranchedFiles)))
                    {
                        string fileBody;
                        DateTime? dateTime;
                        fileBody = sd.GetFile(
                            file.OriginalServerFileName == null ? file.ServerFileName : file.OriginalServerFileName,
                            file.Revision, out dateTime);
                        if (fileBody == null)
                        {
                            Console.WriteLine("ERROR: Could not retrieve {0}#{1}", file.ServerFileName, file.Revision);
                            return;
                        }

                        Console.WriteLine("Adding base revision for {0}#{1}", file.ServerFileName, file.Revision);
                        context.AddVersion(fid, file.Revision, (int)file.Action, dateTime, true, true, true, fileBody,
                            ref vid);
                    }
                    else
                    {
                        // Do this so we print the right thing.
                        haveBase = true;
                    }

                    if (!haveVersion)
                    {
                        if (file.Action == SourceControl.ChangeFile.SourceControlAction.DELETE)
                        {
                            context.AddVersion(
                                fid, file.Revision, (int)file.Action, null, file.IsText, false, false, null, ref vid);
                        }
                        else if ((file.Action == SourceControl.ChangeFile.SourceControlAction.RENAME) || !file.IsText)
                        {
                            context.AddVersion(fid, file.Revision, (int)file.Action, file.LastModifiedTime, file.IsText,
                                false, false, null, ref vid);
                        }
                        else if (file.Action == SourceControl.ChangeFile.SourceControlAction.ADD ||
                            file.Action == SourceControl.ChangeFile.SourceControlAction.BRANCH)
                        {
                            context.AddVersion(fid, file.Revision, (int)file.Action, file.LastModifiedTime, file.IsText,
                                true, false, file.Data, ref vid);
                        }
                        else if (file.Action == SourceControl.ChangeFile.SourceControlAction.EDIT ||
                            file.Action == SourceControl.ChangeFile.SourceControlAction.INTEGRATE)
                        {
                            context.AddVersion(fid, file.Revision, (int)file.Action, file.LastModifiedTime, file.IsText,
                                false, false, file.Data, ref vid);
                        }

                        string textFlag = file.IsText ? "text" : "binary";
                        string action;
                        switch (file.Action)
                        {
                            case SourceControl.ChangeFile.SourceControlAction.ADD:
                                action = "add";
                                break;

                            case SourceControl.ChangeFile.SourceControlAction.EDIT:
                                action = "edit";
                                break;

                            case SourceControl.ChangeFile.SourceControlAction.DELETE:
                                action = "delete";
                                break;

                            case SourceControl.ChangeFile.SourceControlAction.BRANCH:
                                action = "branch";
                                break;

                            case SourceControl.ChangeFile.SourceControlAction.INTEGRATE:
                                action = "integrate";
                                break;

                            case SourceControl.ChangeFile.SourceControlAction.RENAME:
                                action = "rename";
                                break;

                            default:
                                action = "unknown";
                                break;
                        }

                        if (version != null && vid == version.Id)
                        {
                            // The file was already there. This happens sometimes because SQL rountrip (to database
                            // and back) is not an identity: somtimes the non-graphical characters change depending
                            // on the database code page. But if the database has returned a number, and this number
                            // is the same as the previous version id, we know that the file has not really been added.
                            haveVersion = true;
                        }
                        else
                        {
                            Console.WriteLine("Added version for {0}#{1}({2}, {3})", file.ServerFileName, file.Revision,
                                textFlag, action);
                        }
                    }

                    if (haveBase && haveVersion)
                        Console.WriteLine("{0} already exists in the database.", file.ServerFileName);

                    if ((existsIn & FileExistsIn.Database) == FileExistsIn.Database)
                        dbChangeFilesValid = dbChangeFiles.MoveNext();
                    if ((existsIn & FileExistsIn.Change) == FileExistsIn.Change)
                        inChangeFilesValid = inChangeFiles.MoveNext();

                    existsIn = FileExistsIn.Neither;
                }

                foreach (string reviewer in reviewers)
                {
                    int? reviewId = null;
                    context.AddReviewer(reviewer, changeId.Value, ref reviewId);
                }

                foreach (string invitee in invitees)
                {
                    context.AddReviewRequest(changeId.Value, invitee);
                }

                if (link != null)
                {
                    int? attachmentId = null;
                    context.AddAttachment(changeId.Value, linkDescr, link, ref attachmentId);
                }

                if (preview)
                    context.Transaction.Rollback();
                else
                    context.Transaction.Commit();
            }

            var reviewSiteUrl = Environment.GetEnvironmentVariable("REVIEW_SITE_URL");
            if (reviewSiteUrl != null)
            {
                var reviewPage = reviewSiteUrl;
                if (!reviewPage.EndsWith("/"))
                    reviewPage += "/";
                reviewPage += @"default.aspx?cid=" + changeId.ToString();
                Console.WriteLine("Change {0} is ready for review, and may be viewed at", changeList);
                Console.WriteLine("   {0}", reviewPage);

                var allBugIds = Enumerable.Union(change.BugIds, bugIds);
                if (bugServer != null && allBugIds.Count() > 0)
                {
                    Console.WriteLine("Connecting to TFS Work Item Server");
                    if (bugServer.Connect())
                    {
                        foreach (var bugId in allBugIds)
                        {
                            var bug = bugServer.GetBug(bugId);
                            if (bug.AddLink(new Uri(reviewPage), null))
                                Console.WriteLine("Bug {0} has been linked to review page.", bugId);
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("Change {0} is ready for review.", changeList);
            if (isNewReview)
            {
                if (reviewers.Count == 0 && invitees.Count == 0)
                {
                    Console.WriteLine("Note: no reviewers specified. You can add them later using this utility.");
                }
                else
                {
                    Console.WriteLine("If the mail notifier is enabled, the reviewers will shortly receive mail");
                    Console.WriteLine("asking them to review your changes.");
                }
            }
            else
            {
                Console.WriteLine("Note: existing reviewers will not be immediately informed of this update.");
                Console.WriteLine("To ask them to re-review your updated changes, you can visit the review website");
                Console.WriteLine("and submit a response.");
            }
            }

            if (preview)
                Console.WriteLine("In preview mode -- no actual changes committed.");
        }