public MergeResult merge(Mapping mapping, boolean simulate, boolean updateAllTypes) { try (ReleasableLock lock = mappingWriteLock.acquire()) { final MergeResult mergeResult = new MergeResult(simulate, updateAllTypes); this.mapping.merge(mapping, mergeResult); if (simulate == false) { addMappers(mergeResult.getNewObjectMappers(), mergeResult.getNewFieldMappers()); refreshSource(); } return mergeResult; } }
protected void checkNewMappersCompatibility( Collection<ObjectMapper> newObjectMappers, Collection<FieldMapper> newFieldMappers, boolean updateAllTypes) { assert mappingLock.isWriteLockedByCurrentThread(); for (ObjectMapper newObjectMapper : newObjectMappers) { ObjectMapper existingObjectMapper = fullPathObjectMappers.get(newObjectMapper.fullPath()); if (existingObjectMapper != null) { MergeResult result = new MergeResult(true, updateAllTypes); existingObjectMapper.merge(newObjectMapper, result); if (result.hasConflicts()) { throw new IllegalArgumentException( "Mapper for [" + newObjectMapper.fullPath() + "] conflicts with existing mapping in other types" + Arrays.toString(result.buildConflicts())); } } } fieldTypes.checkCompatibility(newFieldMappers, updateAllTypes); }
/** * Updates the index after a content merge has happened. If no conflict has occurred this includes * persisting the merged content to the object database. In case of conflicts this method takes * care to write the correct stages to the index. * * @param base * @param ours * @param theirs * @param result * @throws FileNotFoundException * @throws IOException */ private void updateIndex( CanonicalTreeParser base, CanonicalTreeParser ours, CanonicalTreeParser theirs, MergeResult<RawText> result) throws FileNotFoundException, IOException { File mergedFile = !inCore ? writeMergedFile(result) : null; if (result.containsConflicts()) { // A conflict occurred, the file will contain conflict markers // the index will be populated with the three stages and the // workdir (if used) contains the halfway merged content. add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); mergeResults.put(tw.getPathString(), result); return; } // No conflict occurred, the file will contain fully merged content. // The index will be populated with the new merged version. DirCacheEntry dce = new DirCacheEntry(tw.getPathString()); // Set the mode for the new content. Fall back to REGULAR_FILE if // we can't merge modes of OURS and THEIRS. int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1), tw.getRawMode(2)); dce.setFileMode( newMode == FileMode.MISSING.getBits() ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode)); if (mergedFile != null) { long len = mergedFile.length(); dce.setLastModified(mergedFile.lastModified()); dce.setLength((int) len); InputStream is = new FileInputStream(mergedFile); try { dce.setObjectId(getObjectInserter().insert(OBJ_BLOB, len, is)); } finally { is.close(); } } else dce.setObjectId(insertMergeResult(result)); builder.add(dce); }
/** * Processes one path and tries to merge. This method will do all do all trivial (not content) * merges and will also detect if a merge will fail. The merge will fail when one of the following * is true * * <ul> * <li>the index entry does not match the entry in ours. When merging one branch into the * current HEAD, ours will point to HEAD and theirs will point to the other branch. It is * assumed that the index matches the HEAD because it will only not match HEAD if it was * populated before the merge operation. But the merge commit should not accidentally * contain modifications done before the merge. Check the <a href= * "http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html#_3_way_merge" >git * read-tree</a> documentation for further explanations. * <li>A conflict was detected and the working-tree file is dirty. When a conflict is detected * the content-merge algorithm will try to write a merged version into the working-tree. If * the file is dirty we would override unsaved data. * </ul> * * @param base the common base for ours and theirs * @param ours the ours side of the merge. When merging a branch into the HEAD ours will point to * HEAD * @param theirs the theirs side of the merge. When merging a branch into the current HEAD theirs * will point to the branch which is merged into HEAD. * @param index the index entry * @param work the file in the working tree * @param ignoreConflicts see {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, * RevTree, boolean)} * @return <code>false</code> if the merge will fail because the index entry didn't match ours or * the working-dir file was dirty and a conflict occurred * @throws MissingObjectException * @throws IncorrectObjectTypeException * @throws CorruptObjectException * @throws IOException * @since 3.5 */ protected boolean processEntry( CanonicalTreeParser base, CanonicalTreeParser ours, CanonicalTreeParser theirs, DirCacheBuildIterator index, WorkingTreeIterator work, boolean ignoreConflicts) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { enterSubtree = true; final int modeO = tw.getRawMode(T_OURS); final int modeT = tw.getRawMode(T_THEIRS); final int modeB = tw.getRawMode(T_BASE); if (modeO == 0 && modeT == 0 && modeB == 0) // File is either untracked or new, staged but uncommitted return true; if (isIndexDirty()) return false; DirCacheEntry ourDce = null; if (index == null || index.getDirCacheEntry() == null) { // create a fake DCE, but only if ours is valid. ours is kept only // in case it is valid, so a null ourDce is ok in all other cases. if (nonTree(modeO)) { ourDce = new DirCacheEntry(tw.getRawPath()); ourDce.setObjectId(tw.getObjectId(T_OURS)); ourDce.setFileMode(tw.getFileMode(T_OURS)); } } else { ourDce = index.getDirCacheEntry(); } if (nonTree(modeO) && nonTree(modeT) && tw.idEqual(T_OURS, T_THEIRS)) { // OURS and THEIRS have equal content. Check the file mode if (modeO == modeT) { // content and mode of OURS and THEIRS are equal: it doesn't // matter which one we choose. OURS is chosen. Since the index // is clean (the index matches already OURS) we can keep the existing one keep(ourDce); // no checkout needed! return true; } else { // same content but different mode on OURS and THEIRS. // Try to merge the mode and report an error if this is // not possible. int newMode = mergeFileModes(modeB, modeO, modeT); if (newMode != FileMode.MISSING.getBits()) { if (newMode == modeO) // ours version is preferred keep(ourDce); else { // the preferred version THEIRS has a different mode // than ours. Check it out! if (isWorktreeDirty(work, ourDce)) return false; // we know about length and lastMod only after we have written the new content. // This will happen later. Set these values to 0 for know. DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_0, 0, 0); toBeCheckedOut.put(tw.getPathString(), e); } return true; } else { // FileModes are not mergeable. We found a conflict on modes. // For conflicting entries we don't know lastModified and length. add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); unmergedPaths.add(tw.getPathString()); mergeResults.put( tw.getPathString(), new MergeResult<RawText>(Collections.<RawText>emptyList())); } return true; } } if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) { // THEIRS was not changed compared to BASE. All changes must be in // OURS. OURS is chosen. We can keep the existing entry. if (ourDce != null) keep(ourDce); // no checkout needed! return true; } if (modeB == modeO && tw.idEqual(T_BASE, T_OURS)) { // OURS was not changed compared to BASE. All changes must be in // THEIRS. THEIRS is chosen. // Check worktree before checking out THEIRS if (isWorktreeDirty(work, ourDce)) return false; if (nonTree(modeT)) { // we know about length and lastMod only after we have written // the new content. // This will happen later. Set these values to 0 for know. DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_0, 0, 0); if (e != null) toBeCheckedOut.put(tw.getPathString(), e); return true; } else { // we want THEIRS ... but THEIRS contains a folder or the // deletion of the path. Delete what's in the workingtree (the // workingtree is clean) but do not complain if the file is // already deleted locally. This complements the test in // isWorktreeDirty() for the same case. if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) return true; toBeDeleted.add(tw.getPathString()); return true; } } if (tw.isSubtree()) { // file/folder conflicts: here I want to detect only file/folder // conflict between ours and theirs. file/folder conflicts between // base/index/workingTree and something else are not relevant or // detected later if (nonTree(modeO) && !nonTree(modeT)) { if (nonTree(modeB)) add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); unmergedPaths.add(tw.getPathString()); enterSubtree = false; return true; } if (nonTree(modeT) && !nonTree(modeO)) { if (nonTree(modeB)) add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); unmergedPaths.add(tw.getPathString()); enterSubtree = false; return true; } // ours and theirs are both folders or both files (and treewalk // tells us we are in a subtree because of index or working-dir). // If they are both folders no content-merge is required - we can // return here. if (!nonTree(modeO)) return true; // ours and theirs are both files, just fall out of the if block // and do the content merge } if (nonTree(modeO) && nonTree(modeT)) { // Check worktree before modifying files if (isWorktreeDirty(work, ourDce)) return false; // Don't attempt to resolve submodule link conflicts if (isGitLink(modeO) || isGitLink(modeT)) { add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); unmergedPaths.add(tw.getPathString()); return true; } MergeResult<RawText> result = contentMerge(base, ours, theirs); if (ignoreConflicts) result.setContainsConflicts(false); updateIndex(base, ours, theirs, result); if (result.containsConflicts() && !ignoreConflicts) unmergedPaths.add(tw.getPathString()); modifiedFiles.add(tw.getPathString()); } else if (modeO != modeT) { // OURS or THEIRS has been deleted if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw.idEqual(T_BASE, T_THEIRS)))) { add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); DirCacheEntry e = add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); // OURS was deleted checkout THEIRS if (modeO == 0) { // Check worktree before checking out THEIRS if (isWorktreeDirty(work, ourDce)) return false; if (nonTree(modeT)) { if (e != null) toBeCheckedOut.put(tw.getPathString(), e); } } unmergedPaths.add(tw.getPathString()); // generate a MergeResult for the deleted file mergeResults.put(tw.getPathString(), contentMerge(base, ours, theirs)); } } return true; }
// never expose this to the outside world, we need to reparse the doc mapper so we get fresh // instances of field mappers to properly remove existing doc mapper private DocumentMapper merge(DocumentMapper mapper, boolean updateAllTypes) { try (ReleasableLock lock = mappingWriteLock.acquire()) { if (mapper.type().length() == 0) { throw new InvalidTypeNameException("mapping type name is empty"); } if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1) && mapper.type().length() > 255) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] is too long; limit is length 255 but was [" + mapper.type().length() + "]"); } if (mapper.type().charAt(0) == '_') { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] can't start with '_'"); } if (mapper.type().contains("#")) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] should not include '#' in it"); } if (mapper.type().contains(",")) { throw new InvalidTypeNameException( "mapping type name [" + mapper.type() + "] should not include ',' in it"); } if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1) && mapper.type().equals(mapper.parentFieldMapper().type())) { throw new IllegalArgumentException( "The [_parent.type] option can't point to the same type"); } if (typeNameStartsWithIllegalDot(mapper)) { if (Version.indexCreated(indexSettings).onOrAfter(Version.V_2_0_0_beta1)) { throw new IllegalArgumentException( "mapping type name [" + mapper.type() + "] must not start with a '.'"); } else { logger.warn( "Type [{}] starts with a '.', it is recommended not to start a type name with a '.'", mapper.type()); } } // we can add new field/object mappers while the old ones are there // since we get new instances of those, and when we remove, we remove // by instance equality DocumentMapper oldMapper = mappers.get(mapper.type()); if (oldMapper != null) { MergeResult result = oldMapper.merge(mapper.mapping(), false, updateAllTypes); if (result.hasConflicts()) { // TODO: What should we do??? if (logger.isDebugEnabled()) { logger.debug( "merging mapping for type [{}] resulted in conflicts: [{}]", mapper.type(), Arrays.toString(result.buildConflicts())); } } return oldMapper; } else { List<ObjectMapper> newObjectMappers = new ArrayList<>(); List<FieldMapper> newFieldMappers = new ArrayList<>(); for (MetadataFieldMapper metadataMapper : mapper.mapping().metadataMappers) { newFieldMappers.add(metadataMapper); } MapperUtils.collect(mapper.mapping().root, newObjectMappers, newFieldMappers); checkNewMappersCompatibility(newObjectMappers, newFieldMappers, updateAllTypes); addMappers(newObjectMappers, newFieldMappers); for (DocumentTypeListener typeListener : typeListeners) { typeListener.beforeCreate(mapper); } mappers = newMapBuilder(mappers).put(mapper.type(), mapper).map(); if (mapper.parentFieldMapper().active()) { ImmutableSet.Builder<String> parentTypesCopy = ImmutableSet.builder(); parentTypesCopy.addAll(parentTypes); parentTypesCopy.add(mapper.parentFieldMapper().type()); parentTypes = parentTypesCopy.build(); } assert assertSerialization(mapper); return mapper; } } }
/** * static method to create the object Precondition: If this object is an element, the current or * next start element starts this object and any intervening reader events are ignorable If this * object is not an element, it is a complex type and the reader is at the event just after the * outer start element Postcondition: If this object is an element, the reader is positioned at * its end element If this object is a complex type, the reader is positioned at the end element * of its outer element */ public static MergeResult parse(javax.xml.stream.XMLStreamReader reader) throws java.lang.Exception { MergeResult object = new MergeResult(); int event; java.lang.String nillableValue = null; java.lang.String prefix = ""; java.lang.String namespaceuri = ""; try { while (!reader.isStartElement() && !reader.isEndElement()) reader.next(); if (reader.getAttributeValue("http://www.w3.org/2001/XMLSchema-instance", "type") != null) { java.lang.String fullTypeName = reader.getAttributeValue("http://www.w3.org/2001/XMLSchema-instance", "type"); if (fullTypeName != null) { java.lang.String nsPrefix = null; if (fullTypeName.indexOf(":") > -1) { nsPrefix = fullTypeName.substring(0, fullTypeName.indexOf(":")); } nsPrefix = nsPrefix == null ? "" : nsPrefix; java.lang.String type = fullTypeName.substring(fullTypeName.indexOf(":") + 1); if (!"MergeResult".equals(type)) { // find namespace for the prefix java.lang.String nsUri = reader.getNamespaceContext().getNamespaceURI(nsPrefix); return (MergeResult) com.rsys.ws.fault.ExtensionMapper.getTypeObject(nsUri, type, reader); } } } // Note all attributes that were handled. Used to differ normal attributes // from anyAttributes. java.util.Vector handledAttributes = new java.util.Vector(); reader.next(); while (!reader.isStartElement() && !reader.isEndElement()) reader.next(); if (reader.isStartElement() && new javax.xml.namespace.QName("urn:ws.rsys.com", "insertCount") .equals(reader.getName())) { nillableValue = reader.getAttributeValue("http://www.w3.org/2001/XMLSchema-instance", "nil"); if (!"true".equals(nillableValue) && !"1".equals(nillableValue)) { java.lang.String content = reader.getElementText(); object.setInsertCount( org.apache.axis2.databinding.utils.ConverterUtil.convertToLong(content)); } else { object.setInsertCount(java.lang.Long.MIN_VALUE); reader.getElementText(); // throw away text nodes if any. } reader.next(); } // End of if for expected property start element else { // A start element we are not expecting indicates an invalid parameter was passed throw new org.apache.axis2.databinding.ADBException( "Unexpected subelement " + reader.getName()); } while (!reader.isStartElement() && !reader.isEndElement()) reader.next(); if (reader.isStartElement() && new javax.xml.namespace.QName("urn:ws.rsys.com", "updateCount") .equals(reader.getName())) { nillableValue = reader.getAttributeValue("http://www.w3.org/2001/XMLSchema-instance", "nil"); if (!"true".equals(nillableValue) && !"1".equals(nillableValue)) { java.lang.String content = reader.getElementText(); object.setUpdateCount( org.apache.axis2.databinding.utils.ConverterUtil.convertToLong(content)); } else { object.setUpdateCount(java.lang.Long.MIN_VALUE); reader.getElementText(); // throw away text nodes if any. } reader.next(); } // End of if for expected property start element else { // A start element we are not expecting indicates an invalid parameter was passed throw new org.apache.axis2.databinding.ADBException( "Unexpected subelement " + reader.getName()); } while (!reader.isStartElement() && !reader.isEndElement()) reader.next(); if (reader.isStartElement() && new javax.xml.namespace.QName("urn:ws.rsys.com", "rejectedCount") .equals(reader.getName())) { nillableValue = reader.getAttributeValue("http://www.w3.org/2001/XMLSchema-instance", "nil"); if (!"true".equals(nillableValue) && !"1".equals(nillableValue)) { java.lang.String content = reader.getElementText(); object.setRejectedCount( org.apache.axis2.databinding.utils.ConverterUtil.convertToLong(content)); } else { object.setRejectedCount(java.lang.Long.MIN_VALUE); reader.getElementText(); // throw away text nodes if any. } reader.next(); } // End of if for expected property start element else { // A start element we are not expecting indicates an invalid parameter was passed throw new org.apache.axis2.databinding.ADBException( "Unexpected subelement " + reader.getName()); } while (!reader.isStartElement() && !reader.isEndElement()) reader.next(); if (reader.isStartElement() && new javax.xml.namespace.QName("urn:ws.rsys.com", "totalCount") .equals(reader.getName())) { nillableValue = reader.getAttributeValue("http://www.w3.org/2001/XMLSchema-instance", "nil"); if (!"true".equals(nillableValue) && !"1".equals(nillableValue)) { java.lang.String content = reader.getElementText(); object.setTotalCount( org.apache.axis2.databinding.utils.ConverterUtil.convertToLong(content)); } else { object.setTotalCount(java.lang.Long.MIN_VALUE); reader.getElementText(); // throw away text nodes if any. } reader.next(); } // End of if for expected property start element else { // A start element we are not expecting indicates an invalid parameter was passed throw new org.apache.axis2.databinding.ADBException( "Unexpected subelement " + reader.getName()); } while (!reader.isStartElement() && !reader.isEndElement()) reader.next(); if (reader.isStartElement() && new javax.xml.namespace.QName("urn:ws.rsys.com", "errorMessage") .equals(reader.getName())) { nillableValue = reader.getAttributeValue("http://www.w3.org/2001/XMLSchema-instance", "nil"); if (!"true".equals(nillableValue) && !"1".equals(nillableValue)) { java.lang.String content = reader.getElementText(); object.setErrorMessage( org.apache.axis2.databinding.utils.ConverterUtil.convertToString(content)); } else { reader.getElementText(); // throw away text nodes if any. } reader.next(); } // End of if for expected property start element else { // A start element we are not expecting indicates an invalid parameter was passed throw new org.apache.axis2.databinding.ADBException( "Unexpected subelement " + reader.getName()); } while (!reader.isStartElement() && !reader.isEndElement()) reader.next(); if (reader.isStartElement()) // A start element we are not expecting indicates a trailing invalid property throw new org.apache.axis2.databinding.ADBException( "Unexpected subelement " + reader.getName()); } catch (javax.xml.stream.XMLStreamException e) { throw new java.lang.Exception(e); } return object; }