/// <summary> /// The only entry point in the class. This method (plus its private methods) does the work of the class. /// </summary> /// <param name="pathname">Pathname of the split up file that contains <paramref name="element"/>.</param> /// <param name="sortedData">All of the data being collected that will end up as CmObject elments in the rebuilt fwdata file. These are sorted by guid order.</param> /// <param name="element">The xml element to check for a duplicate guid.</param> /// <param name="isOwnSeqNode">Out paramenter that is used by caller.</param> /// <param name="className">The class of <paramref name="element"/>.</param> /// <returns></returns> internal static string CheckForDuplicateGuid(string pathname, SortedDictionary<string, XElement> sortedData, XElement element, out bool isOwnSeqNode, out string className) { var mdc = MetadataCache.MdCache; FdoClassInfo classInfo; isOwnSeqNode = GetClassInfoFromElement(mdc, element, out classInfo, out className); var elementGuid = element.Attribute(SharedConstants.GuidStr).Value.ToLowerInvariant(); if (!sortedData.ContainsKey(elementGuid)) return elementGuid; // Does LT-12524 "Handle merge in case of conflicting move object to different destination". // This need will manifest itself in the guid already being in 'sortedData' and an exception being thrown. // At this point element has not been flattened, so stuff it owns will still be in it. // That is good, if we go with JohnT's idea of using a new guid for guids that are already in 'sortedData'. // By changing it before flattening, then the owned stuff will get the new one for their ownerguid attrs. // The owned stuff will also be dup, so the idea is to also change their guids right now. [ChangeGuids is recursive down the owning tree.] // Just be sure to change 'elementGuid' to the new one. :-) // The first item added to sortedData has been flattened by this point, but not any following ones. var oldGuid = elementGuid; elementGuid = ChangeGuids(mdc, classInfo, element); using (var listener = new ChorusNotesMergeEventListener(ChorusNotesMergeEventListener.GetChorusNotesFilePath(pathname))) { // Don't try to use something like this: // var contextGenerator = new FieldWorkObjectContextGenerator(); // contextGenerator.GenerateContextDescriptor(element.ToString(), pathname) // it will fail for elements in an owning sequence because in the unflattened form // the object representing a sequence item has element name <ownseq> which won't generate a useful label. var context = FieldWorksMergeServices.GenerateContextDescriptor(pathname, elementGuid, className); listener.EnteringContext(context); // Adding the conflict to the listener, will result in the ChorusNotes file being updated (created if need be.) var conflict = new IncompatibleMoveConflict(className, CmObjectFlatteningService.GetXmlNode(element)) { Situation = new NullMergeSituation() }; // The order of the next three lines is critical. Each prepares state that the following lines use. listener.RecordContextInConflict(conflict); conflict.HtmlDetails = MakeHtmlForIncompatibleMove(conflict, oldGuid, elementGuid, element); listener.ConflictOccurred(conflict); File.WriteAllText(pathname + "." + SharedConstants.dupid, ""); } return elementGuid; }
public void FileOutput_WithContent_UsesCanonicalXmlSettings() { string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + "<notes\r\n" + "\tversion=\"0\">\r\n" + "\t<annotation>Dummy</annotation>\r\n" + "</notes>"; using (var logFile = TempFile.CreateAndGetPathButDontMakeTheFile()) { using (var log = new ChorusNotesMergeEventListener(logFile.Path)) { log.ConflictOccurred(new DummyConflict()); } string result = File.ReadAllText(logFile.Path); Assert.AreEqual(expected, result); } }
public void FileDidNotExist_CreatesCorrectFile() { using (TempFile logFile = TempFile.CreateAndGetPathButDontMakeTheFile()) { using (ChorusNotesMergeEventListener log = new ChorusNotesMergeEventListener(logFile.Path)) { log.ConflictOccurred(new DummyConflict()); log.ConflictOccurred(new DummyConflict()); } XmlDocument doc = new XmlDocument(); doc.Load(logFile.Path); Assert.AreEqual(2, doc.SelectNodes("notes/annotation").Count); } }
/// <summary> /// This method will detect changed vs deleted file conflicts in the output coming from mercurial. /// It produces a conflict for that situation and tells mercurial to keep the changed file. /// <note>The default response for mercurial is to keep the changed, but on some user systems (Windows 8?) /// mercurial will pause waiting for input instead of using the default answer.</note> /// </summary> private bool HandleChangedVsDeletedFiles(string line, StreamWriter standardInput) { // The two situations we are dealing with are: // // local changed [filename] which remote deleted // use (c)hanged version or (d)elete? // and // remote changed [filename] which local deleted // use (c)hanged version or leave (d)eleted? string changedVsDeletedFile = null; var match = Regex.Match(line, @"local changed (.*) which remote deleted"); if(match.Captures.Count > 0) { changedVsDeletedFile = match.Groups[1].Value; } else { match = Regex.Match(line, @"remote changed (.*) which local deleted"); if(match.Captures.Count > 0) { changedVsDeletedFile = match.Groups[1].Value; } } if(changedVsDeletedFile != null) { var conflictPath = Path.Combine(_rootDirectory, changedVsDeletedFile); var conflictReport = new FileChangedVsFileDeletedConflict(conflictPath); using(var chorusNoteCreator = new ChorusNotesMergeEventListener(ChorusNotesMergeEventListener.GetChorusNotesFilePath(conflictPath))) { chorusNoteCreator.ConflictOccurred(conflictReport); } return true; } // Send hg the response it might be waiting for: if(line.Contains(@"(c)hanged") && line.Contains(@"(d)elete")) { standardInput.WriteLine('c'); return true; } // This input line was not related to a Changed vs Deleted File situation return false; }