internal static ShelveResult TryMergeToShelves(
            IRestApi restApi,
            Branch branch,
            string[] destinationBranches,
            MergeReport mergeReport,
            string taskTitle,
            string botName)
        {
            ShelveResult result = new ShelveResult();

            foreach (string destinationBranch in destinationBranches)
            {
                MergeToResponse mergeToResult = MultilinerMergebotApi.MergeBranchTo(
                    restApi,
                    branch.Repository,
                    branch.FullName,
                    destinationBranch,
                    GetComment(branch.FullName, destinationBranch, taskTitle, botName),
                    MultilinerMergebotApi.MergeToOptions.CreateShelve);

                result.MergeStatusByTargetBranch[destinationBranch] = mergeToResult.Status;

                if (mergeToResult.Status == MergeToResultStatus.MergeNotNeeded)
                {
                    string warningMessage = string.Format(
                        "Branch [{0}] was already merged to [{1}] (No merge needed).",
                        branch.FullName,
                        destinationBranch);

                    result.MergesNotNeededMessages.Add(warningMessage);
                    continue;
                }

                if (IsFailedMergeTo(mergeToResult))
                {
                    string errorMsg = string.Format(
                        "Can't merge branch [{0}] to [{1}]. Reason: {2}.",
                        branch.FullName,
                        destinationBranch,
                        mergeToResult.Message);

                    result.ErrorMessages.Add(errorMsg);

                    BuildMergeReport.AddFailedMergeProperty(
                        mergeReport, mergeToResult.Status, mergeToResult.Message);

                    continue;
                }

                result.ShelvesByTargetBranch[destinationBranch] = mergeToResult.ChangesetNumber;
                BuildMergeReport.AddSucceededMergeProperty(mergeReport, mergeToResult.Status);
            }
            return(result);
        }
            internal static Result PostCheckinStage(
                IRestApi restApi,
                Branch branch,
                MergeReport mergeReport,
                string taskNumber,
                string[] destinationBranches,
                MergeToOperations.CheckinResult mergesToCheckins,
                MultilinerBotConfiguration botConfig,
                string codeReviewsStorageFile)
            {
                if (!HasToRunPlanAfterTaskMerged(botConfig.CI))
                {
                    string noCIMessage =
                        "No Continuous Integration Plug was set for this mergebot. Therefore, no " +
                        "build actions for task " + taskNumber + " will be performed.";

                    mLog.Info(noCIMessage);

                    return(Result.Ok);
                }

                if (mergesToCheckins == null ||
                    mergesToCheckins.ChangesetsByTargetBranch == null ||
                    mergesToCheckins.ChangesetsByTargetBranch.Count == 0)
                {
                    string noChangesetsErrorMessage = string.Format(
                        "Something wrong happened. There are no merge-to changesets to build after " +
                        "merging branch [{0}] to its destination branches.", branch.FullName);

                    mLog.Info(noChangesetsErrorMessage);

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, noChangesetsErrorMessage, botConfig.Notifiers);

                    return(Result.Failed);
                }

                string startTestingMessage = string.Format(
                    "Testing branch [{0}] after being merged in the following destination branches: [{1}].",
                    branch.FullName,
                    string.Join(", ", mergesToCheckins.ChangesetsByTargetBranch.Keys));

                mLog.Info(startTestingMessage);

                Notifier.NotifyTaskStatus(
                    restApi, branch.Owner, startTestingMessage, botConfig.Notifiers);

                int iniTime = Environment.TickCount;

                BuildOperations.Result result = BuildOperations.TryBuildTask(
                    restApi,
                    branch,
                    mergeReport,
                    taskNumber,
                    destinationBranches,
                    mergesToCheckins.ChangesetsByTargetBranch,
                    Messages.BuildProperties.StageValues.POST_CHECKIN,
                    botConfig);

                BuildMergeReport.AddBuildTimeProperty(
                    mergeReport, Environment.TickCount - iniTime);

                if (result.AreAllSuccessful)
                {
                    BuildMergeReport.AddSucceededBuildProperty(
                        mergeReport, botConfig.CI.PlanAfterCheckin);

                    string notifyOKMesage = string.Format(
                        "Build successful after merging branch [{0}] " +
                        "to the following destination branches: [{1}].",
                        branch.FullName,
                        string.Join(", ", mergesToCheckins.ChangesetsByTargetBranch.Keys));

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notifyOKMesage, botConfig.Notifiers);

                    return(Result.Ok);
                }

                string errorMessage = string.Join(Environment.NewLine, result.ErrorMessages);

                BuildMergeReport.AddFailedBuildProperty(
                    mergeReport, botConfig.CI.PlanAfterCheckin, errorMessage);

                Notifier.NotifyTaskStatus(
                    restApi, branch.Owner, errorMessage, botConfig.Notifiers);

                return(Result.Failed);
            }
            internal static bool PreCheckinStage(
                IRestApi restApi,
                Branch branch,
                MergeReport mergeReport,
                string taskNumber,
                string[] destinationBranches,
                MergeToOperations.ShelveResult mergesToShelves,
                MultilinerBotConfiguration botConfig,
                string codeReviewsStorageFile)
            {
                if (botConfig.CI == null)
                {
                    string noCIMessage =
                        "No Continuous Integration Plug was set for this mergebot. Therefore, no " +
                        "build actions for task " + taskNumber + " will be performed.";

                    mLog.Info(noCIMessage);

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, noCIMessage, botConfig.Notifiers);

                    return(true);
                }

                if (mergesToShelves == null ||
                    mergesToShelves.ShelvesByTargetBranch == null ||
                    mergesToShelves.ShelvesByTargetBranch.Count == 0)
                {
                    string noShelvesErrorMessage =
                        "Something wrong happened. There are no merge-to shelves to build task " + taskNumber;

                    mLog.Info(noShelvesErrorMessage);

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, noShelvesErrorMessage, botConfig.Notifiers);

                    ChangeTaskStatus.SetTaskAsFailed(
                        restApi, branch, taskNumber, botConfig, codeReviewsStorageFile);

                    return(false);
                }

                string startTestingMessage = string.Format(
                    "Testing branch [{0}] before being merged in the following destination branches: [{1}].",
                    branch.FullName,
                    string.Join(", ", mergesToShelves.ShelvesByTargetBranch.Keys));

                mLog.Info(startTestingMessage);

                ChangeTaskStatus.SetTaskAsTesting(restApi, branch, taskNumber, botConfig);

                Notifier.NotifyTaskStatus(
                    restApi, branch.Owner, startTestingMessage, botConfig.Notifiers);

                int iniTime = Environment.TickCount;

                BuildOperations.Result result = BuildOperations.TryBuildTask(
                    restApi,
                    branch,
                    mergeReport,
                    taskNumber,
                    destinationBranches,
                    mergesToShelves.ShelvesByTargetBranch,
                    Messages.BuildProperties.StageValues.PRE_CHECKIN,
                    botConfig);

                BuildMergeReport.AddBuildTimeProperty(
                    mergeReport, Environment.TickCount - iniTime);

                if (result.AreAllSuccessful)
                {
                    BuildMergeReport.AddSucceededBuildProperty(
                        mergeReport, botConfig.CI.PlanBranch);

                    return(true);
                }

                string errorMessage = string.Join(Environment.NewLine, result.ErrorMessages);

                BuildMergeReport.AddFailedBuildProperty(
                    mergeReport, botConfig.CI.PlanBranch, errorMessage);

                ChangeTaskStatus.SetTaskAsFailed(
                    restApi, branch, taskNumber, botConfig, codeReviewsStorageFile);

                Notifier.NotifyTaskStatus(
                    restApi, branch.Owner, errorMessage, botConfig.Notifiers);

                return(false);
            }
        public static Result TryProcessBranch(
            IRestApi restApi,
            Branch branch,
            MultilinerBotConfiguration botConfig,
            string botName,
            string codeReviewsStorageFile)
        {
            string      taskNumber  = null;
            MergeReport mergeReport = null;

            string[] destinationBranches = null;
            MergeToOperations.ShelveResult mergesToShelves = null;
            string notificationMsg = null;

            try
            {
                mLog.InfoFormat("Getting task number of branch {0} ...", branch.FullName);

                taskNumber = GetTaskNumber(branch.FullName, botConfig.BranchPrefix);

                if (!IsTaskReady(
                        restApi,
                        taskNumber,
                        botConfig.Issues,
                        botConfig.Plastic.IsApprovedCodeReviewFilterEnabled,
                        branch.Repository,
                        branch.Id,
                        codeReviewsStorageFile))
                {
                    return(Result.NotReady);
                }

                destinationBranches = GetMergeToDestinationBranches(
                    restApi,
                    branch,
                    botConfig.MergeToBranchesAttrName);

                if (destinationBranches == null || destinationBranches.Length == 0)
                {
                    notificationMsg = string.Format(
                        "The attribute [{0}] of branch [{1}@{2}@{3}] is not properly set. " +
                        "Branch [{1}@{2}@{3}] status will be set as 'failed': [{4}].",
                        botConfig.MergeToBranchesAttrName,
                        branch.FullName,
                        branch.Repository,
                        botConfig.Server,
                        botConfig.Plastic.StatusAttribute.FailedValue);

                    mLog.Warn(notificationMsg);

                    ChangeTaskStatus.SetTaskAsFailed(
                        restApi, branch, taskNumber, botConfig, codeReviewsStorageFile);

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                    return(Result.Failed);
                }

                foreach (string destinationBranch in destinationBranches)
                {
                    if (ExistsBranch(restApi, destinationBranch, branch.Repository))
                    {
                        continue;
                    }

                    notificationMsg = string.Format(
                        "The destination branch [{0}@{1}@{2}] specified in attribute [{3}] " +
                        "of branch [{4}@{1}@{2}] does not exist. " +
                        "Branch [{4}@{1}@{2}] status will be set as 'failed': [{5}].",
                        destinationBranch,
                        branch.Repository,
                        botConfig.Server,
                        botConfig.MergeToBranchesAttrName,
                        branch.FullName,
                        botConfig.Plastic.StatusAttribute.FailedValue);

                    mLog.Warn(notificationMsg);

                    ChangeTaskStatus.SetTaskAsFailed(
                        restApi, branch, taskNumber, botConfig, codeReviewsStorageFile);

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                    return(Result.Failed);
                }

                foreach (string destinationBranch in destinationBranches)
                {
                    if (IsMergeAllowed(restApi, branch, destinationBranch))
                    {
                        continue;
                    }

                    return(Result.NotReady);
                }

                mLog.InfoFormat("Building the merge report of task {0} ...", taskNumber);

                mergeReport = BuildMergeReport.Build(
                    MultilinerMergebotApi.GetBranch(restApi, branch.Repository, branch.FullName));

                string taskTittle;
                string taskUrl;

                if (GetIssueInfo(
                        restApi,
                        taskNumber,
                        botConfig.Issues,
                        out taskTittle,
                        out taskUrl))
                {
                    BuildMergeReport.AddIssueProperty(mergeReport, taskTittle, taskUrl);
                }

                mLog.InfoFormat("Trying to shelve server-side-merge from [{0}] to [{1}]",
                                branch.FullName, string.Join(", ", destinationBranches));

                mergesToShelves =
                    MergeToOperations.TryMergeToShelves(
                        restApi,
                        branch,
                        destinationBranches,
                        mergeReport,
                        taskTittle,
                        botName);

                if (NoMergesNeeded(mergesToShelves))
                {
                    ChangeTaskStatus.Result chStatResult = ChangeTaskStatus.SetTaskAsMerged(
                        restApi,
                        branch,
                        taskNumber,
                        botConfig,
                        codeReviewsStorageFile);

                    notificationMsg = string.Join(Environment.NewLine, mergesToShelves.MergesNotNeededMessages);

                    if (!string.IsNullOrWhiteSpace(chStatResult.ErrorMessage))
                    {
                        notificationMsg = string.Concat(
                            notificationMsg, Environment.NewLine, chStatResult.ErrorMessage);
                    }

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                    return(Result.Failed);
                }

                if (mergesToShelves.ErrorMessages.Count > 0)
                {
                    ChangeTaskStatus.Result chStatResult = ChangeTaskStatus.SetTaskAsFailed(
                        restApi,
                        branch,
                        taskNumber,
                        botConfig,
                        codeReviewsStorageFile);

                    notificationMsg = string.Join(Environment.NewLine, mergesToShelves.ErrorMessages);

                    if (mergesToShelves.MergesNotNeededMessages.Count > 0)
                    {
                        notificationMsg = string.Concat(
                            notificationMsg,
                            Environment.NewLine,
                            string.Join(Environment.NewLine, mergesToShelves.MergesNotNeededMessages));
                    }

                    if (!string.IsNullOrWhiteSpace(chStatResult.ErrorMessage))
                    {
                        notificationMsg = string.Concat(
                            notificationMsg, Environment.NewLine, chStatResult.ErrorMessage);
                    }

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                    return(Result.Failed);
                }

                if (mergesToShelves.MergesNotNeededMessages.Count > 0)
                {
                    string alreadyMergedMessage = string.Join(
                        Environment.NewLine + "\t", mergesToShelves.MergesNotNeededMessages);

                    notificationMsg = string.Format(
                        "Branch [{0}] is already merged to some " +
                        "of the specified destination branches in the attribute [{1}]. " +
                        "The {2} mergebot will continue building the " +
                        "merge(s) from branch [{0}] to [{3}].{4}{4}" +
                        "Report of already merged branches:{4}\t{5}",
                        branch.FullName,
                        botConfig.MergeToBranchesAttrName,
                        botName,
                        string.Join(", ", mergesToShelves.ShelvesByTargetBranch.Keys),
                        Environment.NewLine,
                        alreadyMergedMessage);

                    mLog.Info(notificationMsg);

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notificationMsg, botConfig.Notifiers);
                }

                bool allBuildsOk = Build.PreCheckinStage(
                    restApi,
                    branch,
                    mergeReport,
                    taskNumber,
                    destinationBranches,
                    mergesToShelves,
                    botConfig,
                    codeReviewsStorageFile);

                if (!allBuildsOk)
                {
                    return(Result.Failed);
                }

                MergeToOperations.CheckinResult mergesToCheckins = MergeToOperations.TryApplyShelves(
                    restApi,
                    branch,
                    destinationBranches,
                    mergesToShelves,
                    mergeReport,
                    taskNumber,
                    taskTittle,
                    botName,
                    botConfig,
                    codeReviewsStorageFile);

                //checkin went OK in all target branches
                if (mergesToCheckins.ErrorMessages.Count == 0 &&
                    mergesToCheckins.DestinationNewChangesWarnings.Count == 0)
                {
                    mLog.InfoFormat("Setting branch {0} as 'integrated'...", branch.FullName);
                    ChangeTaskStatus.SetTaskAsMerged(
                        restApi,
                        branch,
                        taskNumber,
                        botConfig,
                        codeReviewsStorageFile);

                    notificationMsg = string.Format(
                        "OK: Branch [{0}] was successfully merged to [{1}]",
                        branch.FullName,
                        string.Join(", ", mergesToShelves.ShelvesByTargetBranch.Keys));

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                    return(Build.PostCheckinStage(
                               restApi,
                               branch,
                               mergeReport,
                               taskNumber,
                               destinationBranches,
                               mergesToCheckins,
                               botConfig,
                               codeReviewsStorageFile));
                }

                //some of the checkins went wrong -> we must run the post-ci plan for the successful ones,
                //but force-set as failed the source branch (or requeue if the errors are due to New DstChanges
                if (mergesToCheckins.ErrorMessages.Count > 0)
                {
                    ChangeTaskStatus.Result chStatResult = ChangeTaskStatus.SetTaskAsFailed(
                        restApi,
                        branch,
                        taskNumber,
                        botConfig,
                        codeReviewsStorageFile);

                    string checkinErrorMessage = string.Join(
                        Environment.NewLine + "\t", mergesToCheckins.ErrorMessages);

                    if (mergesToCheckins.DestinationNewChangesWarnings.Count > 0)
                    {
                        string dstChangesErrorMessage = string.Join(
                            Environment.NewLine + "\t", mergesToCheckins.DestinationNewChangesWarnings);

                        checkinErrorMessage = string.Concat(
                            checkinErrorMessage,
                            Environment.NewLine + "\t",
                            dstChangesErrorMessage);
                    }

                    notificationMsg = string.Format(
                        "Failed build. The result of building merges from branch [{0}] to [{1}] went OK, " +
                        "but there were some errors checking-in the resulting shelves:{2}\t{3}{2}{2}{4}",
                        branch.FullName,
                        string.Join(", ", mergesToShelves.ShelvesByTargetBranch.Keys),
                        Environment.NewLine,
                        checkinErrorMessage,
                        string.IsNullOrWhiteSpace(chStatResult.ErrorMessage) ?
                        string.Empty : chStatResult.ErrorMessage);

                    mLog.Warn(notificationMsg);

                    if (mergesToCheckins.ChangesetsByTargetBranch.Count == 0)
                    {
                        Notifier.NotifyTaskStatus(
                            restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                        return(Result.Failed);
                    }

                    Build.PostCheckinStage(
                        restApi,
                        branch,
                        mergeReport,
                        taskNumber,
                        destinationBranches,
                        mergesToCheckins,
                        botConfig,
                        codeReviewsStorageFile);

                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                    return(Result.Failed);
                }

                //LAST block -> if (mergesToCheckins.DestinationNewChangesWarnings.Count > 0)
                //Some of the checkins went wrong due to new changeset on at least a destination branch->
                //We must run the post-ci plan for the successful ones,
                //but force requeuing of source branch, so the failed merges to dst branches are re-run
                mLog.InfoFormat("Setting branch {0} as 'resolved' (enqueue) ...", branch.FullName);
                MultilinerMergebotApi.ChangeBranchAttribute(
                    restApi, branch.Repository, branch.FullName,
                    botConfig.Plastic.StatusAttribute.Name,
                    botConfig.Plastic.StatusAttribute.ResolvedValue);

                string dstBranchesNewCsetsMsg = string.Join(
                    Environment.NewLine + "\t", mergesToCheckins.DestinationNewChangesWarnings);

                notificationMsg = string.Format(
                    "Branch [{0}] will be enqueued again, as new changesets appeared in " +
                    "merge destination branches, and thus, the branch needs to be tested again to " +
                    "include those new changesets in the merge. Full report:{1}\t{2}",
                    branch.FullName,
                    Environment.NewLine,
                    dstBranchesNewCsetsMsg);

                mLog.Warn(notificationMsg);

                if (mergesToCheckins.ChangesetsByTargetBranch.Count == 0)
                {
                    Notifier.NotifyTaskStatus(
                        restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                    return(Result.NotReady);
                }

                Build.PostCheckinStage(
                    restApi,
                    branch,
                    mergeReport,
                    taskNumber,
                    destinationBranches,
                    mergesToCheckins,
                    botConfig,
                    codeReviewsStorageFile);

                Notifier.NotifyTaskStatus(
                    restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                return(Result.NotReady);
            }
            catch (Exception ex)
            {
                mLog.ErrorFormat(
                    "The attempt to process task {0} failed for branch {1}: {2}",
                    taskNumber, branch.FullName, ex.Message);

                mLog.DebugFormat(
                    "StackTrace:{0}{1}", Environment.NewLine, ex.StackTrace);

                ChangeTaskStatus.SetTaskAsFailed(
                    restApi,
                    branch,
                    taskNumber,
                    botConfig,
                    codeReviewsStorageFile);

                notificationMsg = string.Format(
                    "Can't process branch [{0}] because of an unexpected error: {1}.",
                    branch.FullName,
                    ex.Message);

                Notifier.NotifyTaskStatus(
                    restApi, branch.Owner, notificationMsg, botConfig.Notifiers);

                BuildMergeReport.SetUnexpectedExceptionProperty(mergeReport, ex.Message);

                return(Result.Failed);
            }
            finally
            {
                ReportMerge(restApi, branch.Repository, branch.FullName, botName, mergeReport);

                MergeToOperations.SafeDeleteShelves(
                    restApi, branch.Repository, destinationBranches, mergesToShelves);
            }
        }
        internal static CheckinResult TryApplyShelves(
            IRestApi restApi,
            Branch branch,
            string[] destinationBranches,
            ShelveResult shelves,
            MergeReport mergeReport,
            string taskNumber,
            string taskTitle,
            string botName,
            MultilinerBotConfiguration botConfig,
            string codeReviewsStorageFile)
        {
            CheckinResult result = new CheckinResult();
            int           shelveId;

            foreach (string destinationBranch in destinationBranches)
            {
                if (!shelves.ShelvesByTargetBranch.ContainsKey(destinationBranch))
                {
                    continue;
                }

                shelveId = shelves.ShelvesByTargetBranch[destinationBranch];

                mLog.InfoFormat(
                    "Checking-in shelveset [{0}] from branch [{1}] to [{2}]",
                    shelveId, branch.FullName, destinationBranch);

                MergeToResponse mergeResult = MultilinerMergebotApi.MergeShelveTo(
                    restApi,
                    branch.Repository,
                    shelveId,
                    destinationBranch,
                    GetComment(branch.FullName, destinationBranch, taskTitle, botName),
                    MultilinerMergebotApi.MergeToOptions.EnsureNoDstChanges);

                result.MergeStatusByTargetBranch[destinationBranch] = mergeResult.Status;

                BuildMergeReport.UpdateMergeProperty(mergeReport, mergeResult.Status, mergeResult.ChangesetNumber);

                if (mergeResult.Status == MergeToResultStatus.OK)
                {
                    result.ChangesetsByTargetBranch[destinationBranch] = mergeResult.ChangesetNumber;

                    mLog.InfoFormat(
                        "Checkin: Created changeset [{0}] in branch [{1}]",
                        mergeResult.ChangesetNumber, destinationBranch);

                    continue;
                }

                if (mergeResult.Status == MergeToResultStatus.DestinationChanges)
                {
                    string dstWarnMessage = string.Format(
                        "Can't checkin shelve [{0}], the resulting shelve from merging branch " +
                        "[{1}] to [{2}]. Reason: new changesets appeared in destination branch " +
                        "while mergebot {3} was processing the merge from [{1}] to [{2}].{4}{5}",
                        shelveId,
                        branch.FullName,
                        destinationBranch,
                        botName,
                        Environment.NewLine,
                        mergeResult.Message);

                    // it should checkin the shelve only on the exact parent shelve cset.
                    // if there are new changes in the destination branch enqueue again the task
                    result.DestinationNewChangesWarnings.Add(dstWarnMessage);
                    continue;
                }

                string errorMsg = string.Format(
                    "Can't checkin shelve [{0}], the resulting shelve from merging branch " +
                    "[{1}] to [{2}]. Reason: {3}",
                    shelveId,
                    branch.FullName,
                    destinationBranch,
                    mergeResult.Message);

                result.ErrorMessages.Add(errorMsg);
            }

            return(result);
        }