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
.GitIgnore
;
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()
74 cache
= Git
.getInstance().getStatusCache();
75 refreshTask
= refresh
.create(new RefreshTask());
79 public boolean beforeDelete(File file
)
83 if (GitUtils
.isPartOfGitMetadata(file
))
86 // We track the deletion of top level directories
87 if (file
.isDirectory()) {
88 for (File dir
: dirsToDelete
.keySet()) {
89 if (file
.equals(dir
.getParentFile()))
90 dirsToDelete
.remove(dir
);
92 if (GitIgnore
.isSharable(file
))
93 dirsToDelete
.put(file
, file
);
99 public void doDelete(File file
) throws IOException
104 public void afterDelete(final File file
)
106 Utils
.post(new Runnable() {
110 fileDeletedImpl(file
);
116 private void fileDeletedImpl(final File file
)
118 if (file
== null || !file
.exists())
121 Git git
= Git
.getInstance();
122 final File root
= git
.getTopmostManagedParent(file
);
123 RequestProcessor rp
= null;
125 rp
= git
.getRequestProcessor(root
.getAbsolutePath());
127 if (file
.isDirectory()) {
129 if (!dirsToDelete
.remove(file
, file
))
133 GitProgressSupport support
= new GitProgressSupport() {
135 public void perform()
137 GitCommand
.doRemove(root
, file
, this.getLogger());
138 // We need to cache the status of all deleted files
139 Map
<File
, StatusInfo
> interestingFiles
= GitCommand
.getInterestingStatus(root
, file
);
140 if (!interestingFiles
.isEmpty()) {
141 Collection
<File
> files
= interestingFiles
.keySet();
143 Map
<File
, Map
<File
, StatusInfo
>> interestingDirs
=
144 GitUtils
.getInterestingDirs(interestingFiles
, files
);
146 for (File tmpFile
: files
) {
147 if (this.isCanceled())
149 StatusInfo fi
= interestingFiles
.get(tmpFile
);
151 cache
.refreshFileStatus(tmpFile
, fi
,
152 interestingDirs
.get(tmpFile
.isDirectory() ? tmpFile
: tmpFile
.getParentFile()), true);
159 support
.start(rp
, root
.getAbsolutePath(),
160 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Remove_Progress")); // NOI18N
162 // If we are deleting a parent directory of this file
163 // skip the call to git remove as we will do it for the directory
167 for (File dir
: dirsToDelete
.keySet()) {
168 File tmpFile
= file
.getParentFile();
169 while (tmpFile
!= null) {
170 if (tmpFile
.equals(dir
))
172 tmpFile
= tmpFile
.getParentFile();
175 GitProgressSupport support
= new GitProgressSupport() {
177 public void perform()
179 GitCommand
.doRemove(root
, file
, this.getLogger());
180 cache
.refresh(file
, StatusCache
.REPOSITORY_STATUS_UNKNOWN
);
184 support
.start(rp
, root
.getAbsolutePath(),
185 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Remove_Progress")); // NOI18N
190 public boolean beforeMove(File from
, File to
)
192 if (from
== null || to
== null || to
.exists())
195 Git git
= Git
.getInstance();
196 if (git
.isManaged(from
))
197 return git
.isManaged(to
);
198 return super.beforeMove(from
, to
);
202 public void doMove(final File from
, final File to
) throws IOException
204 if (from
== null || to
== null || to
.exists())
207 if (SwingUtilities
.isEventDispatchThread()) {
209 Git
.LOG
.log(Level
.INFO
, "Warning: launching external process in AWT", new Exception().fillInStackTrace()); // NOI18N
210 final Throwable innerT
[] = new Throwable
[1];
211 Runnable outOfAwt
= new Runnable() {
216 gitMoveImplementation(from
, to
);
217 } catch (Throwable t
) {
224 Git
.getInstance().getRequestProcessor().post(outOfAwt
).waitFinished();
225 if (innerT
[0] != null)
226 if (innerT
[0] instanceof IOException
)
227 throw (IOException
) innerT
[0];
228 else if (innerT
[0] instanceof RuntimeException
)
229 throw (RuntimeException
) innerT
[0];
230 else if (innerT
[0] instanceof Error
)
231 throw (Error
) innerT
[0];
233 throw new IllegalStateException("Unexpected exception class: " + innerT
[0]);
238 gitMoveImplementation(from
, to
);
241 private void gitMoveImplementation(final File srcFile
, final File dstFile
) throws IOException
243 final Git git
= Git
.getInstance();
244 final File root
= git
.getTopmostManagedParent(srcFile
);
248 RequestProcessor rp
= git
.getRequestProcessor(root
.getAbsolutePath());
250 Git
.LOG
.log(Level
.FINE
, "gitMoveImplementation(): File: {0} {1}", new Object
[]{srcFile
, dstFile
}); // NOI18N
252 srcFile
.renameTo(dstFile
);
253 Runnable moveImpl
= new Runnable() {
257 OutputLogger logger
= OutputLogger
.getLogger(root
.getAbsolutePath());
259 if (dstFile
.isDirectory()) {
260 GitCommand
.doRenameAfter(root
, srcFile
, dstFile
, logger
);
263 int status
= GitCommand
.getSingleStatus(root
, srcFile
).getStatus();
264 Git
.LOG
.log(Level
.FINE
, "gitMoveImplementation(): Status: {0} {1}", new Object
[]{srcFile
, status
}); // NOI18N
265 if (status
== StatusInfo
.STATUS_NOTVERSIONED_NEWLOCALLY
||
266 status
== StatusInfo
.STATUS_NOTVERSIONED_EXCLUDED
) {
267 } else if (status
== StatusInfo
.STATUS_VERSIONED_ADDEDLOCALLY
) {
268 GitCommand
.doRemove(root
, srcFile
, logger
);
269 GitCommand
.doAdd(root
, dstFile
, logger
);
271 GitCommand
.doRenameAfter(root
, srcFile
, dstFile
, logger
);
272 } catch (Exception e
) {
273 Git
.LOG
.log(Level
.FINE
, "Git failed to rename: File: {0} {1}", new Object
[]{srcFile
.getAbsolutePath(), dstFile
.getAbsolutePath()}); // NOI18N
285 public void afterMove(final File from
, final File to
)
287 Utils
.post(new Runnable() {
291 fileMovedImpl(from
, to
);
297 private void fileMovedImpl(final File from
, final File to
)
299 if (from
== null || to
== null || !to
.exists())
301 if (to
.isDirectory())
303 Git git
= Git
.getInstance();
304 final File root
= git
.getTopmostManagedParent(from
);
308 RequestProcessor rp
= git
.getRequestProcessor(root
.getAbsolutePath());
310 GitProgressSupport supportCreate
= new GitProgressSupport() {
312 public void perform()
314 cache
.refresh(from
, StatusCache
.REPOSITORY_STATUS_UNKNOWN
);
315 cache
.refresh(to
, StatusCache
.REPOSITORY_STATUS_UNKNOWN
);
320 supportCreate
.start(rp
, root
.getAbsolutePath(),
321 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Move_Progress")); // NOI18N
325 public boolean beforeCreate(File file
, boolean isDirectory
)
327 return super.beforeCreate(file
, isDirectory
);
331 public void doCreate(File file
, boolean isDirectory
) throws IOException
333 super.doCreate(file
, isDirectory
);
337 public void afterCreate(final File file
)
339 Utils
.post(new Runnable() {
343 fileCreatedImpl(file
);
349 private void fileCreatedImpl(final File file
)
351 if (file
.isDirectory())
353 Git git
= Git
.getInstance();
354 final File root
= git
.getTopmostManagedParent(file
);
358 RequestProcessor rp
= git
.getRequestProcessor(root
.getAbsolutePath());
360 GitProgressSupport supportCreate
= new GitProgressSupport() {
362 public void perform()
364 reScheduleRefresh(file
);
369 supportCreate
.start(rp
, root
.getAbsolutePath(),
370 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Create_Progress")); // NOI18N
374 public void afterChange(final File file
)
376 Utils
.post(new Runnable() {
380 fileChangedImpl(file
);
386 private void fileChangedImpl(final File file
)
388 if (file
.isDirectory())
390 Git git
= Git
.getInstance();
391 final File root
= git
.getTopmostManagedParent(file
);
395 RequestProcessor rp
= git
.getRequestProcessor(root
.getAbsolutePath());
397 GitProgressSupport supportCreate
= new GitProgressSupport() {
399 public void perform()
401 Git
.LOG
.log(Level
.FINE
, "fileChangedImpl(): File: {0}", file
); // NOI18N
402 reScheduleRefresh(file
);
407 supportCreate
.start(rp
, root
.getAbsolutePath(),
408 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Change_Progress")); // NOI18N
411 private void reScheduleRefresh(File fileToRefresh
)
413 // There is no point in refreshing the cache for ignored files.
414 if (GitIgnore
.isIgnored(fileToRefresh
, false))
416 if (!filesToRefresh
.contains(fileToRefresh
))
417 if (!filesToRefresh
.offer(fileToRefresh
))
418 Git
.LOG
.log(Level
.FINE
, "reScheduleRefresh failed to add to filesToRefresh queue {0}", fileToRefresh
);
419 refreshTask
.schedule(1000);
422 private class RefreshTask
implements Runnable
{
426 Thread
.interrupted();
427 File fileToRefresh
= filesToRefresh
.poll();
428 if (fileToRefresh
!= null) {
429 cache
.refresh(fileToRefresh
, StatusCache
.REPOSITORY_STATUS_UNKNOWN
);
430 fileToRefresh
= filesToRefresh
.peek();
431 if (fileToRefresh
!= null)
432 refreshTask
.schedule(0);