2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common
8 * Development and Distribution License("CDDL") (collectively, the
9 * "License"). You may not use this file except in compliance with the
10 * License. You can obtain a copy of the License at
11 * http://www.netbeans.org/cddl-gplv2.html
12 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13 * specific language governing permissions and limitations under the
14 * License. When distributing the software, include this License Header
15 * Notice in each file and include the License file at
16 * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17 * particular file as subject to the "Classpath" exception as provided
18 * by Sun in the GPL Version 2 section of the License file that
19 * accompanied this code. If applicable, add the following below the
20 * License Header, with the fields enclosed by brackets [] replaced by
21 * your own identifying information:
22 * "Portions Copyrighted [year] [name of copyright owner]"
26 * The Original Software is NetBeans. The Initial Developer of the Original
27 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28 * Microsystems, Inc. All Rights Reserved.
29 * Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions).
31 * If you wish your version of this file to be governed by only the CDDL
32 * or only the GPL Version 2, indicate your decision by adding
33 * "[Contributor] elects to include this software in this distribution
34 * under the [CDDL or GPL Version 2] license." If you do not indicate a
35 * single choice of license, a recipient has the option to distribute
36 * your version of this file under either the CDDL, the GPL Version 2 or
37 * to extend the choice of license to its licensees as provided above.
38 * However, if you add GPL Version 2 code and therefore, elected the GPL
39 * Version 2 license, then the option applies only if the new code is
40 * made subject to such option by the copyright holder.
45 import java
.io
.IOException
;
46 import java
.util
.Collection
;
48 import java
.util
.concurrent
.ConcurrentHashMap
;
49 import java
.util
.concurrent
.ConcurrentLinkedQueue
;
50 import java
.util
.logging
.Level
;
51 import javax
.swing
.SwingUtilities
;
52 import org
.nbgit
.util
.GitCommand
;
53 import org
.nbgit
.util
.exclude
.Excludes
;
54 import org
.nbgit
.util
.GitUtils
;
55 import org
.netbeans
.modules
.versioning
.spi
.VCSInterceptor
;
56 import org
.netbeans
.modules
.versioning
.util
.Utils
;
57 import org
.openide
.util
.RequestProcessor
;
60 * Listens on file system changes and reacts appropriately, mainly refreshing affected files' status.
62 * @author Maros Sandor
64 public class GitInterceptor
extends VCSInterceptor
{
66 private final StatusCache cache
;
67 private ConcurrentHashMap
<File
, File
> dirsToDelete
= new ConcurrentHashMap
<File
, File
>();
68 private ConcurrentLinkedQueue
<File
> filesToRefresh
= new ConcurrentLinkedQueue
<File
>();
69 private RequestProcessor
.Task refreshTask
;
70 private static final RequestProcessor refresh
= new RequestProcessor("GitRefresh", 1, true);
72 public GitInterceptor() {
73 cache
= Git
.getInstance().getStatusCache();
74 refreshTask
= refresh
.create(new RefreshTask());
78 public boolean beforeDelete(File file
) {
82 if (GitUtils
.isPartOfGitMetadata(file
)) {
83 return false; // We track the deletion of top level directories
85 if (file
.isDirectory()) {
86 for (File dir
: dirsToDelete
.keySet()) {
87 if (file
.equals(dir
.getParentFile())) {
88 dirsToDelete
.remove(dir
);
91 if (Excludes
.isSharable(file
)) {
92 dirsToDelete
.put(file
, file
);
99 public void doDelete(File file
) throws IOException
{
103 public void afterDelete(final File file
) {
104 Utils
.post(new Runnable() {
107 fileDeletedImpl(file
);
112 private void fileDeletedImpl(final File file
) {
113 if (file
== null || !file
.exists()) {
116 Git git
= Git
.getInstance();
117 final File root
= git
.getTopmostManagedParent(file
);
118 RequestProcessor rp
= null;
120 rp
= git
.getRequestProcessor(root
.getAbsolutePath());
122 if (file
.isDirectory()) {
124 if (!dirsToDelete
.remove(file
, file
)) {
130 GitProgressSupport support
= new GitProgressSupport() {
132 public void perform() {
133 GitCommand
.doRemove(root
, file
, this.getLogger());
134 // We need to cache the status of all deleted files
135 Map
<File
, StatusInfo
> interestingFiles
= GitCommand
.getInterestingStatus(root
, file
);
136 if (!interestingFiles
.isEmpty()) {
137 Collection
<File
> files
= interestingFiles
.keySet();
139 Map
<File
, Map
<File
, StatusInfo
>> interestingDirs
=
140 GitUtils
.getInterestingDirs(interestingFiles
, files
);
142 for (File tmpFile
: files
) {
143 if (this.isCanceled()) {
146 StatusInfo fi
= interestingFiles
.get(tmpFile
);
148 cache
.refreshFileStatus(tmpFile
, fi
,
149 interestingDirs
.get(tmpFile
.isDirectory() ? tmpFile
: tmpFile
.getParentFile()), true);
155 support
.start(rp
, root
.getAbsolutePath(),
156 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Remove_Progress")); // NOI18N
158 // If we are deleting a parent directory of this file
159 // skip the call to git remove as we will do it for the directory
164 for (File dir
: dirsToDelete
.keySet()) {
165 File tmpFile
= file
.getParentFile();
166 while (tmpFile
!= null) {
167 if (tmpFile
.equals(dir
)) {
170 tmpFile
= tmpFile
.getParentFile();
173 GitProgressSupport support
= new GitProgressSupport() {
175 public void perform() {
176 GitCommand
.doRemove(root
, file
, this.getLogger());
177 cache
.refresh(file
, StatusCache
.REPOSITORY_STATUS_UNKNOWN
);
180 support
.start(rp
, root
.getAbsolutePath(),
181 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Remove_Progress")); // NOI18N
186 public boolean beforeMove(File from
, File to
) {
187 if (from
== null || to
== null || to
.exists()) {
190 Git git
= Git
.getInstance();
191 if (git
.isManaged(from
)) {
192 return git
.isManaged(to
);
194 return super.beforeMove(from
, to
);
198 public void doMove(final File from
, final File to
) throws IOException
{
199 if (from
== null || to
== null || to
.exists()) {
202 if (SwingUtilities
.isEventDispatchThread()) {
204 Git
.LOG
.log(Level
.INFO
, "Warning: launching external process in AWT", new Exception().fillInStackTrace()); // NOI18N
205 final Throwable innerT
[] = new Throwable
[1];
206 Runnable outOfAwt
= new Runnable() {
210 gitMoveImplementation(from
, to
);
211 } catch (Throwable t
) {
217 Git
.getInstance().getRequestProcessor().post(outOfAwt
).waitFinished();
218 if (innerT
[0] != null) {
219 if (innerT
[0] instanceof IOException
) {
220 throw (IOException
) innerT
[0];
221 } else if (innerT
[0] instanceof RuntimeException
) {
222 throw (RuntimeException
) innerT
[0];
223 } else if (innerT
[0] instanceof Error
) {
224 throw (Error
) innerT
[0];
226 throw new IllegalStateException("Unexpected exception class: " + innerT
[0]); // end of hack
230 gitMoveImplementation(from
, to
);
234 private void gitMoveImplementation(final File srcFile
, final File dstFile
) throws IOException
{
235 final Git git
= Git
.getInstance();
236 final File root
= git
.getTopmostManagedParent(srcFile
);
240 RequestProcessor rp
= git
.getRequestProcessor(root
.getAbsolutePath());
242 Git
.LOG
.log(Level
.FINE
, "gitMoveImplementation(): File: {0} {1}", new Object
[]{srcFile
, dstFile
}); // NOI18N
244 srcFile
.renameTo(dstFile
);
245 Runnable moveImpl
= new Runnable() {
248 OutputLogger logger
= OutputLogger
.getLogger(root
.getAbsolutePath());
250 if (dstFile
.isDirectory())
251 throw new IllegalStateException("Rename of directory " + dstFile
);
252 int status
= GitCommand
.getSingleStatus(root
, srcFile
).getStatus();
253 Git
.LOG
.log(Level
.FINE
, "gitMoveImplementation(): Status: {0} {1}", new Object
[]{srcFile
, status
}); // NOI18N
254 if (status
== StatusInfo
.STATUS_NOTVERSIONED_NEWLOCALLY
||
255 status
== StatusInfo
.STATUS_NOTVERSIONED_EXCLUDED
) {
256 } else if (status
== StatusInfo
.STATUS_VERSIONED_ADDEDLOCALLY
) {
257 GitCommand
.doMove(root
, srcFile
, dstFile
, logger
);
259 throw new IllegalStateException("Rename with status " + status
);
261 } catch (Exception e
) {
262 Git
.LOG
.log(Level
.FINE
, "Git failed to rename: File: {0} {1}", new Object
[]{srcFile
.getAbsolutePath(), dstFile
.getAbsolutePath()}); // NOI18N
273 public void afterMove(final File from
, final File to
) {
274 Utils
.post(new Runnable() {
277 fileMovedImpl(from
, to
);
282 private void fileMovedImpl(final File from
, final File to
) {
283 if (from
== null || to
== null || !to
.exists()) {
286 if (to
.isDirectory()) {
289 Git git
= Git
.getInstance();
290 final File root
= git
.getTopmostManagedParent(from
);
294 RequestProcessor rp
= git
.getRequestProcessor(root
.getAbsolutePath());
296 GitProgressSupport supportCreate
= new GitProgressSupport() {
298 public void perform() {
299 cache
.refresh(from
, StatusCache
.REPOSITORY_STATUS_UNKNOWN
);
300 cache
.refresh(to
, StatusCache
.REPOSITORY_STATUS_UNKNOWN
);
304 supportCreate
.start(rp
, root
.getAbsolutePath(),
305 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Move_Progress")); // NOI18N
309 public boolean beforeCreate(File file
, boolean isDirectory
) {
310 return super.beforeCreate(file
, isDirectory
);
314 public void doCreate(File file
, boolean isDirectory
) throws IOException
{
315 super.doCreate(file
, isDirectory
);
319 public void afterCreate(final File file
) {
320 Utils
.post(new Runnable() {
323 fileCreatedImpl(file
);
328 private void fileCreatedImpl(final File file
) {
329 if (file
.isDirectory()) {
332 Git git
= Git
.getInstance();
333 final File root
= git
.getTopmostManagedParent(file
);
337 RequestProcessor rp
= git
.getRequestProcessor(root
.getAbsolutePath());
339 GitProgressSupport supportCreate
= new GitProgressSupport() {
341 public void perform() {
342 reScheduleRefresh(file
);
346 supportCreate
.start(rp
, root
.getAbsolutePath(),
347 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Create_Progress")); // NOI18N
351 public void afterChange(final File file
) {
352 Utils
.post(new Runnable() {
355 fileChangedImpl(file
);
360 private void fileChangedImpl(final File file
) {
361 if (file
.isDirectory()) {
364 Git git
= Git
.getInstance();
365 final File root
= git
.getTopmostManagedParent(file
);
369 RequestProcessor rp
= git
.getRequestProcessor(root
.getAbsolutePath());
371 GitProgressSupport supportCreate
= new GitProgressSupport() {
373 public void perform() {
374 Git
.LOG
.log(Level
.FINE
, "fileChangedImpl(): File: {0}", file
); // NOI18N
375 reScheduleRefresh(file
);
379 supportCreate
.start(rp
, root
.getAbsolutePath(),
380 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Change_Progress")); // NOI18N
383 private void reScheduleRefresh(File fileToRefresh
) {
384 // There is no point in refreshing the cache for ignored files.
385 if (Excludes
.isIgnored(fileToRefresh
, false)) {
388 if (!filesToRefresh
.contains(fileToRefresh
)) {
389 if (!filesToRefresh
.offer(fileToRefresh
)) {
390 Git
.LOG
.log(Level
.FINE
, "reScheduleRefresh failed to add to filesToRefresh queue {0}", fileToRefresh
);
393 refreshTask
.schedule(1000);
396 private class RefreshTask
implements Runnable
{
399 Thread
.interrupted();
400 File fileToRefresh
= filesToRefresh
.poll();
401 if (fileToRefresh
!= null) {
402 cache
.refresh(fileToRefresh
, StatusCache
.REPOSITORY_STATUS_UNKNOWN
);
403 fileToRefresh
= filesToRefresh
.peek();
404 if (fileToRefresh
!= null) {
405 refreshTask
.schedule(0);