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
;
44 import java
.awt
.Image
;
46 import java
.lang
.reflect
.Field
;
47 import java
.text
.MessageFormat
;
48 import java
.util
.ArrayList
;
49 import java
.util
.HashMap
;
50 import java
.util
.Iterator
;
51 import java
.util
.List
;
53 import java
.util
.ResourceBundle
;
54 import java
.util
.concurrent
.ConcurrentLinkedQueue
;
55 import java
.util
.logging
.Level
;
56 import java
.util
.regex
.Pattern
;
57 import javax
.swing
.Action
;
58 import org
.netbeans
.api
.project
.Project
;
59 import org
.netbeans
.modules
.git
.ui
.annotate
.AnnotateAction
;
60 import org
.netbeans
.modules
.git
.ui
.clone
.CloneAction
;
61 import org
.netbeans
.modules
.git
.ui
.clone
.CloneExternalAction
;
62 import org
.netbeans
.modules
.git
.ui
.commit
.CommitAction
;
63 import org
.netbeans
.modules
.git
.ui
.create
.CreateAction
;
64 import org
.netbeans
.modules
.git
.ui
.diff
.DiffAction
;
65 import org
.netbeans
.modules
.git
.ui
.diff
.ExportDiffAction
;
66 import org
.netbeans
.modules
.git
.ui
.diff
.ImportDiffAction
;
67 import org
.netbeans
.modules
.git
.ui
.ignore
.IgnoreAction
;
68 import org
.netbeans
.modules
.git
.ui
.log
.IncomingAction
;
69 import org
.netbeans
.modules
.git
.ui
.log
.LogAction
;
70 import org
.netbeans
.modules
.git
.ui
.log
.OutAction
;
71 import org
.netbeans
.modules
.git
.ui
.merge
.MergeAction
;
72 import org
.netbeans
.modules
.git
.ui
.properties
.PropertiesAction
;
73 import org
.netbeans
.modules
.git
.ui
.pull
.FetchAction
;
74 import org
.netbeans
.modules
.git
.ui
.pull
.PullAction
;
75 import org
.netbeans
.modules
.git
.ui
.pull
.PullOtherAction
;
76 import org
.netbeans
.modules
.git
.ui
.push
.PushAction
;
77 import org
.netbeans
.modules
.git
.ui
.push
.PushOtherAction
;
78 import org
.netbeans
.modules
.git
.ui
.rollback
.BackoutAction
;
79 import org
.netbeans
.modules
.git
.ui
.rollback
.RollbackAction
;
80 import org
.netbeans
.modules
.git
.ui
.rollback
.StripAction
;
81 import org
.netbeans
.modules
.git
.ui
.status
.StatusAction
;
82 import org
.netbeans
.modules
.git
.ui
.update
.ConflictResolvedAction
;
83 import org
.netbeans
.modules
.git
.ui
.update
.ResolveConflictsAction
;
84 import org
.netbeans
.modules
.git
.ui
.update
.RevertModificationsAction
;
85 import org
.netbeans
.modules
.git
.ui
.update
.UpdateAction
;
86 import org
.netbeans
.modules
.git
.ui
.view
.ViewAction
;
87 import org
.netbeans
.modules
.git
.util
.GitCommand
;
88 import org
.netbeans
.modules
.git
.util
.GitUtils
;
89 import org
.netbeans
.modules
.versioning
.spi
.VCSAnnotator
;
90 import org
.netbeans
.modules
.versioning
.spi
.VCSContext
;
91 import org
.netbeans
.modules
.versioning
.spi
.VersioningSupport
;
92 import org
.netbeans
.modules
.versioning
.util
.Utils
;
93 import org
.openide
.DialogDisplayer
;
94 import org
.openide
.NotifyDescriptor
;
95 import org
.openide
.nodes
.Node
;
96 import org
.openide
.util
.NbBundle
;
97 import org
.openide
.util
.RequestProcessor
;
98 import org
.openide
.util
.Utilities
;
101 * Responsible for coloring file labels and file icons in the IDE and providing IDE with menu items.
103 * @author Maros Sandor
105 public class GitAnnotator
extends VCSAnnotator
{
107 private static final int INITIAL_ACTION_ARRAY_LENGTH
= 25;
108 private static MessageFormat uptodateFormat
= getFormat("uptodateFormat"); // NOI18N
109 private static MessageFormat newLocallyFormat
= getFormat("newLocallyFormat"); // NOI18N
110 private static MessageFormat addedLocallyFormat
= getFormat("addedLocallyFormat"); // NOI18N
111 private static MessageFormat modifiedLocallyFormat
= getFormat("modifiedLocallyFormat"); // NOI18N
112 private static MessageFormat removedLocallyFormat
= getFormat("removedLocallyFormat"); // NOI18N
113 private static MessageFormat deletedLocallyFormat
= getFormat("deletedLocallyFormat"); // NOI18N
114 private static MessageFormat excludedFormat
= getFormat("excludedFormat"); // NOI18N
115 private static MessageFormat conflictFormat
= getFormat("conflictFormat"); // NOI18N
117 private static final int STATUS_TEXT_ANNOTABLE
=
118 FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
|
119 FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
|
120 FileInformation
.STATUS_VERSIONED_UPTODATE
|
121 FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
|
122 FileInformation
.STATUS_VERSIONED_CONFLICT
|
123 FileInformation
.STATUS_VERSIONED_REMOVEDLOCALLY
|
124 FileInformation
.STATUS_VERSIONED_DELETEDLOCALLY
|
125 FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
;
127 private static final Pattern lessThan
= Pattern
.compile("<"); // NOI18N
129 private static final int STATUS_BADGEABLE
=
130 FileInformation
.STATUS_VERSIONED_UPTODATE
|
131 FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
|
132 FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
;
134 public static String ANNOTATION_REVISION
= "revision"; // NOI18N
135 public static String ANNOTATION_STATUS
= "status"; // NOI18N
136 public static String ANNOTATION_FOLDER
= "folder"; // NOI18N
138 public static String
[] LABELS
= new String
[] {ANNOTATION_REVISION
, ANNOTATION_STATUS
, ANNOTATION_FOLDER
};
140 private FileStatusCache cache
;
141 private MessageFormat format
;
142 private String emptyFormat
;
143 private Boolean needRevisionForFormat
;
144 private File folderToScan
;
145 private ConcurrentLinkedQueue
<File
> dirsToScan
= new ConcurrentLinkedQueue
<File
>();
146 private RequestProcessor
.Task scanTask
;
147 private static final RequestProcessor rp
= new RequestProcessor("GitAnnotateScan", 1, true); // NOI18N
149 public GitAnnotator() {
150 cache
= Git
.getInstance().getFileStatusCache();
151 scanTask
= rp
.create(new ScanTask());
155 private void initDefaults() {
156 Field
[] fields
= GitAnnotator
.class.getDeclaredFields();
157 for (int i
= 0; i
< fields
.length
; i
++) {
158 String name
= fields
[i
].getName();
159 if (name
.endsWith("Format")) { // NOI18N
160 initDefaultColor(name
.substring(0, name
.length() - 6));
166 private void refresh() {
167 throw new UnsupportedOperationException("Not yet implemented");
170 private void initDefaultColor(String name
) {
171 String color
= System
.getProperty("hg.color." + name
); // NOI18N
172 if (color
== null) return;
173 setAnnotationColor(name
, color
);
178 * Changes annotation color of files.
180 * @param name name of the color to change. Can be one of:
181 * newLocally, addedLocally, modifiedLocally, removedLocally, deletedLocally, newInRepository, modifiedInRepository,
182 * removedInRepository, conflict, mergeable, excluded.
183 * @param colorString new color in the format: 4455AA (RGB hexadecimal)
185 private void setAnnotationColor(String name
, String colorString
) {
187 Field field
= GitAnnotator
.class.getDeclaredField(name
+ "Format"); // NOI18N
188 MessageFormat format
= new MessageFormat("<font color=\"" + colorString
+ "\">{0}</font><font color=\"#999999\">{1}</font>"); // NOI18N
189 field
.set(null, format
);
190 } catch (Exception e
) {
191 throw new IllegalArgumentException("Invalid color name"); // NOI18N
195 private static MessageFormat
getFormat(String key
) {
196 String format
= NbBundle
.getMessage(GitAnnotator
.class, key
);
197 return new MessageFormat(format
);
201 public String
annotateName(String name
, VCSContext context
) {
202 int includeStatus
= FileInformation
.STATUS_VERSIONED_UPTODATE
| FileInformation
.STATUS_LOCAL_CHANGE
| FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
;
204 FileInformation mostImportantInfo
= null;
205 File mostImportantFile
= null;
206 boolean folderAnnotation
= false;
208 for (final File file
: context
.getRootFiles()) {
209 FileInformation info
= cache
.getCachedStatus(file
, true);
211 File parentFile
= file
.getParentFile();
212 Git
.LOG
.log(Level
.FINE
, "null cached status for: {0} {1} {2}", new Object
[] {file
, folderToScan
, parentFile
});
213 folderToScan
= parentFile
;
214 reScheduleScan(1000);
215 info
= new FileInformation(FileInformation
.STATUS_VERSIONED_UPTODATE
, false);
217 int status
= info
.getStatus();
218 if ((status
& includeStatus
) == 0) continue;
220 if (isMoreImportant(info
, mostImportantInfo
)) {
221 mostImportantInfo
= info
;
222 mostImportantFile
= file
;
223 folderAnnotation
= file
.isDirectory();
227 if (folderAnnotation
== false && context
.getRootFiles().size() > 1) {
228 folderAnnotation
= !Utils
.shareCommonDataObject(context
.getRootFiles().toArray(new File
[context
.getRootFiles().size()]));
231 if (mostImportantInfo
== null) return null;
232 return folderAnnotation ?
233 annotateFolderNameHtml(name
, mostImportantInfo
, mostImportantFile
) :
234 annotateNameHtml(name
, mostImportantInfo
, mostImportantFile
);
238 public Image
annotateIcon(Image icon
, VCSContext context
) {
239 boolean folderAnnotation
= false;
240 for (File file
: context
.getRootFiles()) {
241 if (file
.isDirectory()) {
242 folderAnnotation
= true;
247 if (folderAnnotation
== false && context
.getRootFiles().size() > 1) {
248 folderAnnotation
= !Utils
.shareCommonDataObject(context
.getRootFiles().toArray(new File
[context
.getRootFiles().size()]));
251 if (folderAnnotation
== false) {
255 boolean isVersioned
= false;
256 for (Iterator i
= context
.getRootFiles().iterator(); i
.hasNext();) {
257 File file
= (File
) i
.next();
258 // There is an assumption here that annotateName was already
259 // called and FileStatusCache.getStatus was scheduled if
260 // FileStatusCache.getCachedStatus returned null.
261 FileInformation info
= cache
.getCachedStatus(file
, true);
262 if ((info
!= null && (info
.getStatus() & STATUS_BADGEABLE
) != 0)) {
267 if (!isVersioned
) return null;
269 boolean allExcluded
= true;
270 boolean modified
= false;
272 Map
<File
, FileInformation
> map
= cache
.getAllModifiedFiles();
273 Map
<File
, FileInformation
> modifiedFiles
= new HashMap
<File
, FileInformation
>();
274 for (Iterator i
= map
.keySet().iterator(); i
.hasNext();) {
275 File file
= (File
) i
.next();
276 FileInformation info
= map
.get(file
);
277 if ((info
.getStatus() & FileInformation
.STATUS_LOCAL_CHANGE
) != 0) modifiedFiles
.put(file
, info
);
280 for (Iterator i
= context
.getRootFiles().iterator(); i
.hasNext();) {
281 File file
= (File
) i
.next();
282 if (VersioningSupport
.isFlat(file
)) {
283 for (Iterator j
= modifiedFiles
.keySet().iterator(); j
.hasNext();) {
284 File mf
= (File
) j
.next();
285 if (mf
.getParentFile().equals(file
)) {
286 FileInformation info
= modifiedFiles
.get(mf
);
287 if (info
.isDirectory()) continue;
288 int status
= info
.getStatus();
289 if (status
== FileInformation
.STATUS_VERSIONED_CONFLICT
) {
290 Image badge
= Utilities
.loadImage("org/netbeans/modules/mercurial/resources/icons/conflicts-badge.png", true); // NOI18N
291 return Utilities
.mergeImages(icon
, badge
, 16, 9);
294 allExcluded
&= isExcludedFromCommit(mf
.getAbsolutePath());
298 for (Iterator j
= modifiedFiles
.keySet().iterator(); j
.hasNext();) {
299 File mf
= (File
) j
.next();
300 if (Utils
.isAncestorOrEqual(file
, mf
)) {
301 FileInformation info
= modifiedFiles
.get(mf
);
302 int status
= info
.getStatus();
303 if ((status
== FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
|| status
== FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
) && file
.equals(mf
)) {
306 if (status
== FileInformation
.STATUS_VERSIONED_CONFLICT
) {
307 Image badge
= Utilities
.loadImage("org/netbeans/modules/mercurial/resources/icons/conflicts-badge.png", true); // NOI18N
308 return Utilities
.mergeImages(icon
, badge
, 16, 9);
311 allExcluded
&= isExcludedFromCommit(mf
.getAbsolutePath());
317 if (modified
&& !allExcluded
) {
318 Image badge
= Utilities
.loadImage("org/netbeans/modules/mercurial/resources/icons/modified-badge.png", true); // NOI18N
319 return Utilities
.mergeImages(icon
, badge
, 16, 9);
326 public Action
[] getActions(VCSContext ctx
, VCSAnnotator
.ActionDestination destination
) {
327 // TODO: get resource strings for all actions:
328 ResourceBundle loc
= NbBundle
.getBundle(GitAnnotator
.class);
329 Node
[] nodes
= ctx
.getElements().lookupAll(Node
.class).toArray(new Node
[0]);
330 File
[] files
= ctx
.getRootFiles().toArray(new File
[ctx
.getRootFiles().size()]);
331 File root
= GitUtils
.getRootFile(ctx
);
332 boolean noneVersioned
= root
== null;
333 boolean onlyFolders
= onlyFolders(files
);
334 boolean onlyProjects
= onlyProjects(nodes
);
336 List
<Action
> actions
= new ArrayList
<Action
>(INITIAL_ACTION_ARRAY_LENGTH
);
337 if (destination
== VCSAnnotator
.ActionDestination
.MainMenu
) {
338 actions
.add(new CreateAction(loc
.getString("CTL_MenuItem_Create"), ctx
)); // NOI18N
340 actions
.add(new StatusAction(loc
.getString("CTL_PopupMenuItem_Status"), ctx
)); // NOI18N
341 actions
.add(new DiffAction(loc
.getString("CTL_PopupMenuItem_Diff"), ctx
)); // NOI18N
342 actions
.add(new UpdateAction(loc
.getString("CTL_PopupMenuItem_Update"), ctx
)); // NOI18N
343 actions
.add(new CommitAction(loc
.getString("CTL_PopupMenuItem_Commit"), ctx
)); // NOI18N
345 actions
.add(new ExportDiffAction(loc
.getString("CTL_PopupMenuItem_ExportDiff"), ctx
)); // NOI18N
346 actions
.add(new ImportDiffAction(loc
.getString("CTL_PopupMenuItem_ImportDiff"), ctx
)); // NOI18N
350 actions
.add(new CloneAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_CloneLocal", // NOI18N
351 root
.getName()), ctx
));
353 actions
.add(new CloneExternalAction(loc
.getString("CTL_PopupMenuItem_CloneOther"), ctx
)); // NOI18N
355 actions
.add(new FetchAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_FetchLocal"), ctx
)); // NOI18N
356 actions
.add(new PushAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_PushLocal"), ctx
)); // NOI18N
357 actions
.add(new PushOtherAction(loc
.getString("CTL_PopupMenuItem_PushOther"), ctx
)); // NOI18N
358 actions
.add(new PullAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_PullLocal"), ctx
)); // NOI18N
359 actions
.add(new PullOtherAction(loc
.getString("CTL_PopupMenuItem_PullOther"), ctx
)); // NOI18N
360 actions
.add(new MergeAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Merge"), ctx
)); // NOI18N
362 AnnotateAction tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_ShowAnnotations"), ctx
); // NOI18N
363 if (tempA
.visible(nodes
)) {
364 tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_HideAnnotations"), ctx
); // NOI18N
367 actions
.add(new LogAction(loc
.getString("CTL_PopupMenuItem_Log"), ctx
)); // NOI18N
368 actions
.add(new IncomingAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowIncoming"), ctx
)); // NOI18N
369 actions
.add(new OutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowOut"), ctx
)); // NOI18N
370 actions
.add(new ViewAction(loc
.getString("CTL_PopupMenuItem_View"), ctx
)); // NOI18N
372 actions
.add(new RevertModificationsAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Revert"), ctx
)); // NOI18N
373 actions
.add(new StripAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Strip"), ctx
)); // NOI18N
374 actions
.add(new BackoutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Backout"), ctx
)); // NOI18N
375 actions
.add(new RollbackAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Rollback"), ctx
)); // NOI18N
376 actions
.add(new ResolveConflictsAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Resolve"), ctx
)); // NOI18N
377 if (!onlyProjects
&& !onlyFolders
) {
378 IgnoreAction tempIA
= new IgnoreAction(loc
.getString("CTL_PopupMenuItem_Ignore"), ctx
); // NOI18N
382 actions
.add(new PropertiesAction(loc
.getString("CTL_PopupMenuItem_Properties"), ctx
)); // NOI18N
385 actions
.add(new CreateAction(loc
.getString("CTL_PopupMenuItem_Create"), ctx
)); // NOI18N
387 actions
.add(new StatusAction(loc
.getString("CTL_PopupMenuItem_Status"), ctx
)); // NOI18N
388 actions
.add(new DiffAction(loc
.getString("CTL_PopupMenuItem_Diff"), ctx
)); // NOI18N
389 actions
.add(new UpdateAction(loc
.getString("CTL_PopupMenuItem_Update"), ctx
)); // NOI18N
390 actions
.add(new CommitAction(loc
.getString("CTL_PopupMenuItem_Commit"), ctx
)); // NOI18N
393 actions
.add(new CloneAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_CloneLocal", // NOI18N
394 root
.getName()), ctx
));
398 actions
.add(new FetchAction(NbBundle
.getMessage(GitAnnotator
.class,
399 "CTL_PopupMenuItem_FetchLocal"), ctx
)); // NOI18N
400 actions
.add(new PushAction(NbBundle
.getMessage(GitAnnotator
.class,
401 "CTL_PopupMenuItem_PushLocal"), ctx
)); // NOI18N
402 actions
.add(new PullAction(NbBundle
.getMessage(GitAnnotator
.class,
403 "CTL_PopupMenuItem_PullLocal"), ctx
)); // NOI18N
404 actions
.add(new MergeAction(NbBundle
.getMessage(GitAnnotator
.class,
405 "CTL_PopupMenuItem_Merge"), ctx
)); // NOI18N
409 AnnotateAction tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_ShowAnnotations"), ctx
); // NOI18N
410 if (tempA
.visible(nodes
)) {
411 tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_HideAnnotations"), ctx
); // NOI18N
415 actions
.add(new LogAction(loc
.getString("CTL_PopupMenuItem_Log"), ctx
)); // NOI18N
416 actions
.add(new IncomingAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowIncoming"), ctx
)); // NOI18N
417 actions
.add(new OutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowOut"), ctx
)); // NOI18N
418 actions
.add(new ViewAction(loc
.getString("CTL_PopupMenuItem_View"), ctx
)); // NOI18N
420 actions
.add(new RevertModificationsAction(NbBundle
.getMessage(GitAnnotator
.class,
421 "CTL_PopupMenuItem_Revert"), ctx
)); // NOI18N
422 actions
.add(new StripAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Strip"), ctx
)); // NOI18N
423 actions
.add(new BackoutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Backout"), ctx
)); // NOI18N
424 actions
.add(new RollbackAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Rollback"), ctx
)); // NOI18N
425 actions
.add(new ResolveConflictsAction(NbBundle
.getMessage(GitAnnotator
.class,
426 "CTL_PopupMenuItem_Resolve"), ctx
)); // NOI18N
427 if (!onlyProjects
&& !onlyFolders
) {
428 actions
.add(new ConflictResolvedAction(NbBundle
.getMessage(GitAnnotator
.class,
429 "CTL_PopupMenuItem_MarkResolved"), ctx
)); // NOI18N
431 IgnoreAction tempIA
= new IgnoreAction(loc
.getString("CTL_PopupMenuItem_Ignore"), ctx
); // NOI18N
435 actions
.add(new PropertiesAction(loc
.getString("CTL_PopupMenuItem_Properties"), ctx
)); // NOI18N
438 return actions
.toArray(new Action
[actions
.size()]);
442 * Applies custom format.
444 private String
formatAnnotation(FileInformation info
, File file
) {
445 String statusString
= ""; // NOI18N
446 int status
= info
.getStatus();
447 if (status
!= FileInformation
.STATUS_VERSIONED_UPTODATE
) {
448 statusString
= info
.getShortStatusText();
451 String revisionString
= ""; // NOI18N
452 String binaryString
= ""; // NOI18N
454 if (needRevisionForFormat
) {
455 if ((status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
) == 0) {
457 File repository
= Git
.getInstance().getTopmostManagedParent(file
);
458 String revStr
= GitCommand
.getLastRevision(repository
, file
);
459 if (revStr
!= null) {
460 revisionString
= revStr
;
462 } catch (GitException ex
) {
463 NotifyDescriptor
.Exception e
= new NotifyDescriptor
.Exception(ex
);
464 DialogDisplayer
.getDefault().notifyLater(e
);
469 //String stickyString = SvnUtils.getCopy(file);
470 String stickyString
= null;
471 if (stickyString
== null) {
472 stickyString
= ""; // NOI18N
475 Object
[] arguments
= new Object
[] {
481 String annotation
= format
.format(arguments
, new StringBuffer(), null).toString().trim();
482 if(annotation
.equals(emptyFormat
)) {
485 return " " + annotation
; // NOI18N
489 public String
annotateNameHtml(File file
, FileInformation info
) {
490 return annotateNameHtml(file
.getName(), info
, file
);
493 public String
annotateNameHtml(String name
, FileInformation mostImportantInfo
, File mostImportantFile
) {
494 // Hg: The codes used to show the status of files are:
499 // ! = deleted, but still tracked
501 // I = ignored (not shown by default)
503 name
= htmlEncode(name
);
505 String textAnnotation
;
506 boolean annotationsVisible
= VersioningSupport
.getPreferences().getBoolean(VersioningSupport
.PREF_BOOLEAN_TEXT_ANNOTATIONS_VISIBLE
, false);
507 int status
= mostImportantInfo
.getStatus();
509 if (annotationsVisible
&& mostImportantFile
!= null && (status
& STATUS_TEXT_ANNOTABLE
) != 0) {
510 if (format
!= null) {
511 textAnnotation
= formatAnnotation(mostImportantInfo
, mostImportantFile
);
513 //String sticky = SvnUtils.getCopy(mostImportantFile);
514 String sticky
= null;
515 if (status
== FileInformation
.STATUS_VERSIONED_UPTODATE
&& sticky
== null) {
516 textAnnotation
= ""; // NOI18N
517 } else if (status
== FileInformation
.STATUS_VERSIONED_UPTODATE
) {
518 textAnnotation
= " [" + sticky
+ "]"; // NOI18N
519 } else if (sticky
== null) {
520 String statusText
= mostImportantInfo
.getShortStatusText();
521 if(!statusText
.equals("")) { // NOI18N
522 textAnnotation
= " [" + mostImportantInfo
.getShortStatusText() + "]"; // NOI18N
524 textAnnotation
= ""; // NOI18N
527 textAnnotation
= " [" + mostImportantInfo
.getShortStatusText() + "; " + sticky
+ "]"; // NOI18N
531 textAnnotation
= ""; // NOI18N
534 if (textAnnotation
.length() > 0) {
535 textAnnotation
= NbBundle
.getMessage(GitAnnotator
.class, "textAnnotation", textAnnotation
); // NOI18N
538 if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
)) {
539 return excludedFormat
.format(new Object
[] { name
, textAnnotation
});
540 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_DELETEDLOCALLY
)) {
541 return deletedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
542 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDLOCALLY
)) {
543 return removedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
544 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
)) {
545 return newLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
546 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
)) {
547 return addedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
548 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
)) {
549 return modifiedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
550 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_UPTODATE
)) {
551 return uptodateFormat
.format(new Object
[] { name
, textAnnotation
});
552 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_CONFLICT
)) {
553 return conflictFormat
.format(new Object
[] { name
, textAnnotation
});
554 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NOTMANAGED
)) {
556 } else if (status
== FileInformation
.STATUS_UNKNOWN
) {
559 throw new IllegalArgumentException("Uncomparable status: " + status
); // NOI18N
563 private String
htmlEncode(String name
) {
564 if (name
.indexOf('<') == -1) return name
;
565 return lessThan
.matcher(name
).replaceAll("<"); // NOI18N
568 private String
annotateFolderNameHtml(String name
, FileInformation mostImportantInfo
, File mostImportantFile
) {
569 String nameHtml
= htmlEncode(name
);
570 if (mostImportantInfo
.getStatus() == FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
){
571 return excludedFormat
.format(new Object
[] { nameHtml
, ""}); // NOI18N
573 String fileName
= mostImportantFile
.getName();
574 if (fileName
.equals(name
)){
575 return uptodateFormat
.format(new Object
[] { nameHtml
, "" }); // NOI18N
578 // Label top level repository nodes with a repository name label when:
579 // Display Name (name) is different from its repo name (repo.getName())
581 File repo
= Git
.getInstance().getTopmostManagedParent(mostImportantFile
);
582 if(repo
!= null && repo
.equals(mostImportantFile
)){
583 if (!repo
.getName().equals(name
)){
584 fileName
= repo
.getName();
587 if (fileName
!= null)
588 return uptodateFormat
.format(new Object
[] { nameHtml
, " [" + fileName
+ "]" }); // NOI18N
590 return uptodateFormat
.format(new Object
[] { nameHtml
, "" }); // NOI18N
593 private boolean isMoreImportant(FileInformation a
, FileInformation b
) {
594 if (b
== null) return true;
595 if (a
== null) return false;
596 return getComparableStatus(a
.getStatus()) < getComparableStatus(b
.getStatus());
600 * Gets integer status that can be used in comparators. The more important the status is for the user,
601 * the lower value it has. Conflict is 0, unknown status is 100.
603 * @return status constant suitable for 'by importance' comparators
605 public static int getComparableStatus(int status
) {
606 if (0 != (status
& FileInformation
.STATUS_VERSIONED_CONFLICT
)) {
608 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MERGE
)) {
610 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_DELETEDLOCALLY
)) {
612 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDLOCALLY
)) {
614 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
)) {
616 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
)) {
618 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
)) {
620 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDINREPOSITORY
)) {
622 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_NEWINREPOSITORY
)) {
624 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDINREPOSITORY
)) {
626 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_UPTODATE
)) {
628 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
)) {
630 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NOTMANAGED
)) {
632 } else if (status
== FileInformation
.STATUS_UNKNOWN
) {
635 throw new IllegalArgumentException("Uncomparable status: " + status
); // NOI18N
639 private boolean isExcludedFromCommit(String absolutePath
) {
643 private boolean isNothingVersioned(File
[] files
) {
644 for (File file
: files
) {
645 if ((cache
.getStatus(file
).getStatus() & FileInformation
.STATUS_MANAGED
) != 0) return false;
650 private static boolean onlyProjects(Node
[] nodes
) {
651 if (nodes
== null) return false;
652 for (Node node
: nodes
) {
653 if (node
.getLookup().lookup(Project
.class) == null) return false;
658 private boolean onlyFolders(File
[] files
) {
659 for (int i
= 0; i
< files
.length
; i
++) {
660 if (files
[i
].isFile()) return false;
661 if (!files
[i
].exists() && !cache
.getStatus(files
[i
]).isDirectory()) return false;
666 private void reScheduleScan(int delayMillis
) {
667 File dirToScan
= dirsToScan
.peek();
668 if (!folderToScan
.equals(dirToScan
)) {
669 if (!dirsToScan
.offer(folderToScan
)) {
670 Git
.LOG
.log(Level
.FINE
, "reScheduleScan failed to add to dirsToScan queue: {0} ", folderToScan
);
673 scanTask
.schedule(delayMillis
);
676 private class ScanTask
implements Runnable
{
678 Thread
.interrupted();
679 File dirToScan
= dirsToScan
.poll();
680 if (dirToScan
!= null) {
681 cache
.getScannedFiles(dirToScan
, null);
682 dirToScan
= dirsToScan
.peek();
683 if (dirToScan
!= null) {
684 scanTask
.schedule(1000);