public IList <Conflict> Merge(int changesetFrom, int changesetTo, MergeOptionsEx mergeOptions = MergeOptionsEx.None) { CheckPathsDefined(); string sourcePath = _branchMap[BranchType.Source].Local; string targetPath = _branchMap[BranchType.Target].Local; var versionFrom = new ChangesetVersionSpec(changesetFrom); var versionTo = new ChangesetVersionSpec(changesetTo); GetStatus status = WorkSpace.Merge(sourcePath, targetPath, versionFrom, versionTo, LockLevel.Unchanged, RecursionType.Full, mergeOptions); if (status.NoActionNeeded && status.NumOperations == 0) { Popups.ShowMessage("No changes found when merging cs " + versionFrom.ToString() + "-" + versionTo.ToString() + ".", MessageBoxImage.Asterisk); } /* Interpreting the return value: * http://teamfoundation.blogspot.fi/2006/11/merging-and-resolving-conflicts-using.html * NoActionNeeded == true && NumOperations == 0 – means that no changes in source needed to be merged, so no actual changes were pended * NoActionNeeded == false && NumOperations > 0 && HaveResolvableWarnings == false – means that merges were performed, * but all conflicts were resolved automatically. Need to check in pended merge changes and that’s about it * NoActionNeeded == false && NumConflicts > 0 – merge was performed and there are conflicts to resolve * */ return(GetConflicts(targetPath, status)); }
/// <summary> /// Merges a range changesets. Does not perform a check in. /// </summary> /// <returns>A tuple, where the boolean signals success, and the string contains and error message in case of failure.</returns> public static Tuple <bool, string> MergeRange(MyTfsConnection tfsConnection, Changeset[] changesets, IList <DirectoryInfo> branches, IProgress <ProgressReportArgs> reporter, CancellationToken cancelToken, FileInfo tfExecutable, IPopupService popupService, MergeOptionsEx mergeOptions = MergeOptionsEx.None) { Debug.Assert(branches.Count == 2, "Merging as range currently only supports two branches (1 source + 1 target)"); //if (doCheckin == false) //{ // Debug.Assert(branches.Count == 2, "Currently not supported to have more than two branches in the chain when not checkin in after each merge."); //} if (changesets.Any() && branches.Count > 1) { DirectoryInfo sourceBranch = branches[0]; DirectoryInfo targetBranch = branches[1]; tfsConnection.SetLocalPath(BranchType.Source, sourceBranch.FullName); tfsConnection.SetLocalPath(BranchType.Target, targetBranch.FullName); string whatAreWeDoing = $"Merging changesets {changesets.First().ChangesetId} - {changesets.Last().ChangesetId}"; reporter?.Report(new ProgressReportArgs(0, null, whatAreWeDoing)); cancelToken.ThrowIfCancellationRequested(); try { CheckInitialConflicts(tfsConnection, targetBranch, whatAreWeDoing); int firstId = changesets.First().ChangesetId; int lastId = changesets.Last().ChangesetId; IList <Conflict> conflicts = tfsConnection.Merge(firstId, lastId, mergeOptions); cancelToken.ThrowIfCancellationRequested(); if (conflicts.Count > 0 && !conflicts.All(c => c.AutoResolved)) { //ResolveConflictsWithAPICall(conflicts, tfsConnection, checkinComment); var conflictRetval = ConflictResolver.ResolveConflictsWithExternalExecutable(tfExecutable, tfsConnection, targetBranch.FullName, popupService); if (!conflictRetval.Item1) { throw new MyTfsConflictException(conflictRetval.Item2.Count() + " unresolved conflicts."); } cancelToken.ThrowIfCancellationRequested(); } reporter?.Report(new ProgressReportArgs(1)); } catch (MyTfsConnectionException ex) { reporter?.Report(new ProgressReportArgs(0, "Error", "Error " + whatAreWeDoing + "\n\n" + ex.ToString())); return(Tuple.Create(false, ex.ToString())); } return(Tuple.Create(true, "Successfully merged.")); } return(Tuple.Create(false, "No changesets, or not >= 2 branches.")); }
/// <summary> /// Build a comment for checkin for a single change set. Tries to remove any previously generated comments from the given comment string. /// </summary> public static string GetComment(string originalComment, int changesetId, string owner, string sourceBranch, string targetBranch, MergeOptionsEx mergeOptions = MergeOptionsEx.None) { string commentPart = TryToRemoveOldPrefix(originalComment, out string originalOwner); string ownerShort = !string.IsNullOrEmpty(originalOwner) ? ShortenOwnerName(originalOwner) : ShortenOwnerName(owner); string optionsPrefix = GetOptionsString(mergeOptions); if (!string.IsNullOrEmpty(optionsPrefix)) { optionsPrefix = optionsPrefix + ", "; } var retval = string.Format(optionsPrefix + "{0} {1} > {2}, {3}: {4}", sourceBranch, changesetId, targetBranch, ownerShort, commentPart); return(retval); }
/// <summary> /// Converts MergeOptionsEx to string. Returns an empty string if options is None. /// </summary> /// <remarks>Does some replace work to improve readability, e.g. AlwaysAcceptMine -> Discard.</remarks> private static string GetOptionsString(MergeOptionsEx options) { string retval; if (options == MergeOptionsEx.None) { retval = string.Empty; } else { retval = options.ToString(); if (options.HasFlag(MergeOptionsEx.ForceMerge)) { retval = retval.Replace("ForceMerge", "Force"); } else if (options.HasFlag(MergeOptionsEx.AlwaysAcceptMine)) { retval = retval.Replace("AlwaysAcceptMine", "Discard"); } } return(retval); }
/// <summary> /// Merges the changesets one by one. Each of the changesets is merged through all branches, ie. from first to last. /// </summary> /// <returns>A tuple, where the boolean signals success, and the string contains and error message in case of failure.</returns> public static Tuple <bool, string> MergeAndCommitOneByOne(MyTfsConnection tfsConnection, Changeset[] changesets, IList <DirectoryInfo> branches, IProgress <ProgressReportArgs> reporter, IProgress <FinishedItemReport> finishedItem, CancellationToken cancelToken, FileInfo tfExecutable, IPopupService popupService, MergeOptionsEx mergeOptions = MergeOptionsEx.None, bool doCheckin = true, bool associateWorkItems = true) { if (!changesets.Any() || branches.Count <= 1) { return(Tuple.Create(false, "No changesets, or not >= 2 branches.")); } if (doCheckin == false) { Debug.Assert(branches.Count == 2, "Currently not supported to have more than two branches in the chain when not checkin in after each merge."); } // For the whole length of the list of changeset in the source branch. foreach (var csOriginal in changesets) { cancelToken.ThrowIfCancellationRequested(); // For the whole depth of the list of branch merge chain. var csCurrent = tfsConnection.GetChangeset(csOriginal.ChangesetId); int newCheckinId = 0; for (int ii = 0; ii < branches.Count - 1; ii++) { cancelToken.ThrowIfCancellationRequested(); if (csCurrent != null) { var sourceBranch = branches[ii]; var targetBranch = branches[ii + 1]; tfsConnection.SetLocalPath(BranchType.Source, sourceBranch.FullName); tfsConnection.SetLocalPath(BranchType.Target, targetBranch.FullName); var id = csCurrent.ChangesetId; string checkinComment = CommentBuilder.GetComment(csOriginal.Comment, id, csOriginal.OwnerDisplayName, sourceBranch.Name, targetBranch.Name, mergeOptions); reporter?.Report(new ProgressReportArgs(0, null, "Merging: " + checkinComment)); try { CheckInitialConflicts(tfsConnection, targetBranch, checkinComment); IList <Conflict> conflicts = tfsConnection.Merge(id, id, mergeOptions); cancelToken.ThrowIfCancellationRequested(); if (conflicts.Count > 0 && !conflicts.All(c => c.AutoResolved)) { //ResolveConflictsWithAPICall(conflicts, tfsConnection, checkinComment); var conflictRetval = ConflictResolver.ResolveConflictsWithExternalExecutable( tfExecutable, tfsConnection, targetBranch.FullName, popupService, checkinComment); if (conflictRetval.Item1 == false) { throw new MyTfsConflictException(conflictRetval.Item2.Count() + " unresolved conflicts."); } cancelToken.ThrowIfCancellationRequested(); } if (doCheckin) { reporter?.Report(new ProgressReportArgs(1, null, "Checkin: " + checkinComment)); var workItems = associateWorkItems ? csCurrent.WorkItems : new WorkItem[0]; workItems = FilterWorkItemsToUpdate(workItems); newCheckinId = tfsConnection.Checkin(checkinComment, workItems); } reporter?.Report(new ProgressReportArgs(1)); finishedItem?.Report(new FinishedItemReport() { SourceChangesetId = id, CommitChangesetId = newCheckinId, CommitComment = checkinComment, SourceBranchIndex = ii }); } catch (MyTfsConnectionException ex) { reporter?.Report(new ProgressReportArgs(0, "Error", "Error merging changeset: " + id.ToString() + "\n\n" + ex.ToString())); return(Tuple.Create(false, ex.ToString())); } } else { var errorMsg = "Error getting changeset information for id " + csOriginal.ChangesetId + ". Operation aborted."; reporter?.Report(new ProgressReportArgs(0, "Error", errorMsg)); return(Tuple.Create(false, errorMsg)); } if (newCheckinId > 0) { csCurrent = tfsConnection.GetChangeset(newCheckinId); } } } return(Tuple.Create(true, "Successfully merged.")); }
public GetStatus Merge(string sourcePath, string targetPath, VersionSpec versionFrom, VersionSpec versionTo, LockLevel lockLevel, RecursionType recursion, MergeOptionsEx mergeOptions) { return(Workspace.Merge(sourcePath, targetPath, versionFrom, versionTo, lockLevel, recursion, mergeOptions)); }
/// <summary> /// Build a comment for checkin for a range of change sets. /// </summary> public static string GetCombinedMergeCheckinComment(string sourceBranch, string targetBranch, ICollection <Tuple <int, string> > idAndOwnerOfChanges, MergeOptionsEx mergeOptions) { int firstChangeset = idAndOwnerOfChanges.Min(ch => ch.Item1); int lastChangeset = idAndOwnerOfChanges.Max(ch => ch.Item1); var owners = idAndOwnerOfChanges.Select(cs => cs.Item2).Distinct().ToArray(); string ownerStr = owners.Length == 1 ? ShortenOwnerName(owners.First()) : $"{owners.Length} authors"; string optionsPrefix = GetOptionsString(mergeOptions); if (!string.IsNullOrEmpty(optionsPrefix)) { optionsPrefix += ", "; } string branchStr = $"{sourceBranch} {firstChangeset}-{lastChangeset} > {targetBranch}"; string comment = optionsPrefix + branchStr + ", " + ownerStr + ": "; if (mergeOptions.HasFlag(MergeOptionsEx.AlwaysAcceptMine)) { var popups = Caliburn.Micro.IoC.Get <IPopupService>(); var answer = popups.AskYesNoQuestion("Is this a \"Cleaning history\" case?"); if (answer == System.Windows.MessageBoxResult.Yes) { comment = $"Cleaning merge history ({comment})"; } } return(comment); }
/// <summary> /// Does the main merge operation for selected items and checks-in every changeset. /// </summary> /// <param name="mfrm">The UserControl.</param> /// <param name="lvcoll">The Collection of ListViewItems.</param> /// <param name="trg">The target path.</param> static internal void DoMerge(UserControl mfrm, IEnumerable <ListViewItem> lvcoll, string trg) { Workspace wrkspc = Utilities.vcext.Explorer.Workspace; if (!ServerItemExists(trg)) { MessageBox.Show("Target server path is cloacked or doesn't exist.", Utilities.AppTitle); return; } if (AutomaticCheckin && wrkspc.GetPendingChanges().Length > 0) { MessageBox.Show("Please resolve all pending changes before going on.", Utilities.AppTitle); return; } //object prgmerge = CreateProgressMerge(vcsrv); //ShowProgressMerge(prgmerge, mfrm); int idx = 0; bool bcanceled = false; int icanceled; var dlg = Utilities.CreateThreadedWaitDialog("Merging changesets", "Stating changesets merge...", "status", 100); ErrorHandler.ThrowOnFailure(dlg.UpdateProgress("Merging changesets", "Stating changesets merge...", "status", idx++, 100, false, out bcanceled)); if (bcanceled) { return; } foreach (var lvItem in lvcoll) { string MergeTypeText = lvItem.SubItems[MergeTypeIndex].Text; if (MergeTypeText == "none") { continue; } Changeset ch = Utilities.vcsrv.GetChangeset((lvItem.Tag as ListViewItemTag).ChangesetID); Utilities.OutputCommandString("Preparing for merge changeset: " + ch.ChangesetId); if (LinkWorkItems) { foreach (var workItm in ch.WorkItems) { Utilities.OutputCommandString("Associated WorkItem: " + workItm.Id); } } ErrorHandler.ThrowOnFailure(dlg.UpdateProgress("Merging changesets", "Merging changeset: " + ch.ChangesetId, "status", idx++, 100, false, out bcanceled)); if (bcanceled) { return; } var chverspc = new ChangesetVersionSpec(ch.ChangesetId); Utilities.OutputCommandString("Changeset Version: " + chverspc.DisplayString); MergeOptionsEx mergeType = (MergeTypeText == "baseless" ? MergeOptionsEx.Baseless : MergeTypeText == "force" ? MergeOptionsEx.ForceMerge : MergeOptionsEx.None); mergeType |= mergeOptions; GetStatus sts = wrkspc.Merge((lvItem.Tag as ListViewItemTag).sourcePath, trg, chverspc, chverspc, LockLevel.Unchanged, RecursionType.Full, mergeType); Utilities.OutputCommandString("Merge summary: MergeOptionsEx=" + mergeType + ", NoActionNeeded=" + sts.NoActionNeeded + ", NumFailures=" + sts.NumFailures + ", NumOperations=" + sts.NumOperations + ", NumUpdated=" + sts.NumUpdated + ", NumWarnings=" + sts.NumWarnings); if (AutomaticCheckin) { while (wrkspc.QueryConflicts(ch.Changes.Select(x => x.Item.ServerItem).ToArray(), true).Length > 0) { //foreach (var conflict in conflicts) wrkspc.ResolveConflict(conflict); DialogResult res = MessageBox.Show("Merge conflicts where found. Resolve them or click Cancel to break merge operaton.", "Merge conflicts", MessageBoxButtons.OKCancel); if (res == DialogResult.Cancel) { ErrorHandler.ThrowOnFailure(dlg.EndWaitDialog(out icanceled)); return; } if (mfrm.InvokeRequired) { var asynchres = mfrm.BeginInvoke(new MethodInvoker(Utilities.ShowResolveConflictsDlg)); mfrm.EndInvoke(asynchres); } else { Utilities.ShowResolveConflictsDlg(); } } var pendingCh = wrkspc.GetPendingChanges();//ch.Changes.Select(x => new ItemSpec(x.Item.ServerItem, RecursionType.Full, x.Item.DeletionId)).ToArray(), true).ToArray(); if (pendingCh.Length == 0) { Utilities.OutputCommandString("No pending changes found."); continue; } var wrkitmch = (LinkWorkItems ? ch.WorkItems.Select(x => new WorkItemCheckinInfo(x, WorkItemCheckinAction.Associate)).ToArray() : null); foreach (var pendChItem in pendingCh) { Utilities.OutputCommandString("Found pending change " + pendChItem.ChangeTypeName + ": " + pendChItem.SourceServerItem); } int newchid = wrkspc.CheckIn(pendingCh, ch.Committer, ch.Comment, ch.CheckinNote, wrkitmch, null, CheckinOptions.SuppressEvent); Utilities.OutputCommandString("Created new changeset: " + newchid); } if (idx == 100) { idx = 0; } } ErrorHandler.ThrowOnFailure(dlg.EndWaitDialog(out icanceled)); //CloseProgressMerge(prgmerge); }