void merge(boolean okToUpgrade, TaskMonitor monitor)
			throws IOException, VersionException, CancelledException {
		if (fileSystem.isReadOnly()) {
			throw new ReadOnlyException("merge permitted within writeable project only");
		}
		if (parent.getProjectLocator().isTransient()) {
			throw new IOException("Merge not permitted for transient project");
		}
		if (!versionedFileSystem.isOnline()) {
			throw new NotConnectedException("Not connected to repository server");
		}
		if (!isCheckedOut()) {
			throw new IOException("File not checked out");
		}
		if (!(versionedFolderItem instanceof DatabaseItem)) {
			throw new IOException("unsupported operation");
		}
		if (folderItem.getCheckoutVersion() == versionedFolderItem.getCurrentVersion()) {
			throw new IOException("Versioned file has not been updated since checkout");
		}
		if (isChanged()) {
			throw new IOException("File has unsaved changes");
		}
		if (canRecover()) {
			throw new IOException("File recovery data exists");
		}
		verifyCheckout("merge");
		if (monitor == null) {
			monitor = TaskMonitor.DUMMY;
		}
		if (busy.getAndSet(true)) {
			throw new FileInUseException(name + " is busy");
		}

		FolderItem tmpItem = null;
		DomainObjectAdapterDB inUseDomainObj = null;
		mergeInProgress = true;
		projectData.mergeStarted();
		try {
			inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("merge");

			ContentHandler<?> contentHandler = getContentHandler();

			long checkoutId = folderItem.getCheckoutId();

			if (!modifiedSinceCheckout()) {
				// Quick database update of local folder item
				folderItem.updateCheckout(versionedFolderItem, true, monitor);
			}
			else {
				// Perform interactive merge

				if (SystemUtilities.isInHeadlessMode()) {
					throw new IOException("Merge failed, merge is not supported in headless mode");
				}

				// Test versioned file for VersionException
				int mergeVer = versionedFolderItem.getCurrentVersion();
				if (!okToUpgrade) {
					// verify remote version can be opened without verion error
					DomainObject testObj = contentHandler.getReadOnlyObject(versionedFolderItem,
						mergeVer, false, this, monitor);
					testObj.release(this);
				}

				Msg.info(this, "Merging version " + mergeVer + " for " + name);

				// Copy current versioned item to temporary private item
				DatabaseItem databaseItem = (DatabaseItem) versionedFolderItem;
				BufferFile bufferFile = databaseItem.open(mergeVer);
				try {
					String tmpName = name + ".merge";
					tmpItem = fileSystem.createTemporaryDatabase(parent.getPathname(), tmpName,
						databaseItem.getFileID(), bufferFile, databaseItem.getContentType(), false,
						monitor);
				}
				catch (InvalidNameException e) {
					throw new AssertException("Unexpected error", e);
				}
				finally {
					bufferFile.dispose();
				}
				int coVer = folderItem.getCheckoutVersion();

				tmpItem.setCheckout(checkoutId, folderItem.isCheckedOutExclusive(), mergeVer, 0);

				DomainObject mergeObj = contentHandler.getDomainObject(tmpItem, null, -1,
					okToUpgrade, false, this, monitor);
				DomainObject sourceObj = null;
				DomainObject originalObj = null;
				DomainObject latestObj = null; // TODO: Is there some way to leverage the buffer file we already copied into tmpItem? Missing required change set
				try {
					sourceObj = contentHandler.getImmutableObject(folderItem, this,
						DomainFile.DEFAULT_VERSION, -1, monitor);
					originalObj = contentHandler.getImmutableObject(versionedFolderItem, this,
						coVer, -1, monitor);
					latestObj = contentHandler.getImmutableObject(versionedFolderItem, this,
						mergeVer, coVer, monitor);

					DomainObjectMergeManager mergeMgr =
						contentHandler.getMergeManager(mergeObj, sourceObj, originalObj, latestObj);

					if (!mergeMgr.merge(monitor)) {
						Msg.info(this, "Merge terminated for " + name);
						return; // error displayed by merge manager
					}

					mergeObj.save("Merge with version " + mergeVer, monitor);

					createKeepFile(sourceObj, monitor);
				}
				finally {
					release(mergeObj);
					release(sourceObj);
					release(originalObj);
					release(latestObj);
				}

				// Update local folder item using temporary file content
				folderItem.updateCheckout(tmpItem, mergeVer);
				tmpItem = null; // update removes temporary file if successful
			}

			// update checkout data within versioned repository
			versionedFolderItem.updateCheckoutVersion(checkoutId,
				folderItem.getCheckoutVersion(), ClientUtil.getUserName());

			Msg.info(this, "Updated checkout completed for " + name);

			if (inUseDomainObj != null) {
				// Reset source file and change-sets for open database
				contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
				inUseDomainObj.domainObjectRestored();
			}
		}
		finally {
			unlockDomainObject(inUseDomainObj);
			busy.set(false);
			mergeInProgress = false;
			try {
				if (tmpItem != null) {
					// remove temporary merge file if error occured
					try {
						tmpItem.delete(-1, ClientUtil.getUserName());
					}
					catch (IOException e) {
						Msg.error(this, "IO error", e);
					}
				}
				parent.fileChanged(name);
				if (parent.visited()) {
					parent.refresh(false, true, null);
				}
			}
			finally {
				projectData.mergeEnded();
			}
		}

	}
Performs merge from current version of versioned file into local checked-out file. 
@param okToUpgrade if true an upgrade will be performed if needed
@param monitor task monitor
@throws IOException if an IO or access error occurs
@throws VersionException if unable to handle domain object version in versioned filesystem.
If okToUpgrade was false, check exception to see if it can be upgraded
@throws CancelledException if task monitor cancelled operation
Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java