GP-4341 Force retained checkout if file is in-use during checkin or add-to-version-control. Deprecated upgrade concept during checkin. Revised manner in which file open for update is updated following a version control operation (perform DBHandle update).

This commit is contained in:
ghidra1 2024-03-20 17:43:49 -04:00
parent 74a5b6f0e1
commit 2dff876f0f
46 changed files with 695 additions and 852 deletions

View file

@ -17,6 +17,7 @@ package ghidra.framework.data;
import java.io.IOException;
import javax.help.UnsupportedOperationException;
import javax.swing.Icon;
import ghidra.framework.model.*;
@ -83,9 +84,8 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
* @throws VersionException if unable to handle file content due to version
* difference which could not be handled.
*/
T getImmutableObject(FolderItem item, Object consumer, int version,
int minChangeVersion, TaskMonitor monitor)
throws IOException, CancelledException, VersionException;
T getImmutableObject(FolderItem item, Object consumer, int version, int minChangeVersion,
TaskMonitor monitor) throws IOException, CancelledException, VersionException;
/**
* Open a folder item for read-only use. While changes are permitted on the
@ -104,9 +104,8 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
* @throws VersionException if unable to handle file content due to version
* difference which could not be handled.
*/
T getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade,
Object consumer, TaskMonitor monitor)
throws IOException, VersionException, CancelledException;
T getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade, Object consumer,
TaskMonitor monitor) throws IOException, VersionException, CancelledException;
/**
* Open a folder item for update. Changes made to the returned object may be
@ -127,8 +126,8 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
* @throws VersionException if unable to handle file content due to version
* difference which could not be handled.
*/
T getDomainObject(FolderItem item, FileSystem userfs, long checkoutId,
boolean okToUpgrade, boolean okToRecover, Object consumer, TaskMonitor monitor)
T getDomainObject(FolderItem item, FileSystem userfs, long checkoutId, boolean okToUpgrade,
boolean okToRecover, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException;
/**
@ -204,4 +203,47 @@ public interface ContentHandler<T extends DomainObjectAdapter> extends Extension
return null;
}
/**
* Determine if this content handler supports the use of
* {@link #resetDBSourceFile(FolderItem, DomainObjectAdapterDB)} .
* <p>
* A versioned {@link DomainObjectAdapterDB domain object} open for update may have its
* underlying database reset to the latest buffer file version:
* <ol>
* <li>The {@link #resetDBSourceFile(FolderItem, DomainObjectAdapterDB)} method is
* invoked (synchronized on filesystem) to reset the underlying database source file and
* and any corresponding change sets held by the specified domain object to the latest
* version,</li>
* <li>afterwhich the caller must {@link DomainObjectAdapter#invalidate() invalidate} the domain
* object instance which will clear all caches and generate a {@link DomainObjectEvent#RESTORED}
* event.</li>
* </ol>
* @return true if this content handler supports DB source file replacement, else false
*/
public default boolean canResetDBSourceFile() {
return false;
}
/**
* Reset the database for the specified domain object to its latest buffer file version.
* It is very important that the specified folder item matches the item which was used to
* originally open the specified domain object. This method should be invoked with a
* filesystem lock.
* <p>
* Following the invocation of this method, the specified domain object should be
* {@link DomainObjectAdapter#invalidate() invalidated} without a filesystem lock.
*
* @param item local versioned database folder item currently checked-out. An error will be
* thrown if not an instanceof LocalDatabaseItem. This should always be the case for an item
* which has just processed a versioning action with a retained checkout (e.g., checkin,
* merge, add-to-version-control).
* @param domainObj domain object which is currently open for update
* @throws IOException if an IO error occurs
* @throws IllegalArgumentException if invalid or unsupported arguments are provided
*/
public default void resetDBSourceFile(FolderItem item, DomainObjectAdapterDB domainObj)
throws IOException {
throw new UnsupportedOperationException();
}
}

View file

@ -17,6 +17,8 @@ package ghidra.framework.data;
import java.io.IOException;
import javax.help.UnsupportedOperationException;
import db.DBHandle;
import db.buffers.ManagedBufferFile;
import ghidra.framework.store.*;
@ -55,9 +57,8 @@ public abstract class DBContentHandler<T extends DomainObjectAdapterDB>
FileSystem fs, String path, String name, TaskMonitor monitor)
throws InvalidNameException, CancelledException, IOException {
DBHandle dbh = domainObj.getDBHandle();
ManagedBufferFile bf =
fs.createDatabase(path, name, FileIDFactory.createFileID(), contentType,
dbh.getBufferSize(), SystemUtilities.getUserName(), null);
ManagedBufferFile bf = fs.createDatabase(path, name, FileIDFactory.createFileID(),
contentType, dbh.getBufferSize(), SystemUtilities.getUserName(), null);
long checkoutId = bf.getCheckinID(); // item remains checked-out after saveAs
boolean success = false;
try {

View file

@ -21,7 +21,7 @@ import ghidra.util.exception.CancelledException;
/**
* <code>DefaultCheckinHandler</code> provides a simple
* check-in handler for use with
* {@link DomainFile#checkin(CheckinHandler, boolean, ghidra.util.task.TaskMonitor)}
* {@link DomainFile#checkin(CheckinHandler, ghidra.util.task.TaskMonitor)}
*/
public class DefaultCheckinHandler implements CheckinHandler {

View file

@ -181,57 +181,31 @@ class DomainFileIndex implements DomainFolderChangeListener {
return null;
}
@Override
public void domainFileAdded(DomainFile file) {
updateFileEntry((GhidraFile) file);
}
@Override
public void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName) {
updateFileEntry((GhidraFile) file);
}
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// no-op
}
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// no-op
}
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// no-op
}
@Override
public void domainFileRemoved(DomainFolder parent, String name, String fileID) {
fileIdToPathIndex.remove(fileID);
}
@Override
public void domainFileRenamed(DomainFile file, String oldName) {
updateFileEntry((GhidraFile) file);
}
@Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (fileIDset) {
updateFileEntry((GhidraFile) file);
}
}
public void domainFolderAdded(DomainFolder folder) {
// no-op
}
public void domainFolderMoved(DomainFolder folder, DomainFolder oldParent) {
// no-op
}
public void domainFolderRemoved(DomainFolder parent, String name) {
// no-op
}
public void domainFolderRenamed(DomainFolder folder, String oldName) {
// no-op
}
public void domainFolderSetActive(DomainFolder folder) {
// no-op
}
}

View file

@ -398,7 +398,7 @@ public class DomainFileProxy implements DomainFile {
}
@Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException("Repository operations not supported");
}

View file

@ -26,8 +26,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
private DomainFileIndex fileIndex;
/** CopyOnWriteArrayList prevents the need for synchronization */
private List<DomainFolderChangeListener> list =
new CopyOnWriteArrayList<>();
private List<DomainFolderChangeListener> list = new CopyOnWriteArrayList<>();
DomainFolderChangeListenerList(DomainFileIndex fileIndex) {
this.fileIndex = fileIndex;
@ -199,19 +198,6 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
});
}
@Override
public void domainFileObjectReplaced(final DomainFile file, final DomainObject oldObject) {
fileIndex.domainFileObjectReplaced(file, oldObject);
if (list.isEmpty()) {
return;
}
Swing.runNow(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileObjectReplaced(file, oldObject);
}
});
}
public void clearAll() {
list.clear();
}

View file

@ -81,8 +81,8 @@ public abstract class DomainObjectAdapter implements DomainObject {
* with consumer.
*
* @param name name of the object
* @param timeInterval the time (in milliseconds) to wait before the event queue is flushed. If
* a new event comes in before the time expires, the timer is reset.
* @param timeInterval the time (in milliseconds) to wait before the event queue is flushed.
* If a new event comes in before the time expires the timer is reset.
* @param consumer the object that created this domain object
*/
protected DomainObjectAdapter(String name, int timeInterval, Object consumer) {
@ -98,6 +98,15 @@ public abstract class DomainObjectAdapter implements DomainObject {
}
}
/**
* Invalidates any caching in a program and generate a {@link DomainObjectEvent#RESTORED}
* event.
* NOTE: Over-using this method can adversely affect system performance.
*/
public void invalidate() {
fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED));
}
@Override
public void release(Object consumer) {
synchronized (consumers) {

View file

@ -37,32 +37,6 @@ import ghidra.util.task.TaskMonitor;
* concept of starting a transaction before a change is made to the
* domain object and ending the transaction. The transaction allows for
* undo/redo changes.
*
* The implementation class must also satisfy the following requirements:
* <pre>
*
* 1. The following constructor signature must be implemented:
*
* **
* * Constructs new Domain Object
* * @param dbh a handle to an open domain object database.
* * @param openMode one of:
* * READ_ONLY: the original database will not be modified
* * UPDATE: the database can be written to.
* * UPGRADE: the database is upgraded to the latest schema as it is opened.
* * @param monitor TaskMonitor that allows the open to be cancelled.
* * @param consumer the object that keeping the program open.
* *
* * @throws IOException if an error accessing the database occurs.
* * @throws VersionException if database version does not match implementation. UPGRADE may be possible.
* **
* public DomainObjectAdapterDB(DBHandle dbh, int openMode, TaskMonitor monitor, Object consumer) throws IOException, VersionException
*
* 2. The following static field must be provided:
*
* public static final String CONTENT_TYPE
*
* </pre>
*/
public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
implements ErrorHandler, DBConstants {
@ -100,12 +74,10 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
/**
* Construct a new DomainObjectAdapterDB object.
* If construction of this object fails, be sure to release with consumer
* @param dbh database handle
* @param name name of the domain object
* @param timeInterval the time (in milliseconds) to wait before the
* event queue is flushed. If a new event comes in before the time expires,
* the timer is reset.
* @param timeInterval the time (in milliseconds) to wait before the event queue is flushed.
* If a new event comes in before the time expires the timer is reset.
* @param consumer the object that created this domain object
*/
protected DomainObjectAdapterDB(DBHandle dbh, String name, int timeInterval, Object consumer) {
@ -129,6 +101,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
* prior to closing a transaction.
*/
public void flushWriteCache() {
// do nothing
}
/**
@ -137,6 +110,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
* prior to aborting a transaction.
*/
public void invalidateWriteCache() {
// do nothing
}
/**
@ -491,6 +465,12 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
transactionMgr.clearUndo(notifyListeners);
}
@Override
public void invalidate() {
clearCache(false);
super.invalidate();
}
protected void clearCache(boolean all) {
options.clearCache();
}

View file

@ -382,10 +382,11 @@ public class GhidraFile implements DomainFile {
@Override
public boolean canAddToRepository() {
try {
return getFileData().canAddToRepository();
getFileData().checkCanAddToRepository();
return true;
}
catch (IOException e) {
fileError(e);
// ignore
}
return false;
}
@ -473,10 +474,9 @@ public class GhidraFile implements DomainFile {
}
@Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
getFileData().checkin(checkinHandler, okToUpgrade,
monitor != null ? monitor : TaskMonitor.DUMMY);
getFileData().checkin(checkinHandler, monitor != null ? monitor : TaskMonitor.DUMMY);
}
@Override

View file

@ -436,9 +436,8 @@ public class GhidraFileData {
ChangeSet getChangesByOthersSinceCheckout() throws VersionException, IOException {
synchronized (fileSystem) {
if (versionedFolderItem != null && folderItem != null && folderItem.isCheckedOut()) {
ContentHandler<?> ch = getContentHandler();
return ch.getChangeSet(versionedFolderItem, folderItem.getCheckoutVersion(),
versionedFolderItem.getCurrentVersion());
return getContentHandler().getChangeSet(versionedFolderItem,
folderItem.getCheckoutVersion(), versionedFolderItem.getCurrentVersion());
}
return null;
}
@ -478,6 +477,12 @@ public class GhidraFileData {
*/
DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToRecover,
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
// Don't allow this call while versioning operation is on-going
if (busy.get()) {
throw new FileInUseException("Cannot open during versioning operation");
}
FolderItem myFolderItem;
DomainObjectAdapter domainObj = null;
synchronized (fileSystem) {
@ -494,9 +499,9 @@ public class GhidraFileData {
return domainObj;
}
}
ContentHandler<?> ch = getContentHandler();
ContentHandler<?> contentHandler = getContentHandler();
if (folderItem == null) {
DomainObjectAdapter doa = ch.getReadOnlyObject(versionedFolderItem,
DomainObjectAdapter doa = contentHandler.getReadOnlyObject(versionedFolderItem,
DomainFile.DEFAULT_VERSION, true, consumer, monitor);
doa.setChanged(false);
DomainFileProxy proxy = new DomainFileProxy(name, parent.getPathname(), doa,
@ -506,7 +511,7 @@ public class GhidraFileData {
}
myFolderItem = folderItem;
domainObj = ch.getDomainObject(myFolderItem, parent.getUserFileSystem(),
domainObj = contentHandler.getDomainObject(myFolderItem, parent.getUserFileSystem(),
FolderItem.DEFAULT_CHECKOUT_ID, okToUpgrade, okToRecover, consumer, monitor);
projectData.setDomainObject(getPathname(), domainObj);
@ -567,8 +572,8 @@ public class GhidraFileData {
FolderItem item =
(folderItem != null && version == DomainFile.DEFAULT_VERSION) ? folderItem
: versionedFolderItem;
ContentHandler<?> ch = getContentHandler();
DomainObjectAdapter doa = ch.getReadOnlyObject(item, version, true, consumer, monitor);
DomainObjectAdapter doa =
getContentHandler().getReadOnlyObject(item, version, true, consumer, monitor);
doa.setChanged(false);
// Notify file manager of in-use domain object.
@ -606,13 +611,14 @@ public class GhidraFileData {
throws VersionException, IOException, CancelledException {
synchronized (fileSystem) {
DomainObjectAdapter obj = null;
ContentHandler<?> ch = getContentHandler();
ContentHandler<?> contentHandler = getContentHandler();
if (versionedFolderItem == null ||
(version == DomainFile.DEFAULT_VERSION && folderItem != null) || isHijacked()) {
obj = ch.getImmutableObject(folderItem, consumer, version, -1, monitor);
obj = contentHandler.getImmutableObject(folderItem, consumer, version, -1, monitor);
}
else {
obj = ch.getImmutableObject(versionedFolderItem, consumer, version, -1, monitor);
obj = contentHandler.getImmutableObject(versionedFolderItem, consumer, version, -1,
monitor);
}
// Notify file manager of in-use domain object.
@ -870,33 +876,6 @@ public class GhidraFileData {
}
}
/**
* Returns true if this private file may be added to the associated repository.
* @return true if can add to the repository
*/
boolean canAddToRepository() {
synchronized (fileSystem) {
try {
if (fileSystem.isReadOnly() || versionedFileSystem.isReadOnly()) {
return false;
}
if (folderItem == null || versionedFolderItem != null) {
return false;
}
if (folderItem.isCheckedOut()) {
return false;
}
if (isLinkFile()) {
return GhidraURL.isServerRepositoryURL(LinkHandler.getURL(folderItem));
}
return !getContentHandler().isPrivateContentType();
}
catch (IOException e) {
return false;
}
}
}
/**
* Returns true if this file may be checked-out from the associated repository.
* User's with read-only repository access will not have checkout ability.
@ -1021,6 +1000,32 @@ public class GhidraFileData {
}
}
/**
* Perform neccessary check to ensure this file may be added to version control.
* @throws IOException if any checks fail or other IO error occurs
*/
void checkCanAddToRepository() throws IOException {
if (!versionedFileSystem.isOnline()) {
throw new NotConnectedException("Not connected to repository server");
}
if (fileSystem.isReadOnly() || versionedFileSystem.isReadOnly()) {
throw new ReadOnlyException(
"versioning permitted within writeable project and repository only");
}
if (folderItem == null) {
throw new FileNotFoundException("File not found");
}
if (folderItem.isCheckedOut() || versionedFolderItem != null) {
throw new IOException("File already versioned");
}
if (isLinkFile() && !GhidraURL.isServerRepositoryURL(LinkHandler.getURL(folderItem))) {
throw new IOException("Local project link-file may not be versioned");
}
if (getContentHandler().isPrivateContentType()) {
throw new IOException("Content may not be versioned: " + getContentType());
}
}
/**
* Adds this private file to version control.
* @param comment new version comment
@ -1033,76 +1038,87 @@ public class GhidraFileData {
*/
void addToVersionControl(String comment, boolean keepCheckedOut, TaskMonitor monitor)
throws IOException, CancelledException {
DomainObjectAdapter oldDomainObj = null;
synchronized (fileSystem) {
if (!canAddToRepository()) {
if (fileSystem.isReadOnly() || versionedFileSystem.isReadOnly()) {
throw new ReadOnlyException(
"addToVersionControl permitted within writeable project and repository only");
}
throw new IOException("addToVersionControl not allowed for file");
}
checkCanAddToRepository();
if (busy.getAndSet(true)) {
throw new FileInUseException(name + " is busy");
}
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted();
try {
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("checkin");
if (isLinkFile()) {
keepCheckedOut = false;
}
String parentPath = parent.getPathname();
String user = ClientUtil.getUserName();
try {
if (folderItem instanceof DatabaseItem) {
DatabaseItem databaseItem = (DatabaseItem) folderItem;
BufferFile bufferFile = databaseItem.open();
try {
versionedFolderItem = versionedFileSystem.createDatabase(parentPath, name,
folderItem.getFileID(), bufferFile, comment,
folderItem.getContentType(), false, monitor, user);
}
finally {
bufferFile.dispose();
}
}
else if (folderItem instanceof DataFileItem) {
DataFileItem dataFileItem = (DataFileItem) folderItem;
InputStream istream = dataFileItem.getInputStream();
try {
versionedFolderItem = versionedFileSystem.createDataFile(parentPath, name,
istream, comment, folderItem.getContentType(), monitor);
}
finally {
istream.close();
}
}
else {
throw new AssertException("Unknown folder item type");
}
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e);
else if (inUseDomainObj != null && !keepCheckedOut) {
keepCheckedOut = true;
Msg.warn(this, "File currently open - must keep checked-out: " + name);
}
oldDomainObj = getOpenedDomainObject();
synchronized (fileSystem) {
if (keepCheckedOut) {
boolean exclusive = !versionedFileSystem.isShared();
ProjectLocator projectLocator = parent.getProjectLocator();
CheckoutType checkoutType;
if (projectLocator.isTransient()) {
checkoutType = CheckoutType.TRANSIENT;
exclusive = true;
String parentPath = parent.getPathname();
String user = ClientUtil.getUserName();
try {
if (folderItem instanceof DatabaseItem) {
DatabaseItem databaseItem = (DatabaseItem) folderItem;
BufferFile bufferFile = databaseItem.open();
try {
versionedFolderItem = versionedFileSystem.createDatabase(parentPath,
name, folderItem.getFileID(), bufferFile, comment,
folderItem.getContentType(), false, monitor, user);
}
finally {
bufferFile.dispose();
}
}
else if (folderItem instanceof DataFileItem) {
DataFileItem dataFileItem = (DataFileItem) folderItem;
InputStream istream = dataFileItem.getInputStream();
try {
versionedFolderItem = versionedFileSystem.createDataFile(parentPath,
name, istream, comment, folderItem.getContentType(), monitor);
}
finally {
istream.close();
}
}
else {
throw new AssertException("Unknown folder item type");
}
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e);
}
if (keepCheckedOut) {
// Maintain exclusive chekout if private repository or file is open for update
boolean exclusive = !versionedFileSystem.isShared() || (inUseDomainObj != null);
ProjectLocator projectLocator = parent.getProjectLocator();
CheckoutType checkoutType;
if (projectLocator.isTransient()) {
checkoutType = CheckoutType.TRANSIENT;
exclusive = true;
}
else {
// All checkouts for non-shared versioning are treated as exclusive
checkoutType =
(exclusive || !versionedFileSystem.isShared()) ? CheckoutType.EXCLUSIVE
: CheckoutType.NORMAL;
}
ItemCheckoutStatus checkout = versionedFolderItem.checkout(checkoutType, user,
ItemCheckoutStatus.getProjectPath(projectLocator.toString(),
projectLocator.isTransient()));
folderItem.setCheckout(checkout.getCheckoutId(), exclusive,
checkout.getCheckoutVersion(), folderItem.getCurrentVersion());
}
else {
// All checkouts for non-shared versioning are treated as exclusive
checkoutType =
(exclusive || !versionedFileSystem.isShared()) ? CheckoutType.EXCLUSIVE
: CheckoutType.NORMAL;
}
ItemCheckoutStatus checkout = versionedFolderItem.checkout(checkoutType, user,
ItemCheckoutStatus.getProjectPath(projectLocator.toString(),
projectLocator.isTransient()));
folderItem.setCheckout(checkout.getCheckoutId(), exclusive,
checkout.getCheckoutVersion(), folderItem.getCurrentVersion());
}
else {
if (oldDomainObj == null) {
// NOTE: file open read-only may prevent removal and result in hijack
try {
folderItem.delete(-1, ClientUtil.getUserName());
folderItem = null;
@ -1111,27 +1127,23 @@ public class GhidraFileData {
// Ignore - should result in Hijacked file
}
}
}
if (oldDomainObj != null) {
// TODO: Develop way to re-use and re-init domain object instead of a switch-a-roo approach
if (inUseDomainObj != null) {
getContentHandler().resetDBSourceFile(folderItem, inUseDomainObj);
}
} // end of synchronized block
projectData.clearDomainObject(getPathname());
oldDomainObj.setDomainFile(new DomainFileProxy("~" + name, oldDomainObj));
oldDomainObj.setTemporary(true);
if (inUseDomainObj != null) {
inUseDomainObj.invalidate();
}
}
if (oldDomainObj != null) {
// Complete re-open of file
DomainFile df = getDomainFile();
listener.domainFileObjectClosed(df, oldDomainObj);
listener.domainFileObjectReplaced(df, oldDomainObj);
}
if (!keepCheckedOut) {
finally {
unlockDomainObject(inUseDomainObj);
busy.set(false);
projectData.mergeEnded();
parent.deleteLocalFolderIfEmpty();
parent.fileChanged(name);
}
statusChanged();
}
/**
@ -1258,7 +1270,8 @@ public class GhidraFileData {
if (versionedFolderItem.getCurrentVersion() != folderItem.getCheckoutVersion()) {
return false;
}
// TODO: assumes folderItem is local - should probably defer createNewVersion to folderItem if possible (requires refactor)
// TODO: assumes folderItem is local - should probably defer createNewVersion
// to folderItem if possible (requires refactor)
srcFile = (LocalManagedBufferFile) ((DatabaseItem) folderItem).open();
}
@ -1266,18 +1279,15 @@ public class GhidraFileData {
if (checkinHandler.createKeepFile()) {
DomainObject sourceObj = null;
try {
ContentHandler<?> ch = getContentHandler();
sourceObj = ch.getImmutableObject(folderItem, this, DomainFile.DEFAULT_VERSION,
-1, monitor);
sourceObj = getContentHandler().getImmutableObject(folderItem, this,
DomainFile.DEFAULT_VERSION, -1, monitor);
createKeepFile(sourceObj, monitor);
}
catch (VersionException e) {
// ignore - unable to create keep file
}
finally {
if (sourceObj != null) {
sourceObj.release(this);
}
release(sourceObj);
}
}
monitor.checkCancelled();
@ -1298,13 +1308,13 @@ public class GhidraFileData {
}
/**
* Verify that current user is the checkout user for this file
* Verify checkout status and that current user is the checkout user for this file
* @param operationName name of user case (e.g., checkin)
* @throws IOException if server/repository will not permit current user to checkin,
* or update checkout version of current file. (i.e., server login does not match
* user name used at time of initial checkout)
*/
private void verifyRepoUser(String operationName) throws IOException {
private void verifyCheckout(String operationName) throws IOException {
if (versionedFileSystem instanceof LocalFileSystem) {
return; // rely on local project ownership
}
@ -1327,15 +1337,13 @@ public class GhidraFileData {
* Performs check in to associated repository. File must be checked-out
* and modified since checkout.
* @param checkinHandler provides user input data to complete checkin process.
* @param okToUpgrade if true an upgrade will be performed if needed
* @param monitor the TaskMonitor.
* @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
* sometime after doing a checkout.
* We are unable to upgrade since this would only occur if checkout is not exclusive.
* @throws CancelledException if task monitor cancelled operation
*/
void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
if (!versionedFileSystem.isOnline()) {
@ -1357,15 +1365,29 @@ public class GhidraFileData {
if (!modifiedSinceCheckout()) {
throw new IOException("File has not been modified since checkout");
}
verifyRepoUser("checkin");
verifyCheckout("checkin");
if (monitor == null) {
monitor = TaskMonitor.DUMMY;
}
if (busy.getAndSet(true)) {
throw new FileInUseException(name + " is busy");
}
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted();
try {
ContentHandler<?> contentHandler = getContentHandler();
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("checkin");
boolean keepCheckedOut = checkinHandler.keepCheckedOut();
if (inUseDomainObj != null && !keepCheckedOut) {
keepCheckedOut = true;
Msg.warn(this, "File currently open - must keep checked-out: " + name);
}
boolean quickCheckin = ALWAYS_MERGE ? false : quickCheckin(checkinHandler, monitor);
if (!quickCheckin) {
@ -1377,9 +1399,8 @@ public class GhidraFileData {
Msg.info(this, "Checkin with merge for " + name);
ContentHandler<?> ch = getContentHandler();
DomainObjectAdapter checkinObj = ch.getDomainObject(versionedFolderItem, null,
folderItem.getCheckoutId(), okToUpgrade, false, this, monitor);
DomainObjectAdapter checkinObj = contentHandler.getDomainObject(versionedFolderItem,
null, folderItem.getCheckoutId(), false, false, this, monitor);
checkinObj.setDomainFile(new DomainFileProxy(name, getParent().getPathname(),
checkinObj, versionedFolderItem.getCurrentVersion() + 1, fileID,
parent.getProjectLocator()));
@ -1390,15 +1411,15 @@ public class GhidraFileData {
try {
synchronized (fileSystem) {
int coVer = folderItem.getCheckoutVersion();
sourceObj = ch.getImmutableObject(folderItem, this,
sourceObj = contentHandler.getImmutableObject(folderItem, this,
DomainFile.DEFAULT_VERSION, -1, monitor);
originalObj =
ch.getImmutableObject(versionedFolderItem, this, coVer, -1, monitor);
latestObj = ch.getImmutableObject(versionedFolderItem, this,
originalObj = contentHandler.getImmutableObject(versionedFolderItem, this,
coVer, -1, monitor);
latestObj = contentHandler.getImmutableObject(versionedFolderItem, this,
DomainFile.DEFAULT_VERSION, coVer, monitor);
}
DomainObjectMergeManager mergeMgr =
ch.getMergeManager(checkinObj, sourceObj, originalObj, latestObj);
DomainObjectMergeManager mergeMgr = contentHandler.getMergeManager(checkinObj,
sourceObj, originalObj, latestObj);
if (!mergeMgr.merge(monitor)) {
Msg.info(this, "Checkin with merge terminated for " + name);
@ -1417,27 +1438,14 @@ public class GhidraFileData {
}
finally {
checkinObj.release(this);
if (sourceObj != null) {
sourceObj.release(this);
}
if (originalObj != null) {
originalObj.release(this);
}
if (latestObj != null) {
latestObj.release(this);
}
release(sourceObj);
release(originalObj);
release(latestObj);
}
}
DomainObjectAdapter oldDomainObj = null;
FolderItem oldLocalItem = null;
boolean keepCheckedOut = checkinHandler.keepCheckedOut();
synchronized (fileSystem) {
oldDomainObj = getOpenedDomainObject();
versionedFolderItem = versionedFileSystem.getItem(parent.getPathname(), name);
if (versionedFolderItem == null) {
throw new IOException("Checkin failed, versioned item not found");
@ -1456,7 +1464,17 @@ public class GhidraFileData {
}
finally {
if (!success) {
// Failed to update checkout for unknown reason
try {
if (inUseDomainObj != null) {
// On error disassociate open domain object from this file
projectData.clearDomainObject(getPathname());
// An invalid version (-2) is specified to avoid file match
inUseDomainObj.setDomainFile(new DomainFileProxy(name,
parent.getPathname(), inUseDomainObj, -2, fileID,
parent.getProjectLocator()));
inUseDomainObj.setTemporary(true);
}
undoCheckout(false, true);
}
catch (IOException e) {
@ -1466,53 +1484,71 @@ public class GhidraFileData {
}
}
else {
if (oldDomainObj != null) {
oldLocalItem = folderItem;
folderItem = null;
}
else {
undoCheckout(false, true);
}
undoCheckout(false, true);
}
if (oldDomainObj != null) {
// TODO: Develop way to re-use and re-init domain object instead of a switch-a-roo approach
projectData.clearDomainObject(getPathname());
oldDomainObj.setDomainFile(new DomainFileProxy(name, parent.getPathname(),
oldDomainObj, -2, fileID, parent.getProjectLocator())); // invalid version (-2) specified to avoid file match
oldDomainObj.setTemporary(true);
if (inUseDomainObj != null) {
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
}
}
if (oldDomainObj != null) {
// complete re-open of domain file
DomainFile df = getDomainFile();
listener.domainFileObjectClosed(df, oldDomainObj);
listener.domainFileObjectReplaced(df, oldDomainObj);
}
} // end of synchronized block
if (oldLocalItem != null) {
synchronized (fileSystem) {
// Undo checkout of old item - this will fail on Windows if item is open
long checkoutId = oldLocalItem.getCheckoutId();
oldLocalItem.delete(-1, ClientUtil.getUserName());
versionedFolderItem.terminateCheckout(checkoutId, true);
}
if (inUseDomainObj != null) {
inUseDomainObj.invalidate();
}
}
finally {
unlockDomainObject(inUseDomainObj);
busy.set(false);
try {
parent.deleteLocalFolderIfEmpty();
parent.fileChanged(name);
projectData.mergeEnded();
parent.deleteLocalFolderIfEmpty();
parent.fileChanged(name);
}
}
private void release(DomainObject domainObj) {
if (domainObj != null) {
domainObj.release(this);
}
}
private void unlockDomainObject(DomainObjectAdapterDB lockedDomainObject) {
try {
if (lockedDomainObject != null) {
lockedDomainObject.unlock();
}
finally {
projectData.mergeEnded();
}
catch (Exception e) {
Msg.error(this, "Unexpected " + getContentType() + " lock error: " + getName());
}
}
private DomainObjectAdapterDB getAndLockInUseDomainObjectForMergeUpdate(String operation)
throws IOException {
DomainObjectAdapterDB inUseDomainObj;
synchronized (fileSystem) {
DomainObjectAdapter domainObj = getOpenedDomainObject();
if (domainObj == null) {
return null;
}
// If we proceed with file in-use it must be instance of DomainObjectAdapterDB
if (!(domainObj instanceof DomainObjectAdapterDB)) {
throw new FileInUseException(name + " is in use");
}
inUseDomainObj = (DomainObjectAdapterDB) domainObj;
if (inUseDomainObj.isChanged()) {
throw new FileInUseException(name + " is in use w/ unsaved changes");
}
}
// Ensure that existing domain object will support DB merge update and is can be locked
ContentHandler<?> contentHandler = getContentHandler();
if (!contentHandler.canResetDBSourceFile() || !inUseDomainObj.lock(operation) ||
inUseDomainObj.getDBHandle().hasUncommittedChanges()) {
throw new FileInUseException(name + " is in use");
}
return inUseDomainObj;
}
/**
@ -1619,7 +1655,7 @@ public class GhidraFileData {
throw new IOException("File not checked out");
}
if (!doForce) {
verifyRepoUser("undo-checkout");
verifyCheckout("undo-checkout");
long checkoutId = folderItem.getCheckoutId();
versionedFolderItem.terminateCheckout(checkoutId, true);
}
@ -1666,7 +1702,7 @@ public class GhidraFileData {
return false;
}
private void createKeepFile(DomainObject oldDomainObj, TaskMonitor monitor) {
private void createKeepFile(DomainObject sourceObj, TaskMonitor monitor) {
String keepName = name + ".keep";
try {
GhidraFileData keepFileData = parent.getFileData(keepName, false);
@ -1683,7 +1719,7 @@ public class GhidraFileData {
}
keepName = getKeepName();
Msg.info(this, "Creating old version keep file: " + keepName);
parent.createFile(keepName, oldDomainObj, monitor);
parent.createFile(keepName, sourceObj, monitor);
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e);
@ -1765,10 +1801,10 @@ public class GhidraFileData {
private void removeAssociatedUserDataFile() {
try {
ContentHandler<?> ch = getContentHandler();
if (ch instanceof DBWithUserDataContentHandler) {
ContentHandler<?> contentHandler = getContentHandler();
if (contentHandler instanceof DBWithUserDataContentHandler) {
FolderItem item = folderItem != null ? folderItem : versionedFolderItem;
((DBWithUserDataContentHandler<?>) ch).removeUserDataFile(item,
((DBWithUserDataContentHandler<?>) contentHandler).removeUserDataFile(item,
parent.getUserFileSystem());
}
}
@ -1812,7 +1848,7 @@ public class GhidraFileData {
if (canRecover()) {
throw new IOException("File recovery data exists");
}
verifyRepoUser("merge");
verifyCheckout("merge");
if (monitor == null) {
monitor = TaskMonitor.DUMMY;
}
@ -1821,8 +1857,11 @@ public class GhidraFileData {
}
FolderItem tmpItem = null;
DomainObjectAdapterDB inUseDomainObj = null;
projectData.mergeStarted();
try {
inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("merge");
if (!modifiedSinceCheckout()) {
// Quick merge
folderItem.updateCheckout(versionedFolderItem, true, monitor);
@ -1830,17 +1869,17 @@ public class GhidraFileData {
else {
if (SystemUtilities.isInHeadlessMode()) {
throw new IOException(
"Merge failed, file merge is not supported in headless mode");
throw new IOException("Merge failed, merge is not supported in headless mode");
}
ContentHandler<?> ch = getContentHandler();
ContentHandler<?> contentHandler = getContentHandler();
// Test versioned file for VersionException
int mergeVer = versionedFolderItem.getCurrentVersion();
if (!okToUpgrade) {
DomainObject testObj =
ch.getReadOnlyObject(versionedFolderItem, mergeVer, false, this, monitor);
// verify remote version can be opened without verion error
DomainObject testObj = contentHandler.getReadOnlyObject(versionedFolderItem,
mergeVer, false, this, monitor);
testObj.release(this);
}
@ -1866,21 +1905,21 @@ public class GhidraFileData {
tmpItem.setCheckout(checkoutId, folderItem.isCheckedOutExclusive(), mergeVer, 0);
DomainObject mergeObj =
ch.getDomainObject(tmpItem, null, -1, okToUpgrade, false, this, monitor);
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 = ch.getImmutableObject(folderItem, this, DomainFile.DEFAULT_VERSION,
-1, monitor);
originalObj =
ch.getImmutableObject(versionedFolderItem, this, coVer, -1, monitor);
latestObj =
ch.getImmutableObject(versionedFolderItem, this, mergeVer, coVer, monitor);
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 =
ch.getMergeManager(mergeObj, sourceObj, originalObj, latestObj);
contentHandler.getMergeManager(mergeObj, sourceObj, originalObj, latestObj);
if (!mergeMgr.merge(monitor)) {
Msg.info(this, "Merge terminated for " + name);
@ -1888,19 +1927,14 @@ public class GhidraFileData {
}
mergeObj.save("Merge with version " + mergeVer, monitor);
createKeepFile(sourceObj, monitor);
}
finally {
mergeObj.release(this);
if (sourceObj != null) {
sourceObj.release(this);
}
if (originalObj != null) {
originalObj.release(this);
}
if (latestObj != null) {
latestObj.release(this);
}
release(mergeObj);
release(sourceObj);
release(originalObj);
release(latestObj);
}
// Update folder item
@ -1909,29 +1943,18 @@ public class GhidraFileData {
ClientUtil.getUserName());
tmpItem = null;
Msg.info(this, "Merge completed for " + name);
}
DomainObjectAdapter oldDomainObj = null;
// TODO: Develop way to re-use and re-init domain object instead of a switch-a-roo approach
synchronized (fileSystem) {
oldDomainObj = getOpenedDomainObject();
if (oldDomainObj != null) {
projectData.clearDomainObject(getPathname());
oldDomainObj.setDomainFile(new DomainFileProxy("~" + name, oldDomainObj));
oldDomainObj.setTemporary(true);
if (inUseDomainObj != null) {
contentHandler.resetDBSourceFile(folderItem, inUseDomainObj);
}
}
if (oldDomainObj != null) {
// Complete re-open of file
DomainFile df = getDomainFile();
listener.domainFileObjectClosed(df, oldDomainObj);
listener.domainFileObjectReplaced(df, oldDomainObj);
if (inUseDomainObj != null) {
inUseDomainObj.invalidate();
}
}
finally {
unlockDomainObject(inUseDomainObj);
busy.set(false);
try {
if (tmpItem != null) {
@ -2281,19 +2304,22 @@ public class GhidraFileData {
/**
* Returns an ordered map containing the metadata stored within a specific {@link FolderItem}
* database. The map contains key,value pairs and are ordered by their insertion order.
* @param item folder item whose metadata should be read
* @return a map containing the metadata that has been associated with the corresponding domain
* object. Map will be empty for a non-database item.
*/
static Map<String, String> getMetadata(FolderItem item) {
if (!(item instanceof DatabaseItem databaseItem)) {
return new HashMap<>();
}
ManagedBufferFile bf = null;
DBHandle dbh = null;
GenericDomainObjectDB genericDomainObj = null;
try {
if (item instanceof DatabaseItem) {
DatabaseItem databaseItem = (DatabaseItem) item;
BufferFile bf = databaseItem.open();
DBHandle dbh = new DBHandle(bf);
genericDomainObj = new GenericDomainObjectDB(dbh);
return genericDomainObj.getMetadata();
}
bf = databaseItem.open();
dbh = new DBHandle(bf);
genericDomainObj = new GenericDomainObjectDB(dbh);
return genericDomainObj.getMetadata();
}
catch (FileNotFoundException e) {
// file has been deleted, just return an empty map.
@ -2308,6 +2334,12 @@ public class GhidraFileData {
if (genericDomainObj != null) {
genericDomainObj.release();
}
if (dbh != null) {
dbh.close();
}
if (bf != null) {
bf.dispose();
}
}
return new HashMap<>();
}

View file

@ -302,7 +302,7 @@ class LinkedGhidraFile implements LinkedDomainFile {
}
@Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException();
}

View file

@ -96,8 +96,7 @@ public class ProjectDataTablePanel extends JPanel {
}
});
gTable.getSelectionModel()
.addListSelectionListener(
e -> plugin.getTool().contextChanged(null));
.addListSelectionListener(e -> plugin.getTool().contextChanged(null));
gTable.setDefaultRenderer(Date.class, new DateCellRenderer());
gTable.setDefaultRenderer(DomainFileType.class, new TypeCellRenderer());
@ -364,11 +363,6 @@ public class ProjectDataTablePanel extends JPanel {
reload();
}
@Override
public void domainFolderSetActive(DomainFolder folder) {
// don't care
}
@Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (ignoreChanges()) {
@ -379,24 +373,6 @@ public class ProjectDataTablePanel extends JPanel {
plugin.getTool().contextChanged(null);
}
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
if (ignoreChanges()) {
return;
}
clearInfo(file);
table.repaint();
}
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// don't care
}
@Override
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// don't care
}
}
/**

View file

@ -140,11 +140,6 @@ class ChangeManager implements DomainFolderChangeListener {
}
}
// @Override
// public void domainFileSaved(DomainFile file, DomainObject dobj) {
// treePanel.getActionManager().adjustActions();
// }
@Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
DomainFileNode fileNode = findDomainFileNode(file, true);
@ -152,7 +147,6 @@ class ChangeManager implements DomainFolderChangeListener {
fileNode.refresh();
}
treePanel.domainChange();
// treePanel.getActionManager().adjustActions();
}
private void getFolderPath(DomainFolder df, List<String> list) {
@ -221,27 +215,4 @@ class ChangeManager implements DomainFolderChangeListener {
}
}
/* (non-Javadoc)
* @see ghidra.framework.model.DomainFolderChangeListener#domainFileObjectReplaced(ghidra.framework.model.DomainFile, ghidra.framework.model.DomainObject)
*/
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// ignored
}
/*
* @see ghidra.framework.model.DomainFolderChangeListener#domainFileObjectOpenedForUpdate(ghidra.framework.model.DomainFile, ghidra.framework.model.DomainObject)
*/
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// ignored
}
/*
* @see ghidra.framework.model.DomainFolderChangeListener#domainFileObjectClosed(ghidra.framework.model.DomainFile, ghidra.framework.model.DomainObject)
*/
@Override
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// ignored
}
}

View file

@ -54,11 +54,9 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
private void promptUser() throws CancelledException {
if (newFile) {
newFile = false;
if (monitor.isCancelled()) {
throw new CancelledException();
}
monitor.checkCancelled();
if (actionID != VersionControlDialog.APPLY_TO_ALL) {
showDialog(false, df.getName(), df.isLinkFile()); // false==> checking in vs.
showDialog(false, df);
// adding to version control
if (actionID == VersionControlDialog.CANCEL) {
monitor.cancel();
@ -69,23 +67,16 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
}
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor myMonitor) {
this.monitor = myMonitor;
myMonitor.setMessage("Examining selected file(s)");
// checkFilesInUse();
String currentName = null;
String currentContentType = null;
try {
for (int i = 0; i < list.size() && actionID != VersionControlDialog.CANCEL; i++) {
df = list.get(i);
currentName = df.getName();
currentContentType = df.getContentType();
newFile = true;
if (i != 0) {
@ -94,27 +85,23 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
Thread.sleep(200);
}
catch (InterruptedException e2) {
break;
}
}
myMonitor.setMessage("Initiating Check In for " + currentName);
try {
df.checkin(this, false, myMonitor);
df.checkin(this, myMonitor);
}
catch (VersionException e) {
if (VersionExceptionHandler.isUpgradeOK(parent, df, "Checkin", e)) {
df.checkin(this, true, myMonitor);
}
VersionExceptionHandler.showVersionError(parent, df.getName(),
df.getContentType(), "Checkin", e);
}
if (myMonitor.isCancelled()) {
break;
}
}
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(parent, df.getName(), currentContentType,
"Checkin", e);
}
catch (CancelledException e) {
Msg.info(this, "Check In Process was canceled");
wasCanceled = true;
@ -125,18 +112,12 @@ public class CheckInTask extends VersionControlTask implements CheckinHandler {
}
}
/*
* @see ghidra.framework.data.CheckinHandler#getComment()
*/
@Override
public String getComment() throws CancelledException {
promptUser();
return comments;
}
/*
* @see ghidra.framework.data.CheckinHandler#keepCheckedOut()
*/
@Override
public boolean keepCheckedOut() throws CancelledException {
promptUser();

View file

@ -57,21 +57,22 @@ public abstract class VersionControlTask extends Task {
* Show the dialog.
* @param addToVersionControl true if the dialog is for
* adding files to version control, false for checking in files.
* @param filename the name of the file currently to be added, whose comment we need.
* @param isLinkFile true if file is a link file, else false. Link-files may not be checked-out
* so keep-checked-out control disabled if this is true.
* @param file the file currently to be added or checked-in to version control
*/
protected void showDialog(boolean addToVersionControl, String filename, boolean isLinkFile) {
protected void showDialog(boolean addToVersionControl, DomainFile file) {
Runnable r = () -> {
VersionControlDialog vcDialog = new VersionControlDialog(addToVersionControl);
vcDialog.setCurrentFileName(filename);
vcDialog.setCurrentFileName(file.getName());
vcDialog.setMultiFiles(list.size() > 1);
if (isLinkFile) {
vcDialog.setKeepCheckboxEnabled(false, false, "Link files may not be Checked Out");
if (file.isLinkFile()) {
vcDialog.setKeepCheckboxEnabled(false, false, "Link file may not be Checked Out");
}
else if (filesInUse) {
vcDialog.setKeepCheckboxEnabled(false, true,
"Must keep Checked Out because the file is in use");
else {
checkFilesInUse();
if (filesInUse) {
vcDialog.setKeepCheckboxEnabled(false, true,
"Must keep Checked Out because the file is in use");
}
}
actionID = vcDialog.showDialog(tool, parent);
keepCheckedOut = vcDialog.keepCheckedOut();
@ -93,9 +94,11 @@ public abstract class VersionControlTask extends Task {
* are still in use.
*/
protected void checkFilesInUse() {
// NOTE: In-use check is currently limited to files open for update but for the purpose of
// maintaining a checkout should really correspond to any file use (e.g., open read-only
// with DomainFileProxy).
filesInUse = false;
for (int i = 0; i < list.size(); i++) {
DomainFile df = list.get(i);
for (DomainFile df : list) {
if (df.getConsumers().size() > 0) {
filesInUse = true;
return;
@ -104,8 +107,7 @@ public abstract class VersionControlTask extends Task {
}
protected boolean checkFilesForUnsavedChanges() {
for (int i = 0; i < list.size(); i++) {
DomainFile df = list.get(i);
for (DomainFile df : list) {
if (df.modifiedSinceCheckout()) {
return true;
}

View file

@ -136,14 +136,13 @@ public class VersionControlAddAction extends VersionControlAction {
@Override
public void run(TaskMonitor monitor) {
checkFilesInUse();
try {
for (DomainFile df : list) {
String name = df.getName();
monitor.setMessage("Adding " + name + " to Version Control");
if (actionID != VersionControlDialog.APPLY_TO_ALL) {
showDialog(true, name, df.isLinkFile());
showDialog(true, df);
}
if (actionID == VersionControlDialog.CANCEL) {
return;

View file

@ -305,19 +305,34 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Returns true if this file may be checked-in to the associated repository.
* @return true if can check-in
*
* Note: this does not take into consideration cases where the file is currently
* in-use which may cause a failure if a checkin is attempted.
*
* @return true if a check-in can be attempted (i.e., file is checked-out with changes),
* else false
*/
public boolean canCheckin();
/**
* Returns true if this file can be merged with the current versioned file.
* @return true if can merge
*
* Note: this does not take into consideration cases where the file is currently
* in-use which may cause a failure if a merge is attempted.
*
* @return true if a merge can be attempted (i.e., file is checked-out and a newer
* version exists), else false
*/
public boolean canMerge();
/**
* Returns true if this private file may be added to the associated repository.
* @return true if can add to the repository
*
* Note: this does not take into consideration cases where the file is currently
* in-use which may cause a failure if add to repository is attempted.
*
* @return true if add to the repository can be attempted (i.e., file in active project
* is not versioned or hijacked)
*/
public boolean canAddToRepository();
@ -381,7 +396,8 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Adds this private file to version control.
* @param comment new version comment
* @param keepCheckedOut if true, the file will be initially checked-out
* @param keepCheckedOut if true, the file will be initially checked-out. This option will be
* ignored if file is currently open in which case file will remain checked-out.
* @param monitor progress monitor
* @throws FileInUseException if this file is in-use.
* @throws IOException if an IO or access error occurs. Also if file is not
@ -405,20 +421,42 @@ public interface DomainFile extends Comparable<DomainFile> {
public boolean checkout(boolean exclusive, TaskMonitor monitor)
throws IOException, CancelledException;
/**
* Performs check in to associated repository. File must be checked-out
* and modified since checkout.
* @param checkinHandler provides user input data to complete checkin process.
* The keep-checked-out option supplied by this handler will be ignored if file is currently
* open in which case file will remain checked-out.
* @param monitor the TaskMonitor.
* @throws IOException if an IO or access error occurs
* @throws VersionException if unable to handle domain object version in versioned filesystem.
* We are unable to upgrade since this would only occur if checkout is not exclusive.
* @throws CancelledException if task monitor cancelled operation
*/
public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException;
/**
* Performs check in to associated repository. File must be checked-out
* and modified since checkout.
* @param checkinHandler provides user input data to complete checkin process.
* @param okToUpgrade if true an upgrade will be performed if needed
* This keep-checked-out option supplied by this handler will be ignored and forced true
* if file is currently open.
* @param okToUpgrade if true an upgrade will be performed if needed (ignored)
* @param monitor the TaskMonitor.
* @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
* sometime after doing a checkout.
* @throws CancelledException if task monitor cancelled operation
* @deprecated use alternative {@link #checkin(CheckinHandler, TaskMonitor)} method since
* okToUpgrade cannot be respected and is ignored. Upgrade cannot be performed during checkin.
*/
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
throws IOException, VersionException, CancelledException;
@Deprecated(since = "11.1", forRemoval = true)
public default void checkin(CheckinHandler checkinHandler, boolean okToUpgrade,
TaskMonitor monitor) throws IOException, VersionException, CancelledException {
checkin(checkinHandler, monitor);
}
/**
* Performs merge from current version of versioned file into local checked-out file.

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,21 +25,27 @@ public interface DomainFolderChangeListener {
* Notification that a folder is added to parent.
* @param folder domain folder which was just added.
*/
public void domainFolderAdded(DomainFolder folder);
public default void domainFolderAdded(DomainFolder folder) {
// do nothing
}
/**
* Notification that a file is added to parent folder. You can
* get the parent from the file.
* @param file domain file which was just added.
*/
public void domainFileAdded(DomainFile file);
public default void domainFileAdded(DomainFile file) {
// do nothing
}
/**
* Notification that a domain folder is removed.
* @param parent domain folder which contained the folder that was just removed.
* @param name the name of the folder that was removed.
*/
public void domainFolderRemoved(DomainFolder parent, String name);
public default void domainFolderRemoved(DomainFolder parent, String name) {
// do nothing
}
/**
* Notification that a file was removed
@ -48,40 +53,54 @@ public interface DomainFolderChangeListener {
* @param name the name of the file that was removed.
* @param fileID file ID or null
*/
public void domainFileRemoved(DomainFolder parent, String name, String fileID);
public default void domainFileRemoved(DomainFolder parent, String name, String fileID) {
// do nothing
}
/**
* Notify listeners when a domain folder is renamed.
* @param folder folder that was renamed
* @param oldName old name of folder
*/
public void domainFolderRenamed(DomainFolder folder, String oldName);
public default void domainFolderRenamed(DomainFolder folder, String oldName) {
// do nothing
}
/**
* Notification that the domain file was renamed.
* @param file file that was renamed
* @param oldName old name of the file
*/
public void domainFileRenamed(DomainFile file, String oldName);
public default void domainFileRenamed(DomainFile file, String oldName) {
// do nothing
}
/**
* Notification that the domain folder was moved.
* @param folder the folder (after move)
* @param oldParent original parent folder
*/
public void domainFolderMoved(DomainFolder folder, DomainFolder oldParent);
public default void domainFolderMoved(DomainFolder folder, DomainFolder oldParent) {
// do nothing
}
/**
* Notification that the domain file was moved.
* @param file the file (after move)
* @param oldParent original parent folder
* @param oldName file name prior to move
*/
public void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName);
public default void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName) {
// do nothing
}
/**
* Notification that the setActive() method on the folder was called.
* @param folder folder which was activated/visited
*/
public void domainFolderSetActive(DomainFolder folder);
public default void domainFolderSetActive(DomainFolder folder) {
// do nothing
}
/**
* Notification that the status for a domain file has changed.
@ -89,30 +108,25 @@ public interface DomainFolderChangeListener {
* @param fileIDset if true indicates that the previously missing fileID has been
* established for the specified file.
*/
public void domainFileStatusChanged(DomainFile file, boolean fileIDset);
/**
* Notification that a new version of the domain object exists and the
* current one is no longer valid. Existing consumers should be immediately
* released and no additional use of the oldObject is permitted once this
* method returns. This is only called for domain objects which were
* opened for update.
* @param file file whose object was replaced
* @param oldObject old object that was replaced
*/
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject);
public default void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
// do nothing
}
/**
* Notification that a domain file has been opened for update.
* @param file domain file
* @param object domain object open for update
*/
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object);
public default void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// do nothing
}
/**
* Notification that a domain file previously open for update is in the process of closing.
* @param file domain file
* @param object domain object which was open for update
*/
public void domainFileObjectClosed(DomainFile file, DomainObject object);
public default void domainFileObjectClosed(DomainFile file, DomainObject object) {
// do nothing
}
}

View file

@ -122,11 +122,6 @@ public abstract class DomainFolderListenerAdapter implements DomainFolderChangeL
stateChanged(file.getPathname(), getPathname(oldParent, oldName), false);
}
@Override
public void domainFolderSetActive(DomainFolder folder) {
// do nothing
}
@Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (enableStateChangeCallback) {
@ -135,18 +130,4 @@ public abstract class DomainFolderListenerAdapter implements DomainFolderChangeL
}
}
@Override
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// do nothing
}
@Override
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// do nothing
}
@Override
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// do nothing
}
}

View file

@ -287,7 +287,7 @@ public class TestDummyDomainFile implements DomainFile {
}
@Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
public void checkin(CheckinHandler checkinHandler, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException();
}