mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-10-06 02:09:57 +00:00
GP-2496 edit shared project info improvements
This commit is contained in:
parent
09d326ddbb
commit
52d1097c5b
|
@ -335,6 +335,7 @@ src/main/help/help/topics/FrontEndPlugin/images/ConnectTools.png||GHIDRA||||END|
|
|||
src/main/help/help/topics/FrontEndPlugin/images/DeleteProject.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessList.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessPanel.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/MemoryUsage.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/NonSharedProjectInfo.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/OpenProject.png||GHIDRA||||END|
|
||||
|
@ -361,6 +362,7 @@ src/main/help/help/topics/FrontEndPlugin/images/VersionedFileCOnoServer.png||GHI
|
|||
src/main/help/help/topics/FrontEndPlugin/images/VersionedFileCOwithServer.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/VersionedFileIcon.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/ViewOtherProjects.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/ViewProjectAccessPanel.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/closedBookBlue.png||GHIDRA||reviewed||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/connected.gif||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/disconnected.gif||GHIDRA||||END|
|
||||
|
|
|
@ -119,11 +119,20 @@
|
|||
|
||||
<H2><A name="Change_Shared_Project_Info"></A>Changing Shared Project Information</H2>
|
||||
|
||||
<P>Changing shared project details may become neccessary when a server's IP address or name
|
||||
has changed. While other cases are supported, these may cause some issues with private and
|
||||
checked-out project files. Any checked-out file which does not match-up properly will be
|
||||
renamed to a private <I>.keep</I> file within the project and a checkin will no longer be
|
||||
possible. In addition, when switching to and a different repository private files may conflict
|
||||
with those in the repository resulting in
|
||||
<A href="Ghidra_Front_end.htm#HijackedFile">hijacked files</A>.
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>To update repository information:</P>
|
||||
|
||||
<OL>
|
||||
<LI>Close all open files and make sure that all files are checked in.</LI>
|
||||
<LI>Close all open files. Closing all of your active tools (e.g., CodeBrowser) may be
|
||||
the simplest way to accomplish this.</LI>
|
||||
|
||||
<LI>In <I>Project Information</I>, click on the <B>Change Shared Project Info...</B> button
|
||||
to start the <I>Change Shared Project Information</I> wizard. </LI>
|
||||
|
@ -201,23 +210,27 @@
|
|||
and modify user privileges by choosing the <B>Project</B><IMG border="0" src=
|
||||
"../../shared/arrow.gif"><B><A href=
|
||||
"Ghidra_Front_end.htm#Edit_Project_Access_List">Edit Project Access List...</A></B> <A
|
||||
href="Ghidra_Front_end.htm">option</A>. </P>
|
||||
href="Ghidra_Front_end.htm"></A> option. </P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<OL start="8">
|
||||
<LI>Select the <B>Finish</B> button. </LI>
|
||||
|
||||
<LI><A name="Step9"></A>A confirmation dialog is displayed; select the <B>Update</B> button
|
||||
to complete the <I>Change Shared Project Information</I> process.</LI>
|
||||
to start the <I>Change Shared Project Information</I> process.</LI>
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG border="0" src="../../shared/note.png"> After you have updated
|
||||
your project information, you may end up with <A href=
|
||||
"Ghidra_Front_end.htm#HijackedFile">hijacked files</A> if a file of the same name exists in
|
||||
the repository. </P>
|
||||
</BLOCKQUOTE>
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG border="0" src="../../shared/warning.png">
|
||||
If one or more checked-out files do not match-up properly with the new repository you will
|
||||
be prompted to allow these checkouts to be terminated and converted to private <I>.keep</I>
|
||||
files. Such file conversion will prevent such files from ever being checked-in and
|
||||
should be avoided when possible. Click <B>Terminate Checkouts and Continue</B> to proceed
|
||||
with change or <B>Cancel</B> to abort change. The conversion of these files to private .keep
|
||||
files can not be undone.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2> </H2>
|
||||
|
||||
|
@ -225,8 +238,12 @@
|
|||
|
||||
<BLOCKQUOTE>
|
||||
<P>The image below shows project information for a project that is not shared. Note that the
|
||||
repository information is disabled. Before you can convert your project, you must first close
|
||||
any files that you have opened, and check in any files that you have checked out.</P>
|
||||
repository information will not be displayed for a private project. If repository information
|
||||
is displayed this is already a shared project.</P>
|
||||
|
||||
<P>Before you can convert your project, you must first close
|
||||
any files that you have opened. Closing all of your active tools (e.g., CodeBrowser) may be
|
||||
the simplest way to accomplish this.</P>
|
||||
|
||||
<P><IMG border="0" src="../../shared/warning.png"> You will lose all
|
||||
version history for files under local <A href=
|
||||
|
@ -241,7 +258,7 @@
|
|||
|
||||
<BLOCKQUOTE>
|
||||
<P>The steps to converting your project are the same as those described for <A href=
|
||||
"#Change_Shared_Project_Info">changing your repository information</A>. However, at <A href=
|
||||
"#Change_Shared_Project_Info">Change Shared Project Information</A>. However, at <A href=
|
||||
"#Step9">Step 9</A> above you will get a dialog that warns you about losing version history
|
||||
on versioned files. From the warning dialog, click on the <B>Convert</B> button to complete
|
||||
the conversion process. </P>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -256,6 +256,7 @@ public interface RepositoryHandle {
|
|||
* @param parentPath parent folder path
|
||||
* @param itemName name of item
|
||||
* @return checkout data list
|
||||
* @throws FileNotFoundException if folder item not found
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
ItemCheckoutStatus[] getCheckouts(String parentPath, String itemName) throws IOException;
|
||||
|
|
|
@ -379,6 +379,11 @@ public class DomainFileProxy implements DomainFile {
|
|||
throw new UnsupportedOperationException("undoCheckout() unsupported for DomainFileProxy");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoCheckout(boolean keep, boolean force) throws IOException {
|
||||
throw new UnsupportedOperationException("undoCheckout() unsupported for DomainFileProxy");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeSet getChangesByOthersSinceCheckout() throws IOException {
|
||||
return null;
|
||||
|
|
|
@ -28,7 +28,6 @@ import ghidra.util.InvalidNameException;
|
|||
import ghidra.util.ReadOnlyException;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
|
||||
public class GhidraFile implements DomainFile {
|
||||
|
||||
|
@ -90,6 +89,7 @@ public class GhidraFile implements DomainFile {
|
|||
/**
|
||||
* Reassign a new file-ID to resolve file-ID conflict.
|
||||
* Conflicts can occur as a result of a cancelled check-out.
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
void resetFileID() throws IOException {
|
||||
getFileData().resetFileID();
|
||||
|
@ -176,21 +176,21 @@ public class GhidraFile implements DomainFile {
|
|||
public DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToRecover,
|
||||
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
|
||||
return getFileData().getDomainObject(consumer, okToUpgrade, okToRecover,
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainObject getReadOnlyDomainObject(Object consumer, int version, TaskMonitor monitor)
|
||||
throws VersionException, IOException, CancelledException {
|
||||
return getFileData().getReadOnlyDomainObject(consumer, version,
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainObject getImmutableDomainObject(Object consumer, int version, TaskMonitor monitor)
|
||||
throws VersionException, IOException, CancelledException {
|
||||
return getFileData().getImmutableDomainObject(consumer, version,
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -205,7 +205,7 @@ public class GhidraFile implements DomainFile {
|
|||
if (isReadOnly()) {
|
||||
throw new ReadOnlyException("Cannot save to read-only file");
|
||||
}
|
||||
dobj.save(null, monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
dobj.save(null, monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -428,21 +428,21 @@ public class GhidraFile implements DomainFile {
|
|||
public boolean checkout(boolean exclusive, TaskMonitor monitor) throws IOException,
|
||||
CancelledException {
|
||||
return getFileData().checkout(exclusive,
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
|
||||
throws IOException, VersionException, CancelledException {
|
||||
getFileData().checkin(checkinHandler, okToUpgrade,
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(boolean okToUpgrade, TaskMonitor monitor) throws IOException,
|
||||
VersionException, CancelledException {
|
||||
getFileData().merge(okToUpgrade,
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -450,6 +450,11 @@ public class GhidraFile implements DomainFile {
|
|||
getFileData().undoCheckout(keep, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoCheckout(boolean keep, boolean force) throws IOException {
|
||||
getFileData().undoCheckout(keep, force, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminateCheckout(long checkoutId) throws IOException {
|
||||
getFileData().terminateCheckout(checkoutId);
|
||||
|
@ -486,7 +491,7 @@ public class GhidraFile implements DomainFile {
|
|||
CancelledException {
|
||||
GhidraFolder newGhidraParent = (GhidraFolder) newParent; // assumes single implementation
|
||||
return getFileData().copyTo(newGhidraParent.getFolderData(),
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -494,17 +499,19 @@ public class GhidraFile implements DomainFile {
|
|||
throws IOException, CancelledException {
|
||||
GhidraFolder destGhidraFolder = (GhidraFolder) destFolder; // assumes single implementation
|
||||
return getFileData().copyVersionTo(version, destGhidraFolder.getFolderData(),
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy this file to make a private file if it is versioned. This method should be called
|
||||
* only when a non shared project is being converted to a shared project.
|
||||
* @throws IOException
|
||||
* @param monitor task monitor
|
||||
* @throws IOException if an IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
void convertToPrivateFile(TaskMonitor monitor) throws IOException, CancelledException {
|
||||
getFileData().convertToPrivateFile(
|
||||
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -542,7 +549,7 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
@Override
|
||||
public void packFile(File file, TaskMonitor monitor) throws IOException, CancelledException {
|
||||
getFileData().packFile(file, monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
getFileData().packFile(file, monitor != null ? monitor : TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1217,6 +1217,10 @@ public class GhidraFileData {
|
|||
}
|
||||
|
||||
void undoCheckout(boolean keep, boolean inUseOK) throws IOException {
|
||||
undoCheckout(keep, false, inUseOK);
|
||||
}
|
||||
|
||||
void undoCheckout(boolean keep, boolean force, boolean inUseOK) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
if (fileSystem.isReadOnly()) {
|
||||
throw new ReadOnlyException("undoCheckout permitted within writeable project only");
|
||||
|
@ -1224,16 +1228,23 @@ public class GhidraFileData {
|
|||
if (!inUseOK) {
|
||||
checkInUse();
|
||||
}
|
||||
if (!versionedFileSystem.isOnline()) {
|
||||
throw new NotConnectedException("Not connected to repository server");
|
||||
boolean doForce = false;
|
||||
boolean isOnline = versionedFileSystem.isOnline();
|
||||
if (!isOnline) {
|
||||
if (!force) {
|
||||
throw new NotConnectedException("Not connected to repository server");
|
||||
}
|
||||
doForce = true;
|
||||
}
|
||||
if (!isCheckedOut()) {
|
||||
throw new IOException("File not checked out");
|
||||
}
|
||||
verifyRepoUser("undo-checkout");
|
||||
long checkoutId = folderItem.getCheckoutId();
|
||||
if (!doForce) {
|
||||
verifyRepoUser("undo-checkout");
|
||||
long checkoutId = folderItem.getCheckoutId();
|
||||
versionedFolderItem.terminateCheckout(checkoutId, true);
|
||||
}
|
||||
String keepName = getKeepName();
|
||||
versionedFolderItem.terminateCheckout(checkoutId, true);
|
||||
if (keep) {
|
||||
folderItem.clearCheckout();
|
||||
try {
|
||||
|
|
|
@ -27,6 +27,7 @@ import ghidra.framework.remote.User;
|
|||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.framework.store.local.LocalFolderItem;
|
||||
import ghidra.framework.store.remote.RemoteFileSystem;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
@ -384,7 +385,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public DomainFolder getFolder(String path) {
|
||||
public GhidraFolder getFolder(String path) {
|
||||
int len = path.length();
|
||||
if (len == 0 || path.charAt(0) != FileSystem.SEPARATOR_CHAR) {
|
||||
throw new IllegalArgumentException(
|
||||
|
@ -565,33 +566,158 @@ public class ProjectFileManager implements ProjectData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void updateRepositoryInfo(RepositoryAdapter newRepository, TaskMonitor monitor)
|
||||
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
// 1) check for checked out files
|
||||
findCheckedOutFiles(getRootFolder(), monitor);
|
||||
|
||||
newRepository.connect();
|
||||
if (!newRepository.isConnected()) {
|
||||
throw new IOException("new respository not connected");
|
||||
}
|
||||
|
||||
// 2) Update the properties with server info
|
||||
// Terminate any local checkouts which are not valid with newRepository
|
||||
List<DomainFile> checkoutFiles = findCheckedOutFiles(monitor);
|
||||
List<DomainFile> invalidCheckoutFiles =
|
||||
findInvalidCheckouts(checkoutFiles, newRepository, monitor);
|
||||
undoCheckouts(invalidCheckoutFiles, true, force, monitor);
|
||||
|
||||
// Update the properties with server info
|
||||
updatePropertiesFile(newRepository);
|
||||
}
|
||||
|
||||
private void findCheckedOutFiles(DomainFolder folder, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
DomainFile[] files = folder.getFiles();
|
||||
for (DomainFile file : files) {
|
||||
if (monitor.isCancelled()) {
|
||||
throw new CancelledException();
|
||||
private boolean hasInvalidCheckout(DomainFile df, RepositoryAdapter newRepository)
|
||||
throws IOException {
|
||||
try {
|
||||
LocalFolderItem item = fileSystem.getItem(df.getParent().getPathname(), df.getName());
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
if (file.isCheckedOut()) {
|
||||
throw new IOException("File " + file.getPathname() + " is checked out.");
|
||||
|
||||
// TODO: this is not bulletproof since we have limited data to validate checkout.
|
||||
long checkoutId = item.getCheckoutId();
|
||||
int checkoutVersion = item.getCheckoutVersion();
|
||||
|
||||
ItemCheckoutStatus otherCheckoutStatus = newRepository.getCheckout(
|
||||
df.getParent().getPathname(), df.getName(), checkoutId);
|
||||
|
||||
if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) {
|
||||
return true;
|
||||
}
|
||||
if (checkoutVersion != otherCheckoutStatus.getCheckoutVersion()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
DomainFolder[] folders = folder.getFolders();
|
||||
for (DomainFolder folder2 : folders) {
|
||||
if (monitor.isCancelled()) {
|
||||
throw new CancelledException();
|
||||
catch (FileNotFoundException e) {
|
||||
return true;
|
||||
}
|
||||
catch (NotConnectedException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (IOException e) {
|
||||
// skip file
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any domain files listed does not correspond to a checkout in the specified
|
||||
* newRespository.
|
||||
* @param checkoutList project domain files to check
|
||||
* @param newRepository repository to check against before updating
|
||||
* @param monitor task monitor
|
||||
* @return true if one or more files are not valid checkouts in newRepository
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
|
||||
RepositoryAdapter newRepository, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
for (DomainFile df : checkoutList) {
|
||||
monitor.checkCanceled();
|
||||
if (hasInvalidCheckout(df, newRepository)) {
|
||||
return true;
|
||||
}
|
||||
findCheckedOutFiles(folder2, monitor);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find those domain files listed which do not correspond to checkouts in the specified
|
||||
* newRespository.
|
||||
* @param checkoutList project domain files to check
|
||||
* @param newRepository repository to check against before updating
|
||||
* @param monitor task monitor
|
||||
* @return list of domain files not checked-out in repo
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
private List<DomainFile> findInvalidCheckouts(List<DomainFile> checkoutList,
|
||||
RepositoryAdapter newRepository, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
List<DomainFile> list = new ArrayList<>();
|
||||
for (DomainFile df : checkoutList) {
|
||||
monitor.checkCanceled();
|
||||
if (hasInvalidCheckout(df, newRepository)) {
|
||||
list.add(df);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo checkouts for all domain files listed.
|
||||
* @param files list of files to undo checkout
|
||||
* @param keep if a .keep copy of any checked-out file should be retained in the local file.
|
||||
* @param force if not connected to the repository the local checkout file will be removed.
|
||||
* Warning: forcing undo checkout will leave a stale checkout in place for the associated
|
||||
* repository if not connected.
|
||||
* @param monitor task monitor
|
||||
* @throws IOException if an IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
private void undoCheckouts(List<DomainFile> files, boolean keep, boolean force,
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
for (DomainFile df : files) {
|
||||
monitor.checkCanceled();
|
||||
if (df.isCheckedOut()) {
|
||||
df.undoCheckout(keep, force);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all project files which are currently checked-out
|
||||
* @param monitor task monitor (no progress updates)
|
||||
* @return list of current checkout files
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
List<DomainFile> list = new ArrayList<>();
|
||||
findCheckedOutFiles("/", list, monitor);
|
||||
return list;
|
||||
}
|
||||
|
||||
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
for (String name : fileSystem.getItemNames(folderPath)) {
|
||||
monitor.checkCanceled();
|
||||
LocalFolderItem item = fileSystem.getItem(folderPath, name);
|
||||
if (item.getCheckoutId() != FolderItem.DEFAULT_CHECKOUT_ID) {
|
||||
checkoutList.add(new GhidraFile(getFolder(folderPath), name));
|
||||
}
|
||||
}
|
||||
|
||||
if (!folderPath.endsWith(FileSystem.SEPARATOR)) {
|
||||
folderPath += FileSystem.SEPARATOR;
|
||||
}
|
||||
|
||||
for (String subfolder : fileSystem.getFolderNames(folderPath)) {
|
||||
monitor.checkCanceled();
|
||||
findCheckedOutFiles(folderPath + subfolder, checkoutList, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ import java.awt.BorderLayout;
|
|||
import java.awt.FlowLayout;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
|
@ -33,6 +35,7 @@ import docking.wizard.WizardManager;
|
|||
import ghidra.app.util.GenericHelpTopics;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.data.ConvertFileSystem;
|
||||
import ghidra.framework.data.TransientDataManager;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.remote.User;
|
||||
|
@ -337,10 +340,17 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
}
|
||||
|
||||
private void updateSharedProjectInfo() {
|
||||
if (filesAreOpen()) {
|
||||
Msg.showInfo(getClass(), getComponent(), "Cannot Change Project Info with Open Files",
|
||||
"Before your project info can be updated, you must close\n" +
|
||||
"files in running tools and make sure you have no files\n" + "checked out.");
|
||||
int openCount = getOpenFileCount();
|
||||
if (openCount != 0) {
|
||||
Msg.showInfo(getClass(), getComponent(),
|
||||
"Cannot Change Project Info with Open Files",
|
||||
"Found " + openCount + " open project file(s).\n" +
|
||||
"Before your project info can be updated, you must\n" +
|
||||
"close all open project files and tools.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkToolsClose()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -356,8 +366,10 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
currentRepository.getName().equals(rep.getName())) {
|
||||
Msg.showInfo(getClass(), getComponent(), "No Changes Made",
|
||||
"No changes were made to the shared project information.");
|
||||
return;
|
||||
}
|
||||
else if (OptionDialog.showOptionDialog(getComponent(), "Update Shared Project Info",
|
||||
|
||||
if (OptionDialog.showOptionDialog(getComponent(), "Update Shared Project Info",
|
||||
"Are you sure you want to update your shared project information?", "Update",
|
||||
OptionDialog.QUESTION_MESSAGE) == OptionDialog.OPTION_ONE) {
|
||||
|
||||
|
@ -376,12 +388,29 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
|
||||
}
|
||||
|
||||
private boolean checkToolsClose() {
|
||||
PluginTool[] runningTools = project.getToolManager().getRunningTools();
|
||||
for (PluginTool runningTool : runningTools) {
|
||||
if (!runningTool.canClose(false)) {
|
||||
return false;
|
||||
}
|
||||
runningTool.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void convertToIndexedFilesystem() {
|
||||
if (filesAreOpen()) {
|
||||
int openCount = getOpenFileCount();
|
||||
if (openCount != 0) {
|
||||
Msg.showInfo(getClass(), getComponent(),
|
||||
"Cannot Convert/Upgrade Project Storage with Open Files",
|
||||
"Found " + openCount + " open project file(s).\n" +
|
||||
"Before your project can be converted, you must close\n" +
|
||||
"files in running tools.");
|
||||
"all open project files and tools.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkToolsClose()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -415,10 +444,18 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
}
|
||||
|
||||
private void convertToShared() {
|
||||
if (filesAreOpen()) {
|
||||
Msg.showInfo(getClass(), getComponent(), "Cannot Convert Project with Open Files",
|
||||
|
||||
int openCount = getOpenFileCount();
|
||||
if (openCount != 0) {
|
||||
Msg.showInfo(getClass(), getComponent(),
|
||||
"Cannot Convert Project with Open Files",
|
||||
"Found " + openCount + " open project file(s).\n" +
|
||||
"Before your project can be converted, you must close\n" +
|
||||
"files in running tools and make sure you have no files\n" + "checked out.");
|
||||
"all open project files and tools.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkToolsClose()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -430,7 +467,7 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
if (rep != null) {
|
||||
StringBuffer confirmMsg = new StringBuffer();
|
||||
confirmMsg.append("All version history on your files will be\n" +
|
||||
"lost after your project is converted.\n" +
|
||||
"lost after your project is converted and checkouts terminated.\n" +
|
||||
"Do you want to convert your project?\n");
|
||||
confirmMsg.append(" \n");
|
||||
confirmMsg.append("WARNING: Convert CANNOT be undone!");
|
||||
|
@ -442,11 +479,12 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
ConvertProjectTask task = new ConvertProjectTask(rep);
|
||||
new TaskLauncher(task, getComponent(), 500);
|
||||
// block until task completes
|
||||
ProjectLocator projectLocator = project.getProjectLocator();
|
||||
if (task.getStatus()) {
|
||||
close();
|
||||
FileActionManager actionMgr = plugin.getFileActionManager();
|
||||
actionMgr.closeProject(false);
|
||||
actionMgr.openProject(project.getProjectLocator());
|
||||
actionMgr.openProject(projectLocator);
|
||||
plugin.getProjectActionManager().showProjectInfo();
|
||||
}
|
||||
else {
|
||||
|
@ -456,36 +494,27 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean filesAreOpen() {
|
||||
PluginTool[] tools = project.getToolManager().getRunningTools();
|
||||
|
||||
if (tools.length > 0) {
|
||||
for (PluginTool tool : tools) {
|
||||
if (tool.getDomainFiles().length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
private int getOpenFileCount() {
|
||||
List<DomainFile> openFiles = new ArrayList<>();
|
||||
project.getProjectData().findOpenFiles(openFiles);
|
||||
TransientDataManager.getTransients(openFiles);
|
||||
return openFiles.size();
|
||||
}
|
||||
|
||||
private class ConvertProjectTask extends Task {
|
||||
private RepositoryAdapter taskRepository;
|
||||
private RepositoryAdapter newRepository;
|
||||
private boolean status;
|
||||
|
||||
ConvertProjectTask(RepositoryAdapter repository) {
|
||||
super("Convert Project to Shared", true, false, true);
|
||||
this.taskRepository = repository;
|
||||
this.newRepository = repository;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
|
||||
*/
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
try {
|
||||
project.getProjectData().convertProjectToShared(taskRepository, monitor);
|
||||
newRepository.connect();
|
||||
project.getProjectData().convertProjectToShared(newRepository, monitor);
|
||||
status = true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
@ -515,9 +544,6 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
this.projectLocator = projectLocator;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
|
||||
*/
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
try {
|
||||
|
@ -544,22 +570,20 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
}
|
||||
|
||||
private class UpdateInfoTask extends Task {
|
||||
private RepositoryAdapter taskRepository;
|
||||
private RepositoryAdapter newRepository;
|
||||
private boolean status;
|
||||
|
||||
UpdateInfoTask(RepositoryAdapter repository) {
|
||||
super("Update Shared Project Info", true, false, true);
|
||||
this.taskRepository = repository;
|
||||
this.newRepository = repository;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
|
||||
*/
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
try {
|
||||
// NOTE: conversion of non-shared project will lose version history
|
||||
project.getProjectData().updateRepositoryInfo(taskRepository, monitor);
|
||||
newRepository.connect();
|
||||
boolean force = useForcedCheckoutTransition(monitor);
|
||||
project.getProjectData().updateRepositoryInfo(newRepository, force, monitor);
|
||||
status = true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
@ -575,6 +599,36 @@ public class ProjectInfoDialog extends DialogComponentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean useForcedCheckoutTransition(TaskMonitor monitor) throws CancelledException, IOException {
|
||||
if (repository == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectData projectData = project.getProjectData();
|
||||
List<DomainFile> checkoutFiles = projectData.findCheckedOutFiles(monitor);
|
||||
if (checkoutFiles.isEmpty() ||
|
||||
!projectData.hasInvalidCheckouts(checkoutFiles, newRepository, monitor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OptionDialog.showOptionDialog(getComponent(), "Terminate Unrecognized Checkouts",
|
||||
"One or more project file checkouts are not recognized by the selected repository.\n" +
|
||||
"These checkouts will be terminated and a local .keep file created." +
|
||||
(repository.isConnected() ? ""
|
||||
: " Doing this\n" +
|
||||
"will abandon such checkouts on the old repository since you are not connected.") +
|
||||
"\n\n" +
|
||||
"Are you sure you want to continue changing your shared project information?",
|
||||
"Terminate Checkouts and Continue",
|
||||
OptionDialog.QUESTION_MESSAGE) != OptionDialog.OPTION_ONE) {
|
||||
|
||||
throw new CancelledException();
|
||||
}
|
||||
|
||||
// Must force termination if not connected to current repository
|
||||
return !repository.isConnected();
|
||||
}
|
||||
|
||||
boolean getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.Map;
|
|||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.framework.client.NotConnectedException;
|
||||
import ghidra.framework.data.CheckinHandler;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.util.InvalidNameException;
|
||||
|
@ -403,11 +404,26 @@ public interface DomainFile extends Comparable<DomainFile> {
|
|||
* Undo "checked-out" file. The original repository file is restored.
|
||||
* @param keep if true, the private database will be renamed with a .keep
|
||||
* extension.
|
||||
* @throws NotConnectedException if shared project and not connected to repository
|
||||
* @throws FileInUseException if this file is in-use / checked-out.
|
||||
* @throws IOException thrown if file is not checked-out or an IO / access error occurs.
|
||||
*/
|
||||
public void undoCheckout(boolean keep) throws IOException;
|
||||
|
||||
/**
|
||||
* Undo "checked-out" file. The original repository file is restored.
|
||||
* @param keep if true, the private database will be renamed with a .keep
|
||||
* extension.
|
||||
* @param force if not connected to the repository the local checkout file will be removed.
|
||||
* Warning: forcing undo checkout will leave a stale checkout in place for the associated
|
||||
* repository if not connected.
|
||||
* @throws NotConnectedException if shared project and not connected to repository and
|
||||
* force is false
|
||||
* @throws FileInUseException if this file is in-use / checked-out.
|
||||
* @throws IOException thrown if file is not checked-out or an IO / access error occurs.
|
||||
*/
|
||||
public void undoCheckout(boolean keep, boolean force) throws IOException;
|
||||
|
||||
/**
|
||||
* Forcefully terminate a checkout for the associated versioned file.
|
||||
* The user must be the owner of the checkout or have administrator privilege
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
*/
|
||||
package ghidra.framework.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
|
@ -22,10 +26,6 @@ import ghidra.util.InvalidNameException;
|
|||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The ProjectData interface provides access to all the data files and folders
|
||||
* in a project.
|
||||
|
@ -79,6 +79,30 @@ public interface ProjectData {
|
|||
*/
|
||||
public void findOpenFiles(List<DomainFile> list);
|
||||
|
||||
/**
|
||||
* Find all project files which are currently checked-out to this project
|
||||
* @param monitor task monitor (no progress updates)
|
||||
* @return list of current checkout files
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
|
||||
throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Determine if any domain files listed do not correspond to a checkout in the specified
|
||||
* newRespository prior to invoking {@link #updateRepositoryInfo(RepositoryAdapter, boolean, TaskMonitor)}.
|
||||
* @param checkoutList project domain files to check
|
||||
* @param newRepository repository to check against before updating
|
||||
* @param monitor task monitor
|
||||
* @return true if one or more files are not valid checkouts in newRepository
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
|
||||
RepositoryAdapter newRepository, TaskMonitor monitor)
|
||||
throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Get domain file specified by its unique fileID.
|
||||
* @param fileID domain file ID
|
||||
|
@ -157,16 +181,19 @@ public interface ProjectData {
|
|||
|
||||
/**
|
||||
* Update the repository for this project; the server may have changed or a different
|
||||
* repository is being used. NOTE: The project should be closed and then reopened after this
|
||||
* method is called.
|
||||
* @param repository new repository to use
|
||||
* repository is being used. Any existing checkout which is not recognized/valid by
|
||||
* newRepository will be terminated and a local .keep file created.
|
||||
* NOTE: The project should be closed and then reopened after this method is called.
|
||||
* @param newRepository new repository to use
|
||||
* @param force if true any existing local checkout which is not recognized/valid
|
||||
* for newRepository will be forceably terminated if offline with old repository.
|
||||
* @param monitor task monitor
|
||||
* @throws IOException thrown if files are still checked out, or if there was a problem accessing
|
||||
* the filesystem
|
||||
* @throws CancelledException if the user canceled the update
|
||||
*/
|
||||
public void updateRepositoryInfo(RepositoryAdapter repository, TaskMonitor monitor)
|
||||
throws IOException, CancelledException;
|
||||
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
|
||||
TaskMonitor monitor) throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Close the project storage associated with this project data object.
|
||||
|
|
|
@ -1207,7 +1207,7 @@ public abstract class PluginTool extends AbstractDockingTool {
|
|||
else {
|
||||
beep();
|
||||
Msg.showInfo(getClass(), getToolFrame(), "Tool Busy",
|
||||
"You must stop all background tasks before exiting.");
|
||||
"You must stop all background tasks before tool may close.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -287,6 +287,11 @@ public class TestDummyDomainFile implements DomainFile {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoCheckout(boolean keep, boolean force) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminateCheckout(long checkoutId) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
@ -63,6 +63,21 @@ public class TestDummyProjectData implements ProjectData {
|
|||
// stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
// stub
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
|
||||
RepositoryAdapter newRepository, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
// stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainFile getFileByID(String fileID) {
|
||||
// stub
|
||||
|
@ -121,8 +136,8 @@ public class TestDummyProjectData implements ProjectData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void updateRepositoryInfo(RepositoryAdapter repository, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
public void updateRepositoryInfo(RepositoryAdapter repository, boolean force,
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
// stub
|
||||
}
|
||||
|
||||
|
|
|
@ -329,7 +329,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
FrontEndPlugin plugin = getPlugin(tool, FrontEndPlugin.class);
|
||||
JComponent projectDataPanel = (JComponent) getInstanceField("projectDataPanel", plugin);
|
||||
JTabbedPane tabbedPane =
|
||||
(JTabbedPane) getInstanceField("projectTabPanel", projectDataPanel);
|
||||
(JTabbedPane) getInstanceField("projectTab", projectDataPanel);
|
||||
tabbedPane.setSelectedIndex(1);
|
||||
setToolSize(800, 600);
|
||||
captureComponent(projectDataPanel);
|
||||
|
|
|
@ -25,7 +25,6 @@ import javax.swing.*;
|
|||
import org.junit.*;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
import docking.AbstractErrDialog;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.wizard.WizardManager;
|
||||
|
@ -278,7 +277,8 @@ public class ProjectInfoDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
waitForTasks();
|
||||
|
||||
// check out file from shared project
|
||||
rootFolder = getProject().getProjectData().getRootFolder();
|
||||
Project oldProject = getProject();
|
||||
rootFolder = oldProject.getProjectData().getRootFolder();
|
||||
DomainFile df = rootFolder.getFile("testA");
|
||||
df.addToVersionControl("test", true, TaskMonitor.DUMMY);
|
||||
assertTrue(df.isCheckedOut());
|
||||
|
@ -299,11 +299,34 @@ public class ProjectInfoDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
assertNotNull(opt);
|
||||
assertEquals("Update Shared Project Info", opt.getTitle());
|
||||
pressButtonByText(opt, "Update");
|
||||
|
||||
opt = waitForDialogComponent(OptionDialog.class);
|
||||
assertNotNull(opt);
|
||||
assertEquals("Terminate Unrecognized Checkouts", opt.getTitle());
|
||||
pressButtonByText(opt, "Terminate Checkouts and Continue");
|
||||
waitForTasks();
|
||||
|
||||
AbstractErrDialog errorDialog = waitForErrorDialog();
|
||||
assertEquals("Failed to Update Shared Project Info", errorDialog.getTitle());
|
||||
close(errorDialog);
|
||||
dialog = waitForDialogComponent(ProjectInfoDialog.class);
|
||||
assertNotNull(dialog);
|
||||
pressButtonByText(dialog, "Dismiss");
|
||||
|
||||
Project updatedProject = getProject();
|
||||
assertNotNull(updatedProject);
|
||||
assertTrue(updatedProject != oldProject);
|
||||
|
||||
RepositoryAdapter rep = updatedProject.getRepository();
|
||||
assertNotNull(rep);
|
||||
assertEquals("AnotherRepository", rep.getName());
|
||||
|
||||
ProjectData updatedProjectData = updatedProject.getProjectData();
|
||||
|
||||
rootFolder = updatedProjectData.getRootFolder();
|
||||
assertNull(rootFolder.getFile("testA"));
|
||||
df = rootFolder.getFile("testA.keep");
|
||||
assertNotNull(df);
|
||||
assertFalse(df.isVersioned());
|
||||
assertFalse(df.isCheckedOut());
|
||||
|
||||
}
|
||||
|
||||
private void checkProjectInfo(String expectedRepName) {
|
||||
|
|
Loading…
Reference in a new issue