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 public void refresh() {
167 String string
= GitModuleConfig
.getDefault().getAnnotationFormat();
168 if (string
!= null && !string
.trim().equals("")) { // NOI18N
169 needRevisionForFormat
= isRevisionInAnnotationFormat(string
);
170 string
= string
.replaceAll("\\{revision\\}", "\\{0\\}"); // NOI18N
171 string
= string
.replaceAll("\\{status\\}", "\\{1\\}"); // NOI18N
172 string
= string
.replaceAll("\\{folder\\}", "\\{2\\}"); // NOI18N
173 format
= new MessageFormat(string
);
174 emptyFormat
= format
.format(new String
[] {"", "", ""} , new StringBuffer(), null).toString().trim(); // NOI18N
178 public static boolean isRevisionInAnnotationFormat(String str
){
179 if (str
.indexOf("{revision}") != -1) { // NOI18N
186 private void initDefaultColor(String name
) {
187 String color
= System
.getProperty("git.color." + name
); // NOI18N
188 if (color
== null) return;
189 setAnnotationColor(name
, color
);
193 * Changes annotation color of files.
195 * @param name name of the color to change. Can be one of:
196 * newLocally, addedLocally, modifiedLocally, removedLocally, deletedLocally, newInRepository, modifiedInRepository,
197 * removedInRepository, conflict, mergeable, excluded.
198 * @param colorString new color in the format: 4455AA (RGB hexadecimal)
200 private void setAnnotationColor(String name
, String colorString
) {
202 Field field
= GitAnnotator
.class.getDeclaredField(name
+ "Format"); // NOI18N
203 MessageFormat format
= new MessageFormat("<font color=\"" + colorString
+ "\">{0}</font><font color=\"#999999\">{1}</font>"); // NOI18N
204 field
.set(null, format
);
205 } catch (Exception e
) {
206 throw new IllegalArgumentException("Invalid color name"); // NOI18N
210 private static MessageFormat
getFormat(String key
) {
211 String format
= NbBundle
.getMessage(GitAnnotator
.class, key
);
212 return new MessageFormat(format
);
216 public String
annotateName(String name
, VCSContext context
) {
217 int includeStatus
= FileInformation
.STATUS_VERSIONED_UPTODATE
| FileInformation
.STATUS_LOCAL_CHANGE
| FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
;
219 FileInformation mostImportantInfo
= null;
220 File mostImportantFile
= null;
221 boolean folderAnnotation
= false;
223 for (final File file
: context
.getRootFiles()) {
224 FileInformation info
= cache
.getCachedStatus(file
, true);
226 File parentFile
= file
.getParentFile();
227 Git
.LOG
.log(Level
.FINE
, "null cached status for: {0} {1} {2}", new Object
[] {file
, folderToScan
, parentFile
});
228 folderToScan
= parentFile
;
229 reScheduleScan(1000);
230 info
= new FileInformation(FileInformation
.STATUS_VERSIONED_UPTODATE
, false);
232 int status
= info
.getStatus();
233 if ((status
& includeStatus
) == 0) continue;
235 if (isMoreImportant(info
, mostImportantInfo
)) {
236 mostImportantInfo
= info
;
237 mostImportantFile
= file
;
238 folderAnnotation
= file
.isDirectory();
242 if (folderAnnotation
== false && context
.getRootFiles().size() > 1) {
243 folderAnnotation
= !Utils
.shareCommonDataObject(context
.getRootFiles().toArray(new File
[context
.getRootFiles().size()]));
246 if (mostImportantInfo
== null) return null;
247 return folderAnnotation ?
248 annotateFolderNameHtml(name
, mostImportantInfo
, mostImportantFile
) :
249 annotateNameHtml(name
, mostImportantInfo
, mostImportantFile
);
253 public Image
annotateIcon(Image icon
, VCSContext context
) {
254 boolean folderAnnotation
= false;
255 for (File file
: context
.getRootFiles()) {
256 if (file
.isDirectory()) {
257 folderAnnotation
= true;
262 if (folderAnnotation
== false && context
.getRootFiles().size() > 1) {
263 folderAnnotation
= !Utils
.shareCommonDataObject(context
.getRootFiles().toArray(new File
[context
.getRootFiles().size()]));
266 if (folderAnnotation
== false) {
270 boolean isVersioned
= false;
271 for (Iterator i
= context
.getRootFiles().iterator(); i
.hasNext();) {
272 File file
= (File
) i
.next();
273 // There is an assumption here that annotateName was already
274 // called and FileStatusCache.getStatus was scheduled if
275 // FileStatusCache.getCachedStatus returned null.
276 FileInformation info
= cache
.getCachedStatus(file
, true);
277 if ((info
!= null && (info
.getStatus() & STATUS_BADGEABLE
) != 0)) {
282 if (!isVersioned
) return null;
284 boolean allExcluded
= true;
285 boolean modified
= false;
287 Map
<File
, FileInformation
> map
= cache
.getAllModifiedFiles();
288 Map
<File
, FileInformation
> modifiedFiles
= new HashMap
<File
, FileInformation
>();
289 for (Iterator i
= map
.keySet().iterator(); i
.hasNext();) {
290 File file
= (File
) i
.next();
291 FileInformation info
= map
.get(file
);
292 if ((info
.getStatus() & FileInformation
.STATUS_LOCAL_CHANGE
) != 0) modifiedFiles
.put(file
, info
);
295 for (Iterator i
= context
.getRootFiles().iterator(); i
.hasNext();) {
296 File file
= (File
) i
.next();
297 if (VersioningSupport
.isFlat(file
)) {
298 for (Iterator j
= modifiedFiles
.keySet().iterator(); j
.hasNext();) {
299 File mf
= (File
) j
.next();
300 if (mf
.getParentFile().equals(file
)) {
301 FileInformation info
= modifiedFiles
.get(mf
);
302 if (info
.isDirectory()) continue;
303 int status
= info
.getStatus();
304 if (status
== FileInformation
.STATUS_VERSIONED_CONFLICT
) {
305 Image badge
= Utilities
.loadImage("org/netbeans/modules/git/resources/icons/conflicts-badge.png", true); // NOI18N
306 return Utilities
.mergeImages(icon
, badge
, 16, 9);
309 allExcluded
&= isExcludedFromCommit(mf
.getAbsolutePath());
313 for (Iterator j
= modifiedFiles
.keySet().iterator(); j
.hasNext();) {
314 File mf
= (File
) j
.next();
315 if (Utils
.isAncestorOrEqual(file
, mf
)) {
316 FileInformation info
= modifiedFiles
.get(mf
);
317 int status
= info
.getStatus();
318 if ((status
== FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
|| status
== FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
) && file
.equals(mf
)) {
321 if (status
== FileInformation
.STATUS_VERSIONED_CONFLICT
) {
322 Image badge
= Utilities
.loadImage("org/netbeans/modules/git/resources/icons/conflicts-badge.png", true); // NOI18N
323 return Utilities
.mergeImages(icon
, badge
, 16, 9);
326 allExcluded
&= isExcludedFromCommit(mf
.getAbsolutePath());
332 if (modified
&& !allExcluded
) {
333 Image badge
= Utilities
.loadImage("org/netbeans/modules/git/resources/icons/modified-badge.png", true); // NOI18N
334 return Utilities
.mergeImages(icon
, badge
, 16, 9);
341 public Action
[] getActions(VCSContext ctx
, VCSAnnotator
.ActionDestination destination
) {
342 // TODO: get resource strings for all actions:
343 ResourceBundle loc
= NbBundle
.getBundle(GitAnnotator
.class);
344 Node
[] nodes
= ctx
.getElements().lookupAll(Node
.class).toArray(new Node
[0]);
345 File
[] files
= ctx
.getRootFiles().toArray(new File
[ctx
.getRootFiles().size()]);
346 File root
= GitUtils
.getRootFile(ctx
);
347 boolean noneVersioned
= root
== null;
348 boolean onlyFolders
= onlyFolders(files
);
349 boolean onlyProjects
= onlyProjects(nodes
);
351 List
<Action
> actions
= new ArrayList
<Action
>(INITIAL_ACTION_ARRAY_LENGTH
);
352 if (destination
== VCSAnnotator
.ActionDestination
.MainMenu
) {
353 actions
.add(new CreateAction(loc
.getString("CTL_MenuItem_Create"), ctx
)); // NOI18N
355 actions
.add(new StatusAction(loc
.getString("CTL_PopupMenuItem_Status"), ctx
)); // NOI18N
356 actions
.add(new DiffAction(loc
.getString("CTL_PopupMenuItem_Diff"), ctx
)); // NOI18N
357 actions
.add(new UpdateAction(loc
.getString("CTL_PopupMenuItem_Update"), ctx
)); // NOI18N
358 actions
.add(new CommitAction(loc
.getString("CTL_PopupMenuItem_Commit"), ctx
)); // NOI18N
360 actions
.add(new ExportDiffAction(loc
.getString("CTL_PopupMenuItem_ExportDiff"), ctx
)); // NOI18N
361 actions
.add(new ImportDiffAction(loc
.getString("CTL_PopupMenuItem_ImportDiff"), ctx
)); // NOI18N
365 actions
.add(new CloneAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_CloneLocal", // NOI18N
366 root
.getName()), ctx
));
368 actions
.add(new CloneExternalAction(loc
.getString("CTL_PopupMenuItem_CloneOther"), ctx
)); // NOI18N
370 actions
.add(new FetchAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_FetchLocal"), ctx
)); // NOI18N
371 actions
.add(new PushAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_PushLocal"), ctx
)); // NOI18N
372 actions
.add(new PushOtherAction(loc
.getString("CTL_PopupMenuItem_PushOther"), ctx
)); // NOI18N
373 actions
.add(new PullAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_PullLocal"), ctx
)); // NOI18N
374 actions
.add(new PullOtherAction(loc
.getString("CTL_PopupMenuItem_PullOther"), ctx
)); // NOI18N
375 actions
.add(new MergeAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Merge"), ctx
)); // NOI18N
377 AnnotateAction tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_ShowAnnotations"), ctx
); // NOI18N
378 if (tempA
.visible(nodes
)) {
379 tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_HideAnnotations"), ctx
); // NOI18N
382 actions
.add(new LogAction(loc
.getString("CTL_PopupMenuItem_Log"), ctx
)); // NOI18N
383 actions
.add(new IncomingAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowIncoming"), ctx
)); // NOI18N
384 actions
.add(new OutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowOut"), ctx
)); // NOI18N
385 actions
.add(new ViewAction(loc
.getString("CTL_PopupMenuItem_View"), ctx
)); // NOI18N
387 actions
.add(new RevertModificationsAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Revert"), ctx
)); // NOI18N
388 actions
.add(new StripAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Strip"), ctx
)); // NOI18N
389 actions
.add(new BackoutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Backout"), ctx
)); // NOI18N
390 actions
.add(new RollbackAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Rollback"), ctx
)); // NOI18N
391 actions
.add(new ResolveConflictsAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Resolve"), ctx
)); // NOI18N
392 if (!onlyProjects
&& !onlyFolders
) {
393 IgnoreAction tempIA
= new IgnoreAction(loc
.getString("CTL_PopupMenuItem_Ignore"), ctx
); // NOI18N
397 actions
.add(new PropertiesAction(loc
.getString("CTL_PopupMenuItem_Properties"), ctx
)); // NOI18N
400 actions
.add(new CreateAction(loc
.getString("CTL_PopupMenuItem_Create"), ctx
)); // NOI18N
402 actions
.add(new StatusAction(loc
.getString("CTL_PopupMenuItem_Status"), ctx
)); // NOI18N
403 actions
.add(new DiffAction(loc
.getString("CTL_PopupMenuItem_Diff"), ctx
)); // NOI18N
404 actions
.add(new UpdateAction(loc
.getString("CTL_PopupMenuItem_Update"), ctx
)); // NOI18N
405 actions
.add(new CommitAction(loc
.getString("CTL_PopupMenuItem_Commit"), ctx
)); // NOI18N
408 actions
.add(new CloneAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_CloneLocal", // NOI18N
409 root
.getName()), ctx
));
413 actions
.add(new FetchAction(NbBundle
.getMessage(GitAnnotator
.class,
414 "CTL_PopupMenuItem_FetchLocal"), ctx
)); // NOI18N
415 actions
.add(new PushAction(NbBundle
.getMessage(GitAnnotator
.class,
416 "CTL_PopupMenuItem_PushLocal"), ctx
)); // NOI18N
417 actions
.add(new PullAction(NbBundle
.getMessage(GitAnnotator
.class,
418 "CTL_PopupMenuItem_PullLocal"), ctx
)); // NOI18N
419 actions
.add(new MergeAction(NbBundle
.getMessage(GitAnnotator
.class,
420 "CTL_PopupMenuItem_Merge"), ctx
)); // NOI18N
424 AnnotateAction tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_ShowAnnotations"), ctx
); // NOI18N
425 if (tempA
.visible(nodes
)) {
426 tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_HideAnnotations"), ctx
); // NOI18N
430 actions
.add(new LogAction(loc
.getString("CTL_PopupMenuItem_Log"), ctx
)); // NOI18N
431 actions
.add(new IncomingAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowIncoming"), ctx
)); // NOI18N
432 actions
.add(new OutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowOut"), ctx
)); // NOI18N
433 actions
.add(new ViewAction(loc
.getString("CTL_PopupMenuItem_View"), ctx
)); // NOI18N
435 actions
.add(new RevertModificationsAction(NbBundle
.getMessage(GitAnnotator
.class,
436 "CTL_PopupMenuItem_Revert"), ctx
)); // NOI18N
437 actions
.add(new StripAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Strip"), ctx
)); // NOI18N
438 actions
.add(new BackoutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Backout"), ctx
)); // NOI18N
439 actions
.add(new RollbackAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Rollback"), ctx
)); // NOI18N
440 actions
.add(new ResolveConflictsAction(NbBundle
.getMessage(GitAnnotator
.class,
441 "CTL_PopupMenuItem_Resolve"), ctx
)); // NOI18N
442 if (!onlyProjects
&& !onlyFolders
) {
443 actions
.add(new ConflictResolvedAction(NbBundle
.getMessage(GitAnnotator
.class,
444 "CTL_PopupMenuItem_MarkResolved"), ctx
)); // NOI18N
446 IgnoreAction tempIA
= new IgnoreAction(loc
.getString("CTL_PopupMenuItem_Ignore"), ctx
); // NOI18N
450 actions
.add(new PropertiesAction(loc
.getString("CTL_PopupMenuItem_Properties"), ctx
)); // NOI18N
453 return actions
.toArray(new Action
[actions
.size()]);
457 * Applies custom format.
459 private String
formatAnnotation(FileInformation info
, File file
) {
460 String statusString
= ""; // NOI18N
461 int status
= info
.getStatus();
462 if (status
!= FileInformation
.STATUS_VERSIONED_UPTODATE
) {
463 statusString
= info
.getShortStatusText();
466 String revisionString
= ""; // NOI18N
467 String binaryString
= ""; // NOI18N
469 if (needRevisionForFormat
) {
470 if ((status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
) == 0) {
472 File repository
= Git
.getInstance().getTopmostManagedParent(file
);
473 String revStr
= GitCommand
.getLastRevision(repository
, file
);
474 if (revStr
!= null) {
475 revisionString
= revStr
;
477 } catch (GitException ex
) {
478 NotifyDescriptor
.Exception e
= new NotifyDescriptor
.Exception(ex
);
479 DialogDisplayer
.getDefault().notifyLater(e
);
484 //String stickyString = SvnUtils.getCopy(file);
485 String stickyString
= null;
486 if (stickyString
== null) {
487 stickyString
= ""; // NOI18N
490 Object
[] arguments
= new Object
[] {
496 String annotation
= format
.format(arguments
, new StringBuffer(), null).toString().trim();
497 if(annotation
.equals(emptyFormat
)) {
500 return " " + annotation
; // NOI18N
504 public String
annotateNameHtml(File file
, FileInformation info
) {
505 return annotateNameHtml(file
.getName(), info
, file
);
508 public String
annotateNameHtml(String name
, FileInformation mostImportantInfo
, File mostImportantFile
) {
509 // Git: The codes used to show the status of files are:
514 // ! = deleted, but still tracked
516 // I = ignored (not shown by default)
518 name
= htmlEncode(name
);
520 String textAnnotation
;
521 boolean annotationsVisible
= VersioningSupport
.getPreferences().getBoolean(VersioningSupport
.PREF_BOOLEAN_TEXT_ANNOTATIONS_VISIBLE
, false);
522 int status
= mostImportantInfo
.getStatus();
524 if (annotationsVisible
&& mostImportantFile
!= null && (status
& STATUS_TEXT_ANNOTABLE
) != 0) {
525 if (format
!= null) {
526 textAnnotation
= formatAnnotation(mostImportantInfo
, mostImportantFile
);
528 //String sticky = SvnUtils.getCopy(mostImportantFile);
529 String sticky
= null;
530 if (status
== FileInformation
.STATUS_VERSIONED_UPTODATE
&& sticky
== null) {
531 textAnnotation
= ""; // NOI18N
532 } else if (status
== FileInformation
.STATUS_VERSIONED_UPTODATE
) {
533 textAnnotation
= " [" + sticky
+ "]"; // NOI18N
534 } else if (sticky
== null) {
535 String statusText
= mostImportantInfo
.getShortStatusText();
536 if(!statusText
.equals("")) { // NOI18N
537 textAnnotation
= " [" + mostImportantInfo
.getShortStatusText() + "]"; // NOI18N
539 textAnnotation
= ""; // NOI18N
542 textAnnotation
= " [" + mostImportantInfo
.getShortStatusText() + "; " + sticky
+ "]"; // NOI18N
546 textAnnotation
= ""; // NOI18N
549 if (textAnnotation
.length() > 0) {
550 textAnnotation
= NbBundle
.getMessage(GitAnnotator
.class, "textAnnotation", textAnnotation
); // NOI18N
553 if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
)) {
554 return excludedFormat
.format(new Object
[] { name
, textAnnotation
});
555 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_DELETEDLOCALLY
)) {
556 return deletedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
557 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDLOCALLY
)) {
558 return removedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
559 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
)) {
560 return newLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
561 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
)) {
562 return addedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
563 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
)) {
564 return modifiedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
565 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_UPTODATE
)) {
566 return uptodateFormat
.format(new Object
[] { name
, textAnnotation
});
567 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_CONFLICT
)) {
568 return conflictFormat
.format(new Object
[] { name
, textAnnotation
});
569 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NOTMANAGED
)) {
571 } else if (status
== FileInformation
.STATUS_UNKNOWN
) {
574 throw new IllegalArgumentException("Uncomparable status: " + status
); // NOI18N
578 private String
htmlEncode(String name
) {
579 if (name
.indexOf('<') == -1) return name
;
580 return lessThan
.matcher(name
).replaceAll("<"); // NOI18N
583 private String
annotateFolderNameHtml(String name
, FileInformation mostImportantInfo
, File mostImportantFile
) {
584 String nameHtml
= htmlEncode(name
);
585 if (mostImportantInfo
.getStatus() == FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
){
586 return excludedFormat
.format(new Object
[] { nameHtml
, ""}); // NOI18N
588 String fileName
= mostImportantFile
.getName();
589 if (fileName
.equals(name
)){
590 return uptodateFormat
.format(new Object
[] { nameHtml
, "" }); // NOI18N
593 // Label top level repository nodes with a repository name label when:
594 // Display Name (name) is different from its repo name (repo.getName())
596 File repo
= Git
.getInstance().getTopmostManagedParent(mostImportantFile
);
597 if(repo
!= null && repo
.equals(mostImportantFile
)){
598 if (!repo
.getName().equals(name
)){
599 fileName
= repo
.getName();
602 if (fileName
!= null)
603 return uptodateFormat
.format(new Object
[] { nameHtml
, " [" + fileName
+ "]" }); // NOI18N
605 return uptodateFormat
.format(new Object
[] { nameHtml
, "" }); // NOI18N
608 private boolean isMoreImportant(FileInformation a
, FileInformation b
) {
609 if (b
== null) return true;
610 if (a
== null) return false;
611 return getComparableStatus(a
.getStatus()) < getComparableStatus(b
.getStatus());
615 * Gets integer status that can be used in comparators. The more important the status is for the user,
616 * the lower value it has. Conflict is 0, unknown status is 100.
618 * @return status constant suitable for 'by importance' comparators
620 public static int getComparableStatus(int status
) {
621 if (0 != (status
& FileInformation
.STATUS_VERSIONED_CONFLICT
)) {
623 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MERGE
)) {
625 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_DELETEDLOCALLY
)) {
627 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDLOCALLY
)) {
629 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
)) {
631 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
)) {
633 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
)) {
635 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDINREPOSITORY
)) {
637 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_NEWINREPOSITORY
)) {
639 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDINREPOSITORY
)) {
641 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_UPTODATE
)) {
643 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
)) {
645 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NOTMANAGED
)) {
647 } else if (status
== FileInformation
.STATUS_UNKNOWN
) {
650 throw new IllegalArgumentException("Uncomparable status: " + status
); // NOI18N
654 private boolean isExcludedFromCommit(String absolutePath
) {
658 private boolean isNothingVersioned(File
[] files
) {
659 for (File file
: files
) {
660 if ((cache
.getStatus(file
).getStatus() & FileInformation
.STATUS_MANAGED
) != 0) return false;
665 private static boolean onlyProjects(Node
[] nodes
) {
666 if (nodes
== null) return false;
667 for (Node node
: nodes
) {
668 if (node
.getLookup().lookup(Project
.class) == null) return false;
673 private boolean onlyFolders(File
[] files
) {
674 for (int i
= 0; i
< files
.length
; i
++) {
675 if (files
[i
].isFile()) return false;
676 if (!files
[i
].exists() && !cache
.getStatus(files
[i
]).isDirectory()) return false;
681 private void reScheduleScan(int delayMillis
) {
682 File dirToScan
= dirsToScan
.peek();
683 if (!folderToScan
.equals(dirToScan
)) {
684 if (!dirsToScan
.offer(folderToScan
)) {
685 Git
.LOG
.log(Level
.FINE
, "reScheduleScan failed to add to dirsToScan queue: {0} ", folderToScan
);
688 scanTask
.schedule(delayMillis
);
691 private class ScanTask
implements Runnable
{
693 Thread
.interrupted();
694 File dirToScan
= dirsToScan
.poll();
695 if (dirToScan
!= null) {
696 cache
.getScannedFiles(dirToScan
, null);
697 dirToScan
= dirsToScan
.peek();
698 if (dirToScan
!= null) {
699 scanTask
.schedule(1000);