/// <summary> /// Creates the object instance and initializes the properties to the specified values. /// </summary> /// <param name="target">The batched item the error occurred on</param> /// <param name="exception">The exception, if any, associated with this event (may be null)</param> public BatchedItemEventArgs(BatchedItem target, Exception exception) : this(target, exception, null) { if (exception != null) { m_message = exception.Message; } }
static bool isAdditiveAction(BatchedItem item) { if ((item.Action == WellKnownChangeActionId.Add) || (item.Action == WellKnownChangeActionId.Branch) || (item.Action == WellKnownChangeActionId.Rename) || (item.Action == WellKnownChangeActionId.Undelete)) { return(true); } return(false); }
public void Add(BatchedItem change) { if (change == null) { throw new ArgumentNullException("change"); } lock (m_locker) { if (!acceptingNewChanges) { Debug.Fail("ChangeOprimizer is not accepting new changes"); } if (change.Action == WellKnownChangeActionId.Rename) { bool addToRenameList = true; foreach (BatchedItem existingRenameChange in m_unresolvedRenames) { if (TfsUtil.isChildItemOf(change, existingRenameChange)) { addToRenameList = false; break; } } if (addToRenameList) { m_unresolvedRenames.Add(change); } else { m_implicitRenames.Add(change.Target); } if (!m_renamePairs.ContainsKey(change.Target)) { m_renamePairs.Add(change.Target, change.Source); } } else if ((change.Action == WellKnownChangeActionId.Branch) || (change.Action == WellKnownChangeActionId.Add) || (change.Action == WellKnownChangeActionId.Undelete)) { m_unresolvedAdditiveActions.Add(change); } else { // Deletes, Edits or Encodings m_unresolvedChanges.Add(change); } } }
public void Add(BatchedItem change) { if (change == null) { throw new ArgumentNullException("change"); } lock (m_locker) { if (!acceptingNewChanges) { Debug.Fail("ChangeOprimizer is not accepting new changes"); } // IMPORTANT NOTE: Currently every BatchedItem passed to this method is added to one of the three lists: // m_unresolvedRenames, m_unresolvedAdditiveActions, or m_unresolvedChanges // The method PreProcessMerges depends on that fact, so if that changes here, PreProcessMerges should be // checked for changes needed as well if (change.Action == WellKnownChangeActionId.Rename) { m_unresolvedRenames.Add(change); if (!m_renamePairs.ContainsKey(change.Target)) { m_renamePairs.Add(change.Target, change); } } else if ((change.Action == WellKnownChangeActionId.Branch) || (change.Action == WellKnownChangeActionId.Add) || (change.Action == WellKnownChangeActionId.Undelete)) { m_unresolvedAdditiveActions.Add(change); } else { //Merges, Deletes, Edits or Encodings m_unresolvedChanges.Add(change); } } }
/// <summary> /// Compare 'Action', 'Source', and 'Target' of the current item with the provided item. /// The current item is the child of the provided item if /// 1. Actions are the same /// 2. Parent items's Target is the sub-item of the current item's Target. /// 3. For rename/branch/merge, Parent item's Source is the sub-item of the item's Source and postfix sould be the same. /// </summary> /// <param name="parentItem">Item to be compared</param> /// <returns>True if the provided item is the parent of the current item.</returns> public static bool isChildItemOf(BatchedItem item, BatchedItem parentItem) { if ((parentItem == null) || (parentItem.Target == null) || (item == null) || (item.Target == null)) { return(false); } if ((item.Action == parentItem.Action) && (VersionControlPath.IsSubItem(item.Target, parentItem.Target))) { if ((item.Action == WellKnownChangeActionId.Rename) && (VersionControlPath.IsSubItem(item.Source, parentItem.Source))) { //Construct a canonlized serverpath instead of a truncated path to avoid assertion failure in TFC debug build. string sourcePostFix = item.Source.Substring(parentItem.Source.Length); string constructedTarget = ConcatWithoutDoubleSlashes(parentItem.Target, sourcePostFix); if (VersionControlPath.EqualsCaseSensitive(item.Target, constructedTarget)) { return(true); } } } return(false); }
private int breakCycle(BatchedItem item) { /* * rename A B * rename B A * * becomes * * rename A <intermediate> * rename B A * rename <intermediate> B * * the conflict is from A to B */ if (item.Action != WellKnownChangeActionId.Rename) { throw new UnresolvableConflictException("Don't know how to break non-rename cycles"); } if (item.Resolved) { return(item.Priority); } string intermediateNameAsTarget; string intermediateNameAsSource; getIntermediateNames(item.Source, item.Target, out intermediateNameAsTarget, out intermediateNameAsSource); BatchedItem intermediate1 = new BatchedItem( adjustSourceForParentRenameIfNeeded(item.Source, item.Target, false), intermediateNameAsTarget, WellKnownChangeActionId.Rename, item.ConflictItem.Priority); item.ConflictItem.Priority++; BatchedItem intermediate2 = new BatchedItem( intermediateNameAsSource, item.Target, WellKnownChangeActionId.Rename, item.ConflictItem.Priority + 1); m_resolvedChanges.Add(intermediate1); m_resolvedChanges.Add(intermediate2); /* Uncomment for debugging * foreach (BatchedItem cycleItem in new BatchedItem[] { intermediate1, item.ConflictItem, intermediate2 }) * { * TraceManager.TraceInformation("ChangeOptimizer.breakCycle() result: Source: {0}, Target: {1}, Priority: {2}", * cycleItem.Source, cycleItem.Target, cycleItem.Priority); * } */ // remove the old change item.Resolved = true; m_unresolvedChanges.Remove(item); if (item.ConflictItem.ConflictItem != null && item.ConflictItem.ConflictItem.ID == item.ID) { item.ConflictItem.Resolved = true; m_unresolvedChanges.Remove(item.ConflictItem); m_resolvedChanges.Add(item.ConflictItem); } else { intermediate2.Priority = breakCycle(item.ConflictItem); } return(item.ConflictItem.Priority); }
public ReadOnlyCollection <BatchedItem> Resolve() { Debug.Assert(acceptingNewChanges, "this should be true when resolve is called"); lock (m_locker) { acceptingNewChanges = false; processRecursiveChanges(); detectConflicts(); // walk backwards so that RemoveAt can work // without the removals affecting indexing for (int i = m_unresolvedChanges.Count - 1; i >= 0; i--) { BatchedItem item = m_unresolvedChanges[i]; if (item.ConflictItem == null) { item.Resolved = true; m_resolvedChanges.Add(item); m_unresolvedChanges.RemoveAt(i); } } if (m_unresolvedChanges.Count != 0) { int currentCount = m_unresolvedChanges.Count; int prevCount = -1; // detect and remove priority issues while (currentCount > 0 && prevCount != currentCount) { // resolve cases that can be solved just with priority adjustment for (int i = m_unresolvedChanges.Count - 1; i >= 0; i--) { BatchedItem item = m_unresolvedChanges[i]; // if the conflict chain ends with the next item // or the next item in the chain is already resolved // then resolve by bumping our priority if (item.ConflictItem.ConflictItem == null || item.ConflictItem.Resolved) { if ((item.Action == WellKnownChangeActionId.Add) && (item.ConflictItem.Action == WellKnownChangeActionId.Rename) && VersionControlPath.IsSubItem(item.ConflictItem.Target, item.Target)) { // For the case Add Folder1 and rename Folder1 to Folder1\Folder1, change the action sequence to // rename Folder1 to intermediate, Add Folder1, rename intermediate to Folder1\Folder1 string intermediateNameAsSource; string intermediateNameAsTarget; getIntermediateNames(item.ConflictItem.Source, item.ConflictItem.Target, out intermediateNameAsTarget, out intermediateNameAsSource); BatchedItem intermediateToOriginal = new BatchedItem( intermediateNameAsSource, item.ConflictItem.Target, WellKnownChangeActionId.Rename, item.ConflictItem.Priority + 2); m_resolvedChanges.Add(intermediateToOriginal); item.ConflictItem.Target = intermediateNameAsTarget; } item.Resolved = true; item.Priority = item.ConflictItem.Priority + 1; m_resolvedChanges.Add(item); m_unresolvedChanges.RemoveAt(i); } } // Identify cycles for (int i = 0; i < m_renameCycles.Count; i++) { List <BatchedItem> renameCycle = m_renameCycles[i]; if (renameCycle[0].Resolved) { continue; } // Set the priority on the later cycles so that they are pended after the earlier cycles if (i > 0) { List <BatchedItem> previousCycle = m_renameCycles[i - 1]; int newCyclePriority = previousCycle[0].Priority + previousCycle.Count; foreach (BatchedItem renameCycleItem in renameCycle) { renameCycleItem.Priority = newCyclePriority; } } breakCycle(renameCycle[0]); } prevCount = currentCount; currentCount = m_unresolvedChanges.Count; } } Debug.Assert(m_unresolvedChanges.Count == 0, "Unable to resolve pending changes"); int highestPriority = 0; foreach (BatchedItem item in m_resolvedChanges) { if (item.Priority > highestPriority) { highestPriority = item.Priority; } } foreach (BatchedItem item in m_resolvedChanges) { // Delay all Edits and Deletes to be processed at last. if ((item.Action == WellKnownChangeActionId.Edit) || (item.Action == WellKnownChangeActionId.Delete)) { item.Priority = item.Priority + highestPriority + 1; } } m_resolvedChanges.Sort( delegate(BatchedItem lhs, BatchedItem rhs) { return(lhs.Priority.CompareTo(rhs.Priority)); } ); return(new ReadOnlyCollection <BatchedItem>(m_resolvedChanges)); } }
private void detectConflicts() { // Sort m_unresolvedRenames by path length with shorter paths first m_unresolvedRenames.Sort(delegate(BatchedItem lhs, BatchedItem rhs) { return(lhs.Source.Length.CompareTo(rhs.Source.Length)); }); // Find implicit renames, add them to m_implictRenames and m_implicitRenamesWithParentItems // If they are not also explict renames, remove them from m_unresolvedRenames for (int childIndex = m_unresolvedRenames.Count - 1; childIndex >= 0; childIndex--) { BatchedItem change = m_unresolvedRenames[childIndex]; bool removeFromUnresolvedRenames = false; for (int parentIndex = 0; parentIndex < childIndex; parentIndex++) { BatchedItem shorterRename = m_unresolvedRenames[parentIndex]; bool childIsAlsoExplicitRename; if (TfsUtil.isChildItemOfRename(change, shorterRename, out childIsAlsoExplicitRename)) { m_implicitRenames.Add(change.Target); m_implicitRenamesWithParentItems.Add(change.Target, shorterRename); // There may be both an implicit rename of the item based on a parent rename and an explicit rename of this item removeFromUnresolvedRenames = !childIsAlsoExplicitRename; break; } } if (removeFromUnresolvedRenames) { m_unresolvedRenames.RemoveAt(childIndex); } } m_unresolvedAdditiveActions.Sort(delegate(BatchedItem lhs, BatchedItem rhs) { return(lhs.Target.Length.CompareTo(rhs.Target.Length)); }); for (int index = m_unresolvedAdditiveActions.Count - 1; index >= 0; index--) { foreach (BatchedItem rename in m_unresolvedRenames) { if (VersionControlPath.IsSubItem(m_unresolvedAdditiveActions[index].Target, rename.Source)) { if ((m_unresolvedAdditiveActions[index].Action == WellKnownChangeActionId.Add) && (VersionControlPath.Equals(rename.Source, m_unresolvedAdditiveActions[index].Target)) && (VersionControlPath.IsSubItem(rename.Target, rename.Source)) && (!VersionControlPath.Equals(rename.Target, rename.Source))) { // This Add is created by a Rename to below itself. Skip it. Example: // Rename $/foo/bar to $/foo/bar/bar2 // We will get two change actions, 1) Rename $/foo/bar to $/foo/bar/bar2 // 2) Add $/foo/bar. // This is to skip the Add action as it will be created implicitly by the Rename. if (!m_implicitAdds.Contains(m_unresolvedAdditiveActions[index].Target)) { m_implicitAdds.Add(m_unresolvedAdditiveActions[index].Target); } m_unresolvedAdditiveActions.RemoveAt(index); break; } // The additive item should be scheduled after the Rename. // Unless the rename is a case only rename which does not change the namespace slot. if (!VersionControlPath.Equals(rename.Source, rename.Target)) { m_unresolvedAdditiveActions[index].ConflictItem = rename; } break; } } } for (int outerIndex = 0; outerIndex < m_unresolvedRenames.Count; outerIndex++) { BatchedItem processedChange = m_unresolvedRenames[outerIndex]; if (processedChange.ConflictItem != null) { continue; } // Find a parent item that was renamed for (int innerIndex = 0; innerIndex < m_unresolvedRenames.Count; innerIndex++) { if (outerIndex == innerIndex) { continue; } BatchedItem change = m_unresolvedRenames[innerIndex]; if (VersionControlPath.IsSubItem(processedChange.Target, change.Source)) { processedChange.ConflictItem = change; break; } } // Check for a rename cycles and chains that starts with the processedChange // To do this: Follow the chain of renames until it ends, it cycles back to the processedChange, or // the number of times through the loop reaches the size of m_renamePairs // Note that the renameCycle list is only added to m_renameCycles (a List of Lists) if a cycle is // found; otherwise it is was just a potential renameCycle that is tossed List <BatchedItem> renameCycle = new List <BatchedItem>(); BatchedItem next; if (!m_renamePairs.TryGetValue(processedChange.Target, out next)) { Debug.Fail("Target of item in m_unresolvedRenames not found in m_renamePairs"); } for (int i = 0; i < m_renamePairs.Count && next != null; i++) { string adjustedSource = null; try { adjustedSource = adjustSourceForParentRenameIfNeeded(next.Source, next.Target, true); } catch { // Can occur if adjusted name is longer than max; in this case treat like not found in m_renamePairs by leaving adjustedSource null } BatchedItem renameSourceItem; // m_renamePairs has the rename target as the key, so the following is checking if the // source of the "next" rename item is the target of another rename if (string.IsNullOrEmpty(adjustedSource) || !m_renamePairs.TryGetValue(adjustedSource, out renameSourceItem)) { renameCycle = null; break; } // Setting the ConflictItem is needed whether this is a cycle or just a chain renameSourceItem.ConflictItem = next; // Add to the renameCycle list though this instance of the list will be discarded unless a cycle is found below renameCycle.Add(next); next = renameSourceItem; if (next.ID == processedChange.ID) { m_renameCycles.Add(renameCycle); break; } } foreach (BatchedItem additiveAction in m_unresolvedAdditiveActions) { if (additiveAction.Action == WellKnownChangeActionId.Undelete) { if (VersionControlPath.IsSubItem(processedChange.Target, additiveAction.Source)) { Debug.Assert(processedChange.ConflictItem == null); processedChange.ConflictItem = additiveAction; continue; } } else { // Add fld2, rename fld1/1.txt to fld2/1.txt // We need to pend the Add first. if (VersionControlPath.IsSubItem(processedChange.Target, additiveAction.Target)) { if ((processedChange.ConflictItem == null) || VersionControlPath.IsSubItem(additiveAction.Target, processedChange.ConflictItem.Target)) { processedChange.ConflictItem = additiveAction; } else { Debug.Assert(VersionControlPath.IsSubItem(processedChange.ConflictItem.Target, additiveAction.Target), string.Format("Item {0} conflicted with two items: {1} and {2}", processedChange.Target, processedChange.ConflictItem.Target, additiveAction.Target)); } continue; } // Add fld1/file1.txt; rename fld -> fld1; rename go first if (VersionControlPath.IsSubItem(additiveAction.Target, processedChange.Target)) { if ((additiveAction.ConflictItem == null) || (VersionControlPath.IsSubItem(processedChange.Target, additiveAction.ConflictItem.Target))) { additiveAction.ConflictItem = processedChange; } else { Debug.Assert(VersionControlPath.IsSubItem(additiveAction.ConflictItem.Target, processedChange.Target), string.Format("Item {0} conflicted with two items: {1} and {2}", additiveAction.Target, additiveAction.ConflictItem.Target, processedChange.Target)); } continue; } } } } m_unresolvedChanges.AddRange(m_unresolvedAdditiveActions); m_unresolvedChanges.AddRange(m_unresolvedRenames); }
private int breakCycle(BatchedItem item) { /* * rename A B * rename B A * * becomes * * rename A <intermediate> * rename B A * rename <intermediate> B * * the conflict is from A to B */ if (item.Action != WellKnownChangeActionId.Rename) { throw new UnresolvableConflictException("Don't know how to break non-rename cycles"); } if (item.Resolved) { return(item.Priority); } string intermediate = getIntermediateName(item.Target); BatchedItem intermediate1 = new BatchedItem( item.Source, intermediate, WellKnownChangeActionId.Rename, item.ConflictItem.Priority); item.ConflictItem.Priority++; BatchedItem intermediate2 = new BatchedItem( intermediate, item.Target, WellKnownChangeActionId.Rename, item.ConflictItem.Priority + 1); m_resolvedChanges.Add(intermediate1); m_resolvedChanges.Add(intermediate2); // remove the old change item.Resolved = true; m_unresolvedChanges.Remove(item); if (item.ConflictItem.ConflictItem != null && item.ConflictItem.ConflictItem.ID == item.ID) { item.ConflictItem.Resolved = true; m_unresolvedChanges.Remove(item.ConflictItem); m_resolvedChanges.Add(item.ConflictItem); } else { intermediate2.Priority = breakCycle(item.ConflictItem); } return(item.ConflictItem.Priority); }
private void detectConflicts() { m_unresolvedRenames.Sort(delegate(BatchedItem lhs, BatchedItem rhs) { return(lhs.Source.Length.CompareTo(rhs.Source.Length)); }); m_unresolvedAdditiveActions.Sort(delegate(BatchedItem lhs, BatchedItem rhs) { return(lhs.Target.Length.CompareTo(rhs.Target.Length)); }); for (int index = m_unresolvedAdditiveActions.Count - 1; index >= 0; index--) { foreach (BatchedItem rename in m_unresolvedRenames) { if (VersionControlPath.IsSubItem(m_unresolvedAdditiveActions[index].Target, rename.Source)) { if ((m_unresolvedAdditiveActions[index].Action == WellKnownChangeActionId.Add) && (VersionControlPath.Equals(rename.Source, m_unresolvedAdditiveActions[index].Target)) && (VersionControlPath.IsSubItem(rename.Target, rename.Source)) && (!VersionControlPath.Equals(rename.Target, rename.Source))) { // This Add is created by a Rename to below itself. Skip it. Example: // Rename $/foo/bar to $/foo/bar/bar2 // We will get two change actions, 1) Rename $/foo/bar to $/foo/bar/bar2 // 2) Add $/foo/bar. // This is to skip the Add action as it will be created implicitly by the Rename. if (!m_implicitAdds.Contains(m_unresolvedAdditiveActions[index].Target)) { m_implicitAdds.Add(m_unresolvedAdditiveActions[index].Target); } m_unresolvedAdditiveActions.RemoveAt(index); break; } // The additive item should be scheduled after the Rename. // Unless the rename is a case only rename which does not change the namespace slot. if (!VersionControlPath.Equals(rename.Source, rename.Target)) { m_unresolvedAdditiveActions[index].ConflictItem = rename; } break; } } } for (int outerIndex = 0; outerIndex < m_unresolvedRenames.Count; outerIndex++) { BatchedItem processedChange = m_unresolvedRenames[outerIndex]; for (int innerIndex = 0; innerIndex < m_unresolvedRenames.Count; innerIndex++) { if (outerIndex == innerIndex) { continue; } BatchedItem change = m_unresolvedRenames[innerIndex]; if (VersionControlPath.IsSubItem(processedChange.Target, change.Source)) { processedChange.ConflictItem = change; break; } } foreach (BatchedItem additiveAction in m_unresolvedAdditiveActions) { if (additiveAction.Action == WellKnownChangeActionId.Undelete) { if (VersionControlPath.IsSubItem(processedChange.Target, additiveAction.Source)) { Debug.Assert(processedChange.ConflictItem == null); processedChange.ConflictItem = additiveAction; continue; } } else { // Add fld2, rename fld1/1.txt to fld2/1.txt // We need to pend the Add first. if (VersionControlPath.IsSubItem(processedChange.Target, additiveAction.Target)) { if ((processedChange.ConflictItem == null) || VersionControlPath.IsSubItem(additiveAction.Target, processedChange.ConflictItem.Target)) { processedChange.ConflictItem = additiveAction; } else { Debug.Assert(VersionControlPath.IsSubItem(processedChange.ConflictItem.Target, additiveAction.Target), string.Format("Item {0} conflicted with two items: {1} and {2}", processedChange.Target, processedChange.ConflictItem.Target, additiveAction.Target)); } continue; } // Add fld1/file1.txt; rename fld -> fld1; rename go first if (VersionControlPath.IsSubItem(additiveAction.Target, processedChange.Target)) { if ((additiveAction.ConflictItem == null) || (VersionControlPath.IsSubItem(processedChange.Target, additiveAction.ConflictItem.Target))) { additiveAction.ConflictItem = processedChange; } else { Debug.Assert(VersionControlPath.IsSubItem(additiveAction.ConflictItem.Target, processedChange.Target), string.Format("Item {0} conflicted with two items: {1} and {2}", additiveAction.Target, additiveAction.ConflictItem.Target, processedChange.Target)); } continue; } } } } m_unresolvedChanges.AddRange(m_unresolvedAdditiveActions); m_unresolvedChanges.AddRange(m_unresolvedRenames); }
public BatchedMergeErrorEventArgs(BatchedItem targetItem, GetStatus stat, Exception exception) : base(targetItem, exception) { m_stat = stat; }
/// <summary> /// Creates the object instance and initializes the properties to the specified values. /// </summary> /// <param name="target">The batched item the error occurred on</param> /// <param name="exception">The exception, if any, associated with this event (may be null)</param> /// <param name="message">The message, if any, associated with this event (may be null)</param> public BatchedItemEventArgs(BatchedItem target, Exception exception, string message) { m_target = target; m_exception = exception; m_message = message; }