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.
42 package org
.netbeans
.modules
.git
;
45 import java
.io
.IOException
;
46 import java
.util
.Calendar
;
47 import java
.util
.Collection
;
49 import java
.util
.concurrent
.ConcurrentHashMap
;
50 import java
.util
.concurrent
.ConcurrentLinkedQueue
;
51 import java
.util
.logging
.Level
;
52 import javax
.swing
.SwingUtilities
;
53 import org
.netbeans
.api
.queries
.SharabilityQuery
;
54 import org
.netbeans
.modules
.git
.util
.GitCommand
;
55 import org
.netbeans
.modules
.git
.util
.GitUtils
;
56 import org
.netbeans
.modules
.versioning
.spi
.VCSInterceptor
;
57 import org
.netbeans
.modules
.versioning
.util
.Utils
;
58 import org
.openide
.util
.RequestProcessor
;
61 * Listens on file system changes and reacts appropriately, mainly refreshing affected files' status.
63 * @author Maros Sandor
65 public class GitInterceptor
extends VCSInterceptor
{
67 private final FileStatusCache cache
;
69 private ConcurrentHashMap
<File
, File
> dirsToDelete
= new ConcurrentHashMap
<File
,File
>();
71 private ConcurrentLinkedQueue
<File
> filesToRefresh
= new ConcurrentLinkedQueue
<File
>();
73 private RequestProcessor
.Task refreshTask
;
75 private static final RequestProcessor rp
= new RequestProcessor("MercurialRefresh", 1, true);
77 public GitInterceptor() {
78 cache
= Git
.getInstance().getFileStatusCache();
79 refreshTask
= rp
.create(new RefreshTask());
83 public boolean beforeDelete(File file
) {
84 if (file
== null) return true;
85 if (GitUtils
.isPartOfMercurialMetadata(file
)) return false;
87 // We track the deletion of top level directories
88 if (file
.isDirectory()) {
89 for (File dir
: dirsToDelete
.keySet()) {
90 if (file
.equals(dir
.getParentFile())) {
91 dirsToDelete
.remove(dir
);
94 if (SharabilityQuery
.getSharability(file
) != SharabilityQuery
.NOT_SHARABLE
) {
95 dirsToDelete
.put(file
, file
);
102 public void doDelete(File file
) throws IOException
{
107 public void afterDelete(final File file
) {
108 Utils
.post(new Runnable() {
110 fileDeletedImpl(file
);
115 private void fileDeletedImpl(final File file
) {
116 if (file
== null) return;
117 Git hg
= Git
.getInstance();
118 final File root
= hg
.getTopmostManagedParent(file
);
119 RequestProcessor rp
= null;
121 rp
= hg
.getRequestProcessor(root
.getAbsolutePath());
124 if (file
.isDirectory()) {
126 if (!dirsToDelete
.remove(file
, file
)) return;
127 if (root
== null) return;
128 GitProgressSupport support
= new GitProgressSupport() {
129 public void perform() {
131 GitCommand
.doRemove(root
, file
, this.getLogger());
132 // We need to cache the status of all deleted files
133 Map
<File
, FileInformation
> interestingFiles
= GitCommand
.getInterestingStatus(root
, file
);
134 if (!interestingFiles
.isEmpty()){
135 Collection
<File
> files
= interestingFiles
.keySet();
137 Map
<File
, Map
<File
,FileInformation
>> interestingDirs
=
138 GitUtils
.getInterestingDirs(interestingFiles
, files
);
140 Calendar start
= Calendar
.getInstance();
141 for (File tmpFile
: files
) {
142 if(this.isCanceled()) {
145 FileInformation fi
= interestingFiles
.get(tmpFile
);
147 cache
.refreshFileStatus(tmpFile
, fi
,
148 interestingDirs
.get(tmpFile
.isDirectory()? tmpFile
: tmpFile
.getParentFile()), true);
150 Calendar end
= Calendar
.getInstance();
152 } catch (GitException ex
) {
153 Git
.LOG
.log(Level
.FINE
, "fileDeletedImpl(): File: {0} {1}", new Object
[] {file
.getAbsolutePath(), ex
.toString()}); // NOI18N
158 support
.start(rp
, root
.getAbsolutePath(),
159 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Remove_Progress")); // NOI18N
161 // If we are deleting a parent directory of this file
162 // skip the call to hg remove as we will do it for the directory
164 if (root
== null) return;
165 for (File dir
: dirsToDelete
.keySet()) {
166 File tmpFile
= file
.getParentFile();
167 while (tmpFile
!= null) {
168 if (tmpFile
.equals(dir
)) return;
169 tmpFile
= tmpFile
.getParentFile();
172 GitProgressSupport support
= new GitProgressSupport() {
173 public void perform() {
175 GitCommand
.doRemove(root
, file
, this.getLogger());
176 cache
.refresh(file
, FileStatusCache
.REPOSITORY_STATUS_UNKNOWN
);
177 } catch (GitException ex
) {
178 Git
.LOG
.log(Level
.FINE
, "fileDeletedImpl(): File: {0} {1}", new Object
[] {file
.getAbsolutePath(), ex
.toString()}); // NOI18N
182 support
.start(rp
, root
.getAbsolutePath(),
183 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Remove_Progress")); // NOI18N
189 public boolean beforeMove(File from
, File to
) {
190 if (from
== null || to
== null || to
.exists()) return true;
192 Git hg
= Git
.getInstance();
193 if (hg
.isManaged(from
)) {
194 return hg
.isManaged(to
);
196 return super.beforeMove(from
, to
);
200 public void doMove(final File from
, final File to
) throws IOException
{
201 if (from
== null || to
== null || to
.exists()) return;
203 if (SwingUtilities
.isEventDispatchThread()) {
205 Git
.LOG
.log(Level
.INFO
, "Warning: launching external process in AWT", new Exception().fillInStackTrace()); // NOI18N
206 final Throwable innerT
[] = new Throwable
[1];
207 Runnable outOfAwt
= new Runnable() {
210 hgMoveImplementation(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]); // NOI18N
233 hgMoveImplementation(from
, to
);
237 private void hgMoveImplementation(final File srcFile
, final File dstFile
) throws IOException
{
238 final Git hg
= Git
.getInstance();
239 final File root
= hg
.getTopmostManagedParent(srcFile
);
240 if (root
== null) return;
242 RequestProcessor rp
= hg
.getRequestProcessor(root
.getAbsolutePath());
244 Git
.LOG
.log(Level
.FINE
, "hgMoveImplementation(): File: {0} {1}", new Object
[] {srcFile
, dstFile
}); // NOI18N
246 srcFile
.renameTo(dstFile
);
247 Runnable moveImpl
= new Runnable() {
249 OutputLogger logger
= OutputLogger
.getLogger(root
.getAbsolutePath());
251 if (dstFile
.isDirectory()) {
252 GitCommand
.doRenameAfter(root
, srcFile
, dstFile
, logger
);
255 int status
= GitCommand
.getSingleStatus(root
, srcFile
.getParent(), srcFile
.getName()).getStatus();
256 Git
.LOG
.log(Level
.FINE
, "hgMoveImplementation(): Status: {0} {1}", new Object
[] {srcFile
, status
}); // NOI18N
257 if (status
== FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
||
258 status
== FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
) {
259 } else if (status
== FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
) {
260 GitCommand
.doRemove(root
, srcFile
, logger
);
261 GitCommand
.doAdd(root
, dstFile
, logger
);
263 GitCommand
.doRenameAfter(root
, srcFile
, dstFile
, logger
);
265 } catch (GitException e
) {
266 Git
.LOG
.log(Level
.FINE
, "Mercurial failed to rename: File: {0} {1}", new Object
[] {srcFile
.getAbsolutePath(), dstFile
.getAbsolutePath()}); // NOI18N
277 public void afterMove(final File from
, final File to
) {
278 Utils
.post(new Runnable() {
280 fileMovedImpl(from
, to
);
285 private void fileMovedImpl(final File from
, final File to
) {
286 if (from
== null || to
== null || !to
.exists()) return;
287 if (to
.isDirectory()) return;
288 Git hg
= Git
.getInstance();
289 final File root
= hg
.getTopmostManagedParent(from
);
290 if (root
== null) return;
292 RequestProcessor rp
= hg
.getRequestProcessor(root
.getAbsolutePath());
294 GitProgressSupport supportCreate
= new GitProgressSupport() {
295 public void perform() {
296 cache
.refresh(from
, FileStatusCache
.REPOSITORY_STATUS_UNKNOWN
);
297 cache
.refresh(to
, FileStatusCache
.REPOSITORY_STATUS_UNKNOWN
);
301 supportCreate
.start(rp
, root
.getAbsolutePath(),
302 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Move_Progress")); // NOI18N
306 public boolean beforeCreate(File file
, boolean isDirectory
) {
307 return super.beforeCreate(file
, isDirectory
);
311 public void doCreate(File file
, boolean isDirectory
) throws IOException
{
312 super.doCreate(file
, isDirectory
);
316 public void afterCreate(final File file
) {
317 Utils
.post(new Runnable() {
319 fileCreatedImpl(file
);
324 private void fileCreatedImpl(final File file
) {
325 if (file
.isDirectory()) return;
326 Git hg
= Git
.getInstance();
327 final File root
= hg
.getTopmostManagedParent(file
);
328 if (root
== null) return;
330 RequestProcessor rp
= hg
.getRequestProcessor(root
.getAbsolutePath());
332 GitProgressSupport supportCreate
= new GitProgressSupport() {
333 public void perform() {
334 // There is no point in refreshing the cache for ignored files.
335 if (!GitUtils
.isIgnored(file
, false)) {
336 reScheduleRefresh(1000, file
);
341 supportCreate
.start(rp
, root
.getAbsolutePath(),
342 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Create_Progress")); // NOI18N
346 public void afterChange(final File file
) {
347 Utils
.post(new Runnable() {
349 fileChangedImpl(file
);
354 private void fileChangedImpl(final File file
) {
355 if (file
.isDirectory()) return;
356 Git hg
= Git
.getInstance();
357 final File root
= hg
.getTopmostManagedParent(file
);
358 if (root
== null) return;
360 RequestProcessor rp
= hg
.getRequestProcessor(root
.getAbsolutePath());
362 GitProgressSupport supportCreate
= new GitProgressSupport() {
363 public void perform() {
364 Git
.LOG
.log(Level
.FINE
, "fileChangedImpl(): File: {0}", file
); // NOI18N
365 // There is no point in refreshing the cache for ignored files.
366 if (!GitUtils
.isIgnored(file
, false)) {
367 reScheduleRefresh(1000, file
);
372 supportCreate
.start(rp
, root
.getAbsolutePath(),
373 org
.openide
.util
.NbBundle
.getMessage(GitInterceptor
.class, "MSG_Change_Progress")); // NOI18N
376 private void reScheduleRefresh(int delayMillis
, File fileToRefresh
) {
377 if (!filesToRefresh
.contains(fileToRefresh
)) {
378 if (!filesToRefresh
.offer(fileToRefresh
)) {
379 Git
.LOG
.log(Level
.FINE
, "reScheduleRefresh failed to add to filesToRefresh queue {0}", fileToRefresh
);
382 refreshTask
.schedule(delayMillis
);
385 private class RefreshTask
implements Runnable
{
387 Thread
.interrupted();
388 File fileToRefresh
= filesToRefresh
.poll();
389 if (fileToRefresh
!= null) {
390 cache
.refresh(fileToRefresh
, FileStatusCache
.REPOSITORY_STATUS_UNKNOWN
);
391 fileToRefresh
= filesToRefresh
.peek();
392 if (fileToRefresh
!= null) {
393 refreshTask
.schedule(0);