diff --git a/git/nbproject/project.xml b/git/nbproject/project.xml --- a/git/nbproject/project.xml +++ b/git/nbproject/project.xml @@ -20,7 +20,7 @@ 1 - 1.0 + 1.5 diff --git a/git/src/org/netbeans/modules/git/FileStatusCache.java b/git/src/org/netbeans/modules/git/FileStatusCache.java --- a/git/src/org/netbeans/modules/git/FileStatusCache.java +++ b/git/src/org/netbeans/modules/git/FileStatusCache.java @@ -65,6 +65,7 @@ import org.netbeans.modules.versioning.spi.VCSContext; import org.netbeans.modules.versioning.spi.VersioningSupport; import org.netbeans.modules.git.FileInformation.Status; +import org.netbeans.modules.git.client.GitClient; import org.netbeans.modules.git.utils.GitUtils; import org.openide.filesystems.FileUtil; import org.openide.util.RequestProcessor; @@ -205,9 +206,11 @@ LOG.log(Level.FINE, "refreshAllRoots() roots: {0}, repositoryRoot: {1} ", new Object[] {refreshEntry.getValue(), repository.getAbsolutePath()}); // NOI18N } Map interestingFiles; + GitClient client = null; try { // find all files with not up-to-date or ignored status - interestingFiles = Git.getInstance().getClient(repository).getStatus(refreshEntry.getValue().toArray(new File[refreshEntry.getValue().size()]), pm); + client = Git.getInstance().getClient(repository); + interestingFiles = client.getStatus(refreshEntry.getValue().toArray(new File[refreshEntry.getValue().size()]), pm); if (pm.isCanceled()) { return; } @@ -258,6 +261,9 @@ } catch (GitException ex) { LOG.log(Level.INFO, "refreshAllRoots() file: {0} {1} {2} ", new Object[] {repository.getAbsolutePath(), refreshEntry.getValue(), ex.toString()}); //NOI18N } finally { + if (client != null) { + client.release(); + } if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "refreshAllRoots() roots: finished repositoryRoot: {0} ", new Object[] { repository.getAbsolutePath() } ); // NOI18N } diff --git a/git/src/org/netbeans/modules/git/FilesystemInterceptor.java b/git/src/org/netbeans/modules/git/FilesystemInterceptor.java --- a/git/src/org/netbeans/modules/git/FilesystemInterceptor.java +++ b/git/src/org/netbeans/modules/git/FilesystemInterceptor.java @@ -61,6 +61,7 @@ import org.netbeans.libs.git.GitException; import org.netbeans.libs.git.GitRemoteConfig; import org.netbeans.modules.git.FileInformation.Status; +import org.netbeans.modules.git.client.GitClient; import org.netbeans.modules.git.ui.history.SearchHistoryAction; import org.netbeans.modules.git.ui.repository.RepositoryInfo; import org.netbeans.modules.git.utils.GitUtils; @@ -122,10 +123,16 @@ Git git = Git.getInstance(); final File root = git.getRepositoryRoot(file); if (root == null) return false; + GitClient client = null; try { - git.getClient(root).reset(new File[] { file }, "HEAD", true, GitUtils.NULL_PROGRESS_MONITOR); + client = git.getClient(root); + client.reset(new File[] { file }, "HEAD", true, GitUtils.NULL_PROGRESS_MONITOR); } catch (GitException ex) { LOG.log(Level.INFO, "beforeCreate(): File: {0} {1}", new Object[] { file.getAbsolutePath(), ex.toString()}); //NOI18N + } finally { + if (client != null) { + client.release(); + } } LOG.log(Level.FINER, "beforeCreate(): finished: {0}", file); // NOI18N } @@ -158,9 +165,11 @@ if (file == null) return; Git git = Git.getInstance(); File root = git.getRepositoryRoot(file); + GitClient client = null; try { if (GitUtils.getGitFolderForRoot(root).exists()) { - git.getClient(root).remove(new File[] { file }, false, GitUtils.NULL_PROGRESS_MONITOR); + client = git.getClient(root); + client.remove(new File[] { file }, false, GitUtils.NULL_PROGRESS_MONITOR); } else if (file.exists()) { Utils.deleteRecursively(file); if (file.exists()) { @@ -178,6 +187,10 @@ Exceptions.attachLocalizedMessage(e, NbBundle.getMessage(FilesystemInterceptor.class, "MSG_DeleteFailed", new Object[] { file, e.getLocalizedMessage() })); //NOI18N ex.initCause(e); throw ex; + } finally { + if (client != null) { + client.release(); + } } } @@ -207,17 +220,19 @@ Git git = Git.getInstance(); File root = git.getRepositoryRoot(from); File dstRoot = git.getRepositoryRoot(to); + GitClient client = null; try { if (root != null && root.equals(dstRoot) && !cache.getStatus(to).containsStatus(Status.NOTVERSIONED_EXCLUDED)) { // target does not lie under ignored folder and is in the same repo as src + client = git.getClient(root); if (equalPathsIgnoreCase(from, to)) { // must do rename --after because the files/paths equal on Win or Mac if (!from.renameTo(to)) { throw new IOException(NbBundle.getMessage(FilesystemInterceptor.class, "MSG_MoveFailed", new Object[] { from, to, "" })); //NOI18N } - git.getClient(root).rename(from, to, true, GitUtils.NULL_PROGRESS_MONITOR); + client.rename(from, to, true, GitUtils.NULL_PROGRESS_MONITOR); } else { - git.getClient(root).rename(from, to, false, GitUtils.NULL_PROGRESS_MONITOR); + client.rename(from, to, false, GitUtils.NULL_PROGRESS_MONITOR); } } else { boolean result = from.renameTo(to); @@ -225,7 +240,8 @@ throw new IOException(NbBundle.getMessage(FilesystemInterceptor.class, "MSG_MoveFailed", new Object[] { from, to, "" })); //NOI18N } if (root != null) { - git.getClient(root).remove(new File[] { from }, true, GitUtils.NULL_PROGRESS_MONITOR); + client = git.getClient(root); + client.remove(new File[] { from }, true, GitUtils.NULL_PROGRESS_MONITOR); } } } catch (GitException e) { @@ -233,6 +249,10 @@ Exceptions.attachLocalizedMessage(e, NbBundle.getMessage(FilesystemInterceptor.class, "MSG_MoveFailed", new Object[] { from, to, e.getLocalizedMessage() })); //NOI18N ex.initCause(e); throw ex; + } finally { + if (client != null) { + client.release(); + } } } @@ -283,15 +303,21 @@ // target lies under ignored folder, do not add it return; } + GitClient client = null; try { if (root.equals(dstRoot)) { - git.getClient(root).copyAfter(from, to, GitUtils.NULL_PROGRESS_MONITOR); + client = git.getClient(root); + client.copyAfter(from, to, GitUtils.NULL_PROGRESS_MONITOR); } } catch (GitException e) { IOException ex = new IOException(); Exceptions.attachLocalizedMessage(e, NbBundle.getMessage(FilesystemInterceptor.class, "MSG_CopyFailed", new Object[] { from, to, e.getLocalizedMessage() })); //NOI18N ex.initCause(e); throw ex; + } finally { + if (client != null) { + client.release(); + } } } diff --git a/git/src/org/netbeans/modules/git/Git.java b/git/src/org/netbeans/modules/git/Git.java --- a/git/src/org/netbeans/modules/git/Git.java +++ b/git/src/org/netbeans/modules/git/Git.java @@ -145,8 +145,9 @@ void getOriginalFile (File workingCopy, File originalFile) { File repository = getRepositoryRoot(workingCopy); if (repository != null) { + GitClient client = null; try { - GitClient client = getClient(repository); + client = getClient(repository); FileOutputStream fos = new FileOutputStream(originalFile); boolean ok; try { @@ -168,6 +169,10 @@ originalFile.delete(); } catch (IOException ex) { LOG.log(Level.INFO, "IO exception", ex); //NOI18N + } finally { + if (client != null) { + client.release(); + } } } } diff --git a/git/src/org/netbeans/modules/git/HistoryProvider.java b/git/src/org/netbeans/modules/git/HistoryProvider.java --- a/git/src/org/netbeans/modules/git/HistoryProvider.java +++ b/git/src/org/netbeans/modules/git/HistoryProvider.java @@ -333,8 +333,9 @@ HistoryEntry ancestorEntry = commonAncestors.get(file); if (ancestorEntry == null && !commonAncestors.containsKey(file)) { GitRevisionInfo parent = null; + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); if (info.getParents().length == 1) { File historyFile = info.getModifiedFiles().containsKey(file) ? file @@ -347,6 +348,10 @@ } } catch (GitException ex) { LOG.log(Level.INFO, null, ex); + } finally { + if (client != null) { + client.release(); + } } ancestorEntry = parent == null ? null : createHistoryEntry(parent, files, repository); commonAncestors.put(file, ancestorEntry); diff --git a/git/src/org/netbeans/modules/git/HistoryRegistry.java b/git/src/org/netbeans/modules/git/HistoryRegistry.java --- a/git/src/org/netbeans/modules/git/HistoryRegistry.java +++ b/git/src/org/netbeans/modules/git/HistoryRegistry.java @@ -85,13 +85,19 @@ crit.setFiles(files); crit.setFollowRenames(true); crit.setIncludeMerges(false); - GitRevisionInfo[] history = client.log(crit, pm); - if (!pm.isCanceled() && history.length > 0) { - for (File f : files) { - logs.put(f, Arrays.asList(history)); + try { + GitRevisionInfo[] history = client.log(crit, pm); + if (!pm.isCanceled() && history.length > 0) { + for (File f : files) { + logs.put(f, Arrays.asList(history)); + } + } + return history; + } finally { + if (client != null) { + client.release(); } } - return history; } public File getHistoryFile(final File repository, final File originalFile, final String revision, final boolean dryTry) { @@ -150,13 +156,18 @@ if(changePaths == null && !dryTry) { long t1 = System.currentTimeMillis(); Map cps = null; + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); GitRevisionInfo lms = client.log(historyRevision, pm); assert lms != null; cps = lms.getModifiedFiles(); } catch (GitException ex) { LOG.log(Level.INFO, null, ex); + } finally { + if (client != null) { + client.release(); + } } if (cps == null) { changePaths = Collections.emptyList(); diff --git a/git/src/org/netbeans/modules/git/VersionsCache.java b/git/src/org/netbeans/modules/git/VersionsCache.java --- a/git/src/org/netbeans/modules/git/VersionsCache.java +++ b/git/src/org/netbeans/modules/git/VersionsCache.java @@ -84,8 +84,9 @@ } else { File tempFile = new File(Utils.getTempFolder(), "nb-git-" + base.getName()); //NOI18N tempFile.deleteOnExit(); + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); boolean result; FileOutputStream fos = new FileOutputStream(tempFile); try { @@ -109,6 +110,10 @@ tempFile = null; } catch (GitException ex) { throw new IOException(ex); + } finally { + if (client != null) { + client.release(); + } } return tempFile; } diff --git a/git/src/org/netbeans/modules/git/api/Git.java b/git/src/org/netbeans/modules/git/api/Git.java --- a/git/src/org/netbeans/modules/git/api/Git.java +++ b/git/src/org/netbeans/modules/git/api/Git.java @@ -95,8 +95,9 @@ } public static void initializeRepository (File localFolder, String repositoryUrl, PasswordAuthentication credentials) throws IOException, URISyntaxException { + GitClient client = null; try { - GitClient client = org.netbeans.modules.git.Git.getInstance().getClient(localFolder); + client = org.netbeans.modules.git.Git.getInstance().getClient(localFolder); client.init(GitUtils.NULL_PROGRESS_MONITOR); String remoteName = "origin"; //NOI18N client.setRemote(new GitRemoteConfig(remoteName, Arrays.asList(repositoryUrl), @@ -122,6 +123,9 @@ } catch (GitException ex) { throw new IOException(ex); } finally { + if (client != null) { + client.release(); + } org.netbeans.modules.git.Git.getInstance().clearAncestorCaches(); VersioningSupport.versionedRootsChanged(); } diff --git a/git/src/org/netbeans/modules/git/client/GitClient.java b/git/src/org/netbeans/modules/git/client/GitClient.java --- a/git/src/org/netbeans/modules/git/client/GitClient.java +++ b/git/src/org/netbeans/modules/git/client/GitClient.java @@ -46,8 +46,10 @@ import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; @@ -77,6 +79,7 @@ import org.netbeans.modules.versioning.util.IndexingBridge; import org.netbeans.modules.versioning.util.Utils; import org.openide.util.NetworkSettings; +import org.openide.util.RequestProcessor; /** * @@ -87,6 +90,9 @@ private final org.netbeans.libs.git.GitClient delegate; private final GitProgressSupport progressSupport; private final boolean handleAuthenticationIssues; + private static final int CLEANUP_TIME = 15000; + private static final List unusedClients = new LinkedList(); + private static RequestProcessor.Task cleanTask = Git.getInstance().getRequestProcessor().create(new CleanTask()); /** * Set of commands that do not need to run under repository lock @@ -194,6 +200,7 @@ )); private static final Logger LOG = Logger.getLogger(GitClient.class.getName()); private final File repositoryRoot; + private boolean released; public GitClient (File repository, GitProgressSupport progressSupport, boolean handleAuthenticationIssues) throws GitException { this.repositoryRoot = repository; @@ -586,6 +593,20 @@ } }, "push"); //NOI18N } + + /** + * Schedule cleanup of git repository used by this client + */ + public void release () { + synchronized (unusedClients) { + if (released) { + return; + } + unusedClients.add(delegate); + released = true; + } + cleanTask.schedule(CLEANUP_TIME); + } public void remove (final File[] roots, final boolean cached, final ProgressMonitor monitor) throws GitException { new CommandInvoker().runMethod(new Callable() { @@ -681,10 +702,29 @@ } }, "unignore"); //NOI18N } + + private static class CleanTask implements Runnable { + + @Override + public void run () { + Set toRelease; + synchronized (unusedClients) { + toRelease = new HashSet(unusedClients); + unusedClients.clear(); + } + for (org.netbeans.libs.git.GitClient unusuedClient : toRelease) { + unusuedClient.release(); + } + } + + } private final class CommandInvoker { private T runMethod (Callable callable, String methodName) throws GitException { + if (released) { + throw new IllegalStateException("Client already released."); + } return runMethod(callable, methodName, new File[0]); } @@ -796,4 +836,12 @@ private static boolean withoutAuthenticator (String commandName) { return NETWORK_COMMANDS.contains(commandName); } + + @Override + protected void finalize () throws Throwable { + if (!released) { + release(); + } + super.finalize(); + } } diff --git a/git/src/org/netbeans/modules/git/client/GitProgressSupport.java b/git/src/org/netbeans/modules/git/client/GitProgressSupport.java --- a/git/src/org/netbeans/modules/git/client/GitProgressSupport.java +++ b/git/src/org/netbeans/modules/git/client/GitProgressSupport.java @@ -105,6 +105,9 @@ LOG.log(Level.FINE, "End - {0}", originalDisplayName); //NOI18N finishProgress(); getLogger().closeLog(); + if (gitClient != null) { + gitClient.release(); + } } } diff --git a/git/src/org/netbeans/modules/git/ui/actions/SingleRepositoryAction.java b/git/src/org/netbeans/modules/git/ui/actions/SingleRepositoryAction.java --- a/git/src/org/netbeans/modules/git/ui/actions/SingleRepositoryAction.java +++ b/git/src/org/netbeans/modules/git/ui/actions/SingleRepositoryAction.java @@ -120,8 +120,9 @@ @Override public void run () { Set urls = new HashSet(); + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repositoryRoot); + client = Git.getInstance().getClient(repositoryRoot); Map cfgs = client.getRemotes(GitUtils.NULL_PROGRESS_MONITOR); for (Map.Entry e : cfgs.entrySet()) { GitRemoteConfig cfg = e.getValue(); @@ -133,6 +134,10 @@ } } catch (GitException ex) { // not interested + } finally { + if (client != null) { + client.release(); + } } for (String url : urls) { if (!url.trim().isEmpty()) { diff --git a/git/src/org/netbeans/modules/git/ui/branch/CreateBranch.java b/git/src/org/netbeans/modules/git/ui/branch/CreateBranch.java --- a/git/src/org/netbeans/modules/git/ui/branch/CreateBranch.java +++ b/git/src/org/netbeans/modules/git/ui/branch/CreateBranch.java @@ -168,8 +168,9 @@ @Override public void run () { final String branchName = CreateBranch.this.branchName; + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); final Map branches = client.getBranches(false, GitUtils.NULL_PROGRESS_MONITOR); EventQueue.invokeLater(new Runnable () { @Override @@ -182,6 +183,10 @@ }); } catch (GitException ex) { GitClientExceptionHandler.notifyException(ex, true); + } finally { + if (client != null) { + client.release(); + } } } } diff --git a/git/src/org/netbeans/modules/git/ui/clone/RepositoryStep.java b/git/src/org/netbeans/modules/git/ui/clone/RepositoryStep.java --- a/git/src/org/netbeans/modules/git/ui/clone/RepositoryStep.java +++ b/git/src/org/netbeans/modules/git/ui/clone/RepositoryStep.java @@ -189,8 +189,9 @@ @Override public void perform() { + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(getRepositoryRoot(), this, false); + client = Git.getInstance().getClient(getRepositoryRoot(), this, false); client.init(getProgressMonitor()); branches = new HashMap(); branches.putAll(client.listRemoteBranches(uri.toPrivateString(), getProgressMonitor())); @@ -204,6 +205,9 @@ message = new Message(str, false); setValid(false, message); } finally { + if (client != null) { + client.release(); + } Utils.deleteRecursively(getRepositoryRoot()); if (message == null && isCanceled()) { message = new Message(NbBundle.getMessage(RepositoryStep.class, "MSG_RepositoryStep.validationCanceled"), true); //NOI18N diff --git a/git/src/org/netbeans/modules/git/ui/commit/CommitAction.java b/git/src/org/netbeans/modules/git/ui/commit/CommitAction.java --- a/git/src/org/netbeans/modules/git/ui/commit/CommitAction.java +++ b/git/src/org/netbeans/modules/git/ui/commit/CommitAction.java @@ -114,12 +114,17 @@ public void run() { GitUser user = null; + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); user = client.getUser(); } catch (GitException ex) { GitClientExceptionHandler.notifyException(ex, true); return; + } finally { + if (client != null) { + client.release(); + } } GitCommitPanel panel = state == GitRepositoryState.MERGING_RESOLVED @@ -326,11 +331,16 @@ commitPermitted = false; Map conflicts = Collections.emptyMap(); if (state.equals(GitRepositoryState.MERGING)) { + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); conflicts = client.getConflicts(new File[] { repository }, GitUtils.NULL_PROGRESS_MONITOR); } catch (GitException ex) { LOG.log(Level.INFO, null, ex); + } finally { + if (client != null) { + client.release(); + } } } NotifyDescriptor nd; diff --git a/git/src/org/netbeans/modules/git/ui/commit/DeleteLocalAction.java b/git/src/org/netbeans/modules/git/ui/commit/DeleteLocalAction.java --- a/git/src/org/netbeans/modules/git/ui/commit/DeleteLocalAction.java +++ b/git/src/org/netbeans/modules/git/ui/commit/DeleteLocalAction.java @@ -114,14 +114,19 @@ }; for (Map.Entry> e : sortedFiles.entrySet()) { File root = e.getKey(); + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(root); + client = Git.getInstance().getClient(root); client.addNotificationListener(list); File[] roots = e.getValue().toArray(new File[e.getValue().size()]); client.reset(roots, GitUtils.HEAD, false, getProgressMonitor()); client.clean(roots, getProgressMonitor()); } catch (GitException ex) { LOG.log(Level.INFO, null, ex); + } finally { + if (client != null) { + client.release(); + } } } } diff --git a/git/src/org/netbeans/modules/git/ui/ignore/IgnoreAction.java b/git/src/org/netbeans/modules/git/ui/ignore/IgnoreAction.java --- a/git/src/org/netbeans/modules/git/ui/ignore/IgnoreAction.java +++ b/git/src/org/netbeans/modules/git/ui/ignore/IgnoreAction.java @@ -174,12 +174,17 @@ private File[] filterFolders (File repository, File[] roots) { List unignoredFolders = new LinkedList(); Map statuses; + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); statuses = client.getStatus(roots, GitUtils.NULL_PROGRESS_MONITOR); } catch (GitException ex) { LOG.log(Level.INFO, null, ex); statuses = Collections.emptyMap(); + } finally { + if (client != null) { + client.release(); + } } for (File f : roots) { GitStatus st = statuses.get(f); diff --git a/git/src/org/netbeans/modules/git/ui/push/PushAction.java b/git/src/org/netbeans/modules/git/ui/push/PushAction.java --- a/git/src/org/netbeans/modules/git/ui/push/PushAction.java +++ b/git/src/org/netbeans/modules/git/ui/push/PushAction.java @@ -193,15 +193,21 @@ List revisionList = new LinkedList(); Set visitedRevisions = new HashSet(); GitClient client = Git.getInstance().getClient(getRepositoryRoot()); // do not use progresssupport's client, that one logs into output - for (PushMapping mapping : pushMappings) { - if (mapping instanceof PushMapping.PushBranchMapping) { - PushMapping.PushBranchMapping branchMapping = (PushMapping.PushBranchMapping) mapping; - String remoteRevisionId = branchMapping.getRemoteRepositoryBranchHeadId(); - String localRevisionId = branchMapping.getLocalRepositoryBranchHeadId(); - revisionList.addAll(addRevisions(client, visitedRevisions, remoteRevisionId, localRevisionId)); + try { + for (PushMapping mapping : pushMappings) { + if (mapping instanceof PushMapping.PushBranchMapping) { + PushMapping.PushBranchMapping branchMapping = (PushMapping.PushBranchMapping) mapping; + String remoteRevisionId = branchMapping.getRemoteRepositoryBranchHeadId(); + String localRevisionId = branchMapping.getLocalRepositoryBranchHeadId(); + revisionList.addAll(addRevisions(client, visitedRevisions, remoteRevisionId, localRevisionId)); + } + if (isCanceled()) { + break; + } } - if (isCanceled()) { - break; + } finally { + if (client != null) { + client.release(); } } return revisionList; @@ -211,12 +217,18 @@ List revisionList = new LinkedList(); Set visitedRevisions = new HashSet(); GitClient client = Git.getInstance().getClient(getRepositoryRoot()); // do not use progresssupport's client, that one logs into output - for (Map.Entry update : remoteRepositoryUpdates.entrySet()) { - String remoteRevisionId = update.getValue().getOldObjectId(); - String localRevisionId = update.getValue().getNewObjectId(); - revisionList.addAll(addRevisions(client, visitedRevisions, remoteRevisionId, localRevisionId)); - if (isCanceled()) { - break; + try { + for (Map.Entry update : remoteRepositoryUpdates.entrySet()) { + String remoteRevisionId = update.getValue().getOldObjectId(); + String localRevisionId = update.getValue().getNewObjectId(); + revisionList.addAll(addRevisions(client, visitedRevisions, remoteRevisionId, localRevisionId)); + if (isCanceled()) { + break; + } + } + } finally { + if (client != null) { + client.release(); } } return revisionList; diff --git a/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java b/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java --- a/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java +++ b/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java @@ -948,8 +948,9 @@ @Override public void run () { String tt = null; + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); GitRevisionInfo info = client.getCommonAncestor(new String[] { id, trackedBranch.getId() }, GitUtils.NULL_PROGRESS_MONITOR); if (info == null || !(info.getRevision().equals(id) || info.getRevision().equals(trackedBranch.getId()))) { tt = NbBundle.getMessage(RepositoryBrowserPanel.class, "MSG_BranchNode.tracking.mergeNeeded", trackedBranch.getName()); //NOI18N @@ -976,6 +977,10 @@ } } catch (GitException ex) { LOG.log(Level.INFO, null, ex); + } finally { + if (client != null) { + client.release(); + } } final String toolTip = tt; EventQueue.invokeLater(new Runnable() { diff --git a/git/src/org/netbeans/modules/git/ui/repository/RepositoryInfo.java b/git/src/org/netbeans/modules/git/ui/repository/RepositoryInfo.java --- a/git/src/org/netbeans/modules/git/ui/repository/RepositoryInfo.java +++ b/git/src/org/netbeans/modules/git/ui/repository/RepositoryInfo.java @@ -160,12 +160,13 @@ public void refresh () { assert !java.awt.EventQueue.isDispatchThread(); File root = rootRef.get(); + GitClient client = null; try { if (root == null) { LOG.log(Level.WARNING, "refresh (): root is null, it has been collected in the meantime"); //NOI18N } else { LOG.log(Level.FINE, "refresh (): starting for {0}", root); //NOI18N - GitClient client = Git.getInstance().getClient(root); + client = Git.getInstance().getClient(root); // get all needed information at once before firing events. Thus we supress repeated annotations' refreshing Map newBranches = client.getBranches(true, GitUtils.NULL_PROGRESS_MONITOR); setBranches(newBranches); @@ -180,6 +181,10 @@ } catch (GitException ex) { Level level = root.exists() ? Level.INFO : Level.FINE; // do not polute the message log with messages concerning temporary or deleted repositories LOG.log(level, null, ex); + } finally { + if (client != null) { + client.release(); + } } } @@ -189,17 +194,22 @@ */ public void refreshRemotes () { assert !java.awt.EventQueue.isDispatchThread(); + GitClient client = null; try { File root = rootRef.get(); if (root == null) { LOG.log(Level.WARNING, "refreshRemotes (): root is null, it has been collected in the meantime"); //NOI18N } else { LOG.log(Level.FINE, "refreshRemotes (): starting for {0}", root); //NOI18N - GitClient client = Git.getInstance().getClient(root); + client = Git.getInstance().getClient(root); refreshRemotes(client); } } catch (GitException ex) { LOG.log(Level.INFO, null, ex); + } finally { + if (client != null) { + client.release(); + } } } diff --git a/git/src/org/netbeans/modules/git/ui/repository/RevisionInfoPanelController.java b/git/src/org/netbeans/modules/git/ui/repository/RevisionInfoPanelController.java --- a/git/src/org/netbeans/modules/git/ui/repository/RevisionInfoPanelController.java +++ b/git/src/org/netbeans/modules/git/ui/repository/RevisionInfoPanelController.java @@ -159,18 +159,23 @@ public void run () { final String revision = currentCommit; GitRevisionInfo revisionInfo; + GitClient client = null; try { monitor = new ProgressMonitor.DefaultProgressMonitor(); if (Thread.interrupted()) { return; } - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); revisionInfo = client.log(revision, monitor); } catch (GitException ex) { if (!(ex instanceof GitException.MissingObjectException)) { GitClientExceptionHandler.notifyException(ex, true); } revisionInfo = null; + } finally { + if (client != null) { + client.release(); + } } final GitRevisionInfo info = revisionInfo; final ProgressMonitor.DefaultProgressMonitor m = monitor; diff --git a/git/src/org/netbeans/modules/git/ui/repository/remote/SelectUriStep.java b/git/src/org/netbeans/modules/git/ui/repository/remote/SelectUriStep.java --- a/git/src/org/netbeans/modules/git/ui/repository/remote/SelectUriStep.java +++ b/git/src/org/netbeans/modules/git/ui/repository/remote/SelectUriStep.java @@ -240,8 +240,8 @@ @Override protected void perform () { String uri = getSelectedUri(); + GitClient client = null; try { - GitClient client; if (newRepositorySpecification) { repository.store(); client = Git.getInstance().getClient(getRepositoryRoot(), this, false); @@ -258,6 +258,10 @@ } Logger.getLogger(SelectUriStep.class.getName()).log(Level.INFO, "Cannot connect to " + uri, ex); //NOI18N message[0] = new Message(NbBundle.getMessage(SelectUriStep.class, "MSG_SelectUriStep.errorCannotConnect", uri), false); //NOI18N + } finally { + if (client != null) { + client.release(); + } } } diff --git a/git/src/org/netbeans/modules/git/ui/tag/CreateTag.java b/git/src/org/netbeans/modules/git/ui/tag/CreateTag.java --- a/git/src/org/netbeans/modules/git/ui/tag/CreateTag.java +++ b/git/src/org/netbeans/modules/git/ui/tag/CreateTag.java @@ -196,8 +196,9 @@ @Override public void run () { final String tagName = CreateTag.this.branchName; + GitClient client = null; try { - GitClient client = Git.getInstance().getClient(repository); + client = Git.getInstance().getClient(repository); final Map tags = client.getTags(GitUtils.NULL_PROGRESS_MONITOR, true); EventQueue.invokeLater(new Runnable () { @Override @@ -214,6 +215,10 @@ }); } catch (GitException ex) { GitClientExceptionHandler.notifyException(ex, true); + } finally { + if (client != null) { + client.release(); + } } } } diff --git a/libs.git/apichanges.xml b/libs.git/apichanges.xml --- a/libs.git/apichanges.xml +++ b/libs.git/apichanges.xml @@ -111,6 +111,23 @@ + Adding new method to GitClient: release() + + + + + + JGit repository normally does not close all opened file descriptors + and prevents external commands from working with the same repository. + Clients of the JGit API are supposed to call JGitRepository.close() + when they're done with the repository. We need to pass this method to the + Git Library API so when clients are done with calling all required commands + the library closes all descriptors and frees the git metadata files + for other tools. + + + + Initial version of the API diff --git a/libs.git/manifest.mf b/libs.git/manifest.mf --- a/libs.git/manifest.mf +++ b/libs.git/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.libs.git/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/libs/git/Bundle.properties -OpenIDE-Module-Specification-Version: 1.4 +OpenIDE-Module-Specification-Version: 1.5 diff --git a/libs.git/src/org/netbeans/libs/git/GitClient.java b/libs.git/src/org/netbeans/libs/git/GitClient.java --- a/libs.git/src/org/netbeans/libs/git/GitClient.java +++ b/libs.git/src/org/netbeans/libs/git/GitClient.java @@ -202,10 +202,11 @@ private final Set listeners; private JGitCredentialsProvider credentialsProvider; - GitClient (JGitRepository gitRepository) { + GitClient (JGitRepository gitRepository) throws GitException { this.gitRepository = gitRepository; listeners = new HashSet(); delegateListener = new DelegateListener(); + gitRepository.increaseClientUsage(); } /** @@ -755,6 +756,17 @@ cmd.execute(); return cmd.getResult(); } + + /** + * Marks this client as released and notifies the repository it does not + * have to stay open for this client. When all repository's clients are + * released the repository closes, flushes all cached metadata and closes + * all opened metadata files and file descriptors. + * @since 1.5 + */ + public void release () { + gitRepository.decreaseClientUsage(); + } /** * Removes given files/folders from the index and/or from the working tree diff --git a/libs.git/src/org/netbeans/libs/git/GitRepository.java b/libs.git/src/org/netbeans/libs/git/GitRepository.java --- a/libs.git/src/org/netbeans/libs/git/GitRepository.java +++ b/libs.git/src/org/netbeans/libs/git/GitRepository.java @@ -55,14 +55,20 @@ * still have to provide a local file which indicates where the repository would be created when * {@link GitClient#init(org.netbeans.libs.git.progress.ProgressMonitor) } was called.

*

To get an instance of GitClient to run git commands with, use {@link #createClient() } method. It always returns - * a new instance of the GitClient, it is not shared among the callers.

+ * a new instance of the GitClient, it is not shared among the callers.

+ *

When done with the client - you finish calling all desired commands and do not + * plan to use the client's instance any more - {@link GitClient#release() } must be called. + * When all created clients are released this way, repository metadata are flushed, + * all open metadata files are closed and thus do not block any external tools + * (such as a commandline client).

*

Internally the class keeps a map of its instances that are cached under * a weak reference to the instance of the local file passed in the {@link #getInstance(java.io.File) } method. * Along with the instance it caches also all repository metadata (branches, index, references etc.) * needed to construct the client and operate with the actual Git repository.
* Every call to the getInstance method with the same instance of the file * will always return the same instance of GitRepository. It is up to a caller's - * responsibility to hold a strong reference to the file so all metadata are kept in memory and not garbage collected. + * responsibility to hold a strong reference to the file so a created client always works with + * the same instance of the git repository.

* * @author Ondra Vrabec */ @@ -119,7 +125,7 @@ } } - private GitClient createClient (JGitRepository repository) { + private GitClient createClient (JGitRepository repository) throws GitException { return new GitClient(repository); } diff --git a/libs.git/src/org/netbeans/libs/git/jgit/JGitRepository.java b/libs.git/src/org/netbeans/libs/git/jgit/JGitRepository.java --- a/libs.git/src/org/netbeans/libs/git/jgit/JGitRepository.java +++ b/libs.git/src/org/netbeans/libs/git/jgit/JGitRepository.java @@ -53,11 +53,23 @@ * @author ondra */ public final class JGitRepository { - private final Repository repository; - private boolean closed; + private Repository repository; + private final File location; - public JGitRepository (File location) throws GitException { - this.repository = getRepository(location); + public JGitRepository (File location) { + this.location = location; + } + + public synchronized void increaseClientUsage () throws GitException { + if (repository == null) { + repository = getRepository(location); + } else { + repository.incrementOpen(); + } + } + + public synchronized void decreaseClientUsage () { + repository.close(); } private Repository getRepository (File workDir) throws GitException { @@ -75,21 +87,7 @@ } public Repository getRepository () { + assert repository != null; return repository; } - - public synchronized void close () { - if (repository != null) { - if (!closed) { - repository.close(); - closed = true; - } - } - } - - @Override - protected void finalize () throws Throwable { - close(); - super.finalize(); - } } diff --git a/libs.git/test/unit/src/org/netbeans/libs/git/jgit/AbstractGitTestCase.java b/libs.git/test/unit/src/org/netbeans/libs/git/jgit/AbstractGitTestCase.java --- a/libs.git/test/unit/src/org/netbeans/libs/git/jgit/AbstractGitTestCase.java +++ b/libs.git/test/unit/src/org/netbeans/libs/git/jgit/AbstractGitTestCase.java @@ -180,10 +180,12 @@ if (createLocalClone()) { GitRepository fact = GitRepository.getInstance(wc); - fact.createClient().init(NULL_PROGRESS_MONITOR); + GitClient client = fact.createClient(); + client.init(NULL_PROGRESS_MONITOR); Field f = GitRepository.class.getDeclaredField("gitRepository"); f.setAccessible(true); localRepository = (JGitRepository) f.get(fact); + client.release(); } } diff --git a/libs.git/test/unit/src/org/netbeans/libs/git/jgit/factory/CreateClientTest.java b/libs.git/test/unit/src/org/netbeans/libs/git/jgit/factory/CreateClientTest.java --- a/libs.git/test/unit/src/org/netbeans/libs/git/jgit/factory/CreateClientTest.java +++ b/libs.git/test/unit/src/org/netbeans/libs/git/jgit/factory/CreateClientTest.java @@ -43,7 +43,9 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; @@ -126,4 +128,39 @@ assertStatus(statuses, subRepo, newFile, true, GitStatus.Status.STATUS_ADDED, GitStatus.Status.STATUS_NORMAL, GitStatus.Status.STATUS_ADDED, false); } + public void testClientRelease () throws Exception { + GitClient client1 = GitRepository.getInstance(workDir).createClient(); + Repository jgitRepo1 = getRepository(client1); + assertRepoClients(jgitRepo1, 1); + client1.release(); + assertRepoClients(jgitRepo1, 0); + + client1 = GitRepository.getInstance(workDir).createClient(); + assertEquals(jgitRepo1, getRepository(client1)); + assertRepoClients(jgitRepo1, 1); + // some commands + client1.getStatus(new File[] { workDir }, NULL_PROGRESS_MONITOR); + client1.add(new File[] { workDir }, NULL_PROGRESS_MONITOR); + + GitClient client2 = GitRepository.getInstance(workDir).createClient(); + assertEquals(jgitRepo1, getRepository(client2)); + assertRepoClients(jgitRepo1, 2); + // some commands + client2.getStatus(new File[] { workDir }, NULL_PROGRESS_MONITOR); + client2.add(new File[] { workDir }, NULL_PROGRESS_MONITOR); + + assertRepoClients(jgitRepo1, 2); + client1.release(); + assertRepoClients(jgitRepo1, 1); + client2.release(); + assertRepoClients(jgitRepo1, 0); + } + + private void assertRepoClients (Repository jgitRepo1, int expectedClients) throws Exception { + Field f = Repository.class.getDeclaredField("useCnt"); + f.setAccessible(true); + AtomicInteger cnt = (AtomicInteger) f.get(jgitRepo1); + assertEquals(expectedClients, cnt.intValue()); + } + }