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
.ApplyDiffAction
;
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
.stash
.StashAction
;
82 import org
.netbeans
.modules
.git
.ui
.status
.StatusAction
;
83 import org
.netbeans
.modules
.git
.ui
.update
.ConflictResolvedAction
;
84 import org
.netbeans
.modules
.git
.ui
.update
.ResolveConflictsAction
;
85 import org
.netbeans
.modules
.git
.ui
.update
.RevertModificationsAction
;
86 import org
.netbeans
.modules
.git
.ui
.update
.UpdateAction
;
87 import org
.netbeans
.modules
.git
.ui
.view
.ViewAction
;
88 import org
.netbeans
.modules
.git
.util
.GitCommand
;
89 import org
.netbeans
.modules
.git
.util
.GitUtils
;
90 import org
.netbeans
.modules
.versioning
.spi
.VCSAnnotator
;
91 import org
.netbeans
.modules
.versioning
.spi
.VCSContext
;
92 import org
.netbeans
.modules
.versioning
.spi
.VersioningSupport
;
93 import org
.netbeans
.modules
.versioning
.util
.Utils
;
94 import org
.openide
.DialogDisplayer
;
95 import org
.openide
.NotifyDescriptor
;
96 import org
.openide
.nodes
.Node
;
97 import org
.openide
.util
.NbBundle
;
98 import org
.openide
.util
.RequestProcessor
;
99 import org
.openide
.util
.Utilities
;
102 * Responsible for coloring file labels and file icons in the IDE and providing
103 * IDE with menu items.
105 * @author Maros Sandor
108 public class GitAnnotator
extends VCSAnnotator
{
110 private static final int INITIAL_ACTION_ARRAY_LENGTH
= 25;
111 private static MessageFormat uptodateFormat
= getFormat("uptodateFormat"); // NOI18N
112 private static MessageFormat newLocallyFormat
= getFormat("newLocallyFormat"); // NOI18N
113 private static MessageFormat addedLocallyFormat
= getFormat("addedLocallyFormat"); // NOI18N
114 private static MessageFormat modifiedLocallyFormat
= getFormat("modifiedLocallyFormat"); // NOI18N
115 private static MessageFormat removedLocallyFormat
= getFormat("removedLocallyFormat"); // NOI18N
116 private static MessageFormat deletedLocallyFormat
= getFormat("deletedLocallyFormat"); // NOI18N
117 private static MessageFormat excludedFormat
= getFormat("excludedFormat"); // NOI18N
118 private static MessageFormat conflictFormat
= getFormat("conflictFormat"); // NOI18N
120 private static final int STATUS_TEXT_ANNOTABLE
=
121 FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
|
122 FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
|
123 FileInformation
.STATUS_VERSIONED_UPTODATE
|
124 FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
|
125 FileInformation
.STATUS_VERSIONED_CONFLICT
|
126 FileInformation
.STATUS_VERSIONED_REMOVEDLOCALLY
|
127 FileInformation
.STATUS_VERSIONED_DELETEDLOCALLY
|
128 FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
;
130 private static final Pattern lessThan
= Pattern
.compile("<"); // NOI18N
132 private static final int STATUS_BADGEABLE
=
133 FileInformation
.STATUS_VERSIONED_UPTODATE
|
134 FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
|
135 FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
;
137 public static String ANNOTATION_REVISION
= "revision"; // NOI18N
138 public static String ANNOTATION_STATUS
= "status"; // NOI18N
139 public static String ANNOTATION_FOLDER
= "folder"; // NOI18N
141 public static String
[] LABELS
= new String
[] {ANNOTATION_REVISION
, ANNOTATION_STATUS
, ANNOTATION_FOLDER
};
143 private FileStatusCache cache
;
144 private MessageFormat format
;
145 private String emptyFormat
;
146 private Boolean needRevisionForFormat
;
147 private File folderToScan
;
148 private ConcurrentLinkedQueue
<File
> dirsToScan
= new ConcurrentLinkedQueue
<File
>();
149 private RequestProcessor
.Task scanTask
;
150 private static final RequestProcessor rp
= new RequestProcessor("GitAnnotateScan", 1, true); // NOI18N
152 public GitAnnotator() {
153 cache
= Git
.getInstance().getFileStatusCache();
154 scanTask
= rp
.create(new ScanTask());
158 private void initDefaults() {
159 Field
[] fields
= GitAnnotator
.class.getDeclaredFields();
160 for (int i
= 0; i
< fields
.length
; i
++) {
161 String name
= fields
[i
].getName();
162 if (name
.endsWith("Format")) { // NOI18N
163 initDefaultColor(name
.substring(0, name
.length() - 6));
169 public void refresh() {
170 String string
= GitModuleConfig
.getDefault().getAnnotationFormat();
171 if (string
!= null && !string
.trim().equals("")) { // NOI18N
172 needRevisionForFormat
= isRevisionInAnnotationFormat(string
);
173 string
= string
.replaceAll("\\{revision\\}", "\\{0\\}"); // NOI18N
174 string
= string
.replaceAll("\\{status\\}", "\\{1\\}"); // NOI18N
175 string
= string
.replaceAll("\\{folder\\}", "\\{2\\}"); // NOI18N
176 format
= new MessageFormat(string
);
177 emptyFormat
= format
.format(new String
[] {"", "", ""} , new StringBuffer(), null).toString().trim(); // NOI18N
181 public static boolean isRevisionInAnnotationFormat(String str
){
182 if (str
.indexOf("{revision}") != -1) { // NOI18N
189 private void initDefaultColor(String name
) {
190 String color
= System
.getProperty("git.color." + name
); // NOI18N
191 if (color
== null) return;
192 setAnnotationColor(name
, color
);
196 * Changes annotation color of files.
198 * @param name name of the color to change. Can be one of:
199 * newLocally, addedLocally, modifiedLocally, removedLocally, deletedLocally, newInRepository, modifiedInRepository,
200 * removedInRepository, conflict, mergeable, excluded.
201 * @param colorString new color in the format: 4455AA (RGB hexadecimal)
203 private void setAnnotationColor(String name
, String colorString
) {
205 Field field
= GitAnnotator
.class.getDeclaredField(name
+ "Format"); // NOI18N
206 MessageFormat msgFormat
= new MessageFormat("<font color=\"" + colorString
+ "\">{0}</font><font color=\"#999999\">{1}</font>"); // NOI18N
207 field
.set(null, msgFormat
);
208 } catch (Exception e
) {
209 throw new IllegalArgumentException("Invalid color name"); // NOI18N
213 private static MessageFormat
getFormat(String key
) {
214 String format
= NbBundle
.getMessage(GitAnnotator
.class, key
);
215 return new MessageFormat(format
);
219 public String
annotateName(String name
, VCSContext context
) {
220 int includeStatus
= FileInformation
.STATUS_VERSIONED_UPTODATE
| FileInformation
.STATUS_LOCAL_CHANGE
| FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
;
222 FileInformation mostImportantInfo
= null;
223 File mostImportantFile
= null;
224 boolean folderAnnotation
= false;
226 for (final File file
: context
.getRootFiles()) {
227 FileInformation info
= cache
.getCachedStatus(file
, true);
229 File parentFile
= file
.getParentFile();
230 Git
.LOG
.log(Level
.FINE
, "null cached status for: {0} {1} {2}", new Object
[] {file
, folderToScan
, parentFile
});
231 folderToScan
= parentFile
;
232 reScheduleScan(1000);
233 info
= new FileInformation(FileInformation
.STATUS_VERSIONED_UPTODATE
, false);
235 int status
= info
.getStatus();
236 if ((status
& includeStatus
) == 0) continue;
238 if (isMoreImportant(info
, mostImportantInfo
)) {
239 mostImportantInfo
= info
;
240 mostImportantFile
= file
;
241 folderAnnotation
= file
.isDirectory();
245 if (folderAnnotation
== false && context
.getRootFiles().size() > 1) {
246 folderAnnotation
= !Utils
.shareCommonDataObject(context
.getRootFiles().toArray(new File
[context
.getRootFiles().size()]));
249 if (mostImportantInfo
== null) return null;
250 return folderAnnotation ?
251 annotateFolderNameHtml(name
, mostImportantInfo
, mostImportantFile
) :
252 annotateNameHtml(name
, mostImportantInfo
, mostImportantFile
);
256 public Image
annotateIcon(Image icon
, VCSContext context
) {
257 boolean folderAnnotation
= false;
258 for (File file
: context
.getRootFiles()) {
259 if (file
.isDirectory()) {
260 folderAnnotation
= true;
265 if (folderAnnotation
== false && context
.getRootFiles().size() > 1) {
266 folderAnnotation
= !Utils
.shareCommonDataObject(context
.getRootFiles().toArray(new File
[context
.getRootFiles().size()]));
269 if (folderAnnotation
== false) {
273 boolean isVersioned
= false;
274 for (Iterator i
= context
.getRootFiles().iterator(); i
.hasNext();) {
275 File file
= (File
) i
.next();
276 // There is an assumption here that annotateName was already
277 // called and FileStatusCache.getStatus was scheduled if
278 // FileStatusCache.getCachedStatus returned null.
279 FileInformation info
= cache
.getCachedStatus(file
, true);
280 if ((info
!= null && (info
.getStatus() & STATUS_BADGEABLE
) != 0)) {
285 if (!isVersioned
) return null;
287 boolean allExcluded
= true;
288 boolean modified
= false;
290 Map
<File
, FileInformation
> map
= cache
.getAllModifiedFiles();
291 Map
<File
, FileInformation
> modifiedFiles
= new HashMap
<File
, FileInformation
>();
292 for (Iterator i
= map
.keySet().iterator(); i
.hasNext();) {
293 File file
= (File
) i
.next();
294 FileInformation info
= map
.get(file
);
295 if ((info
.getStatus() & FileInformation
.STATUS_LOCAL_CHANGE
) != 0) modifiedFiles
.put(file
, info
);
298 for (Iterator i
= context
.getRootFiles().iterator(); i
.hasNext();) {
299 File file
= (File
) i
.next();
300 if (VersioningSupport
.isFlat(file
)) {
301 for (Iterator j
= modifiedFiles
.keySet().iterator(); j
.hasNext();) {
302 File mf
= (File
) j
.next();
303 if (mf
.getParentFile().equals(file
)) {
304 FileInformation info
= modifiedFiles
.get(mf
);
305 if (info
.isDirectory()) continue;
306 int status
= info
.getStatus();
307 if (status
== FileInformation
.STATUS_VERSIONED_CONFLICT
) {
308 Image badge
= Utilities
.loadImage("org/netbeans/modules/git/resources/icons/conflicts-badge.png", true); // NOI18N
309 return Utilities
.mergeImages(icon
, badge
, 16, 9);
312 allExcluded
&= isExcludedFromCommit(mf
.getAbsolutePath());
316 for (Iterator j
= modifiedFiles
.keySet().iterator(); j
.hasNext();) {
317 File mf
= (File
) j
.next();
318 if (Utils
.isAncestorOrEqual(file
, mf
)) {
319 FileInformation info
= modifiedFiles
.get(mf
);
320 int status
= info
.getStatus();
321 if ((status
== FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
|| status
== FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
) && file
.equals(mf
)) {
324 if (status
== FileInformation
.STATUS_VERSIONED_CONFLICT
) {
325 Image badge
= Utilities
.loadImage("org/netbeans/modules/git/resources/icons/conflicts-badge.png", true); // NOI18N
326 return Utilities
.mergeImages(icon
, badge
, 16, 9);
329 allExcluded
&= isExcludedFromCommit(mf
.getAbsolutePath());
335 if (modified
&& !allExcluded
) {
336 Image badge
= Utilities
.loadImage("org/netbeans/modules/git/resources/icons/modified-badge.png", true); // NOI18N
337 return Utilities
.mergeImages(icon
, badge
, 16, 9);
344 public Action
[] getActions(VCSContext ctx
, VCSAnnotator
.ActionDestination destination
) {
345 // TODO: get resource strings for all actions:
346 ResourceBundle loc
= NbBundle
.getBundle(GitAnnotator
.class);
347 Node
[] nodes
= ctx
.getElements().lookupAll(Node
.class).toArray(new Node
[0]);
348 File
[] files
= ctx
.getRootFiles().toArray(new File
[ctx
.getRootFiles().size()]);
349 File root
= GitUtils
.getRootFile(ctx
);
350 boolean noneVersioned
= root
== null;
351 boolean onlyFolders
= onlyFolders(files
);
352 boolean onlyProjects
= onlyProjects(nodes
);
354 List
<Action
> actions
= new ArrayList
<Action
>(INITIAL_ACTION_ARRAY_LENGTH
);
355 if (destination
== VCSAnnotator
.ActionDestination
.MainMenu
) {
356 actions
.add(new CreateAction(loc
.getString("CTL_MenuItem_Create"), ctx
)); // NOI18N
358 actions
.add(new StatusAction(loc
.getString("CTL_PopupMenuItem_Status"), ctx
)); // NOI18N
359 actions
.add(new DiffAction(loc
.getString("CTL_PopupMenuItem_Diff"), ctx
)); // NOI18N
360 actions
.add(new UpdateAction(loc
.getString("CTL_PopupMenuItem_Update"), ctx
)); // NOI18N
361 actions
.add(new CommitAction(loc
.getString("CTL_PopupMenuItem_Commit"), ctx
)); // NOI18N
363 actions
.add(new ExportDiffAction(loc
.getString("CTL_PopupMenuItem_ExportDiff"), ctx
)); // NOI18N
364 actions
.add(new ApplyDiffAction(loc
.getString("CTL_PopupMenuItem_ImportDiff"), ctx
)); // NOI18N
368 actions
.add(new CloneAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_CloneLocal", // NOI18N
369 root
.getName()), ctx
));
371 actions
.add(new CloneExternalAction(loc
.getString("CTL_PopupMenuItem_CloneOther"), ctx
)); // NOI18N
373 actions
.add(new FetchAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_FetchLocal"), ctx
)); // NOI18N
374 actions
.add(new PushAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_PushLocal"), ctx
)); // NOI18N
375 actions
.add(new PushOtherAction(loc
.getString("CTL_PopupMenuItem_PushOther"), ctx
)); // NOI18N
376 actions
.add(new PullAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_PullLocal"), ctx
)); // NOI18N
377 actions
.add(new PullOtherAction(loc
.getString("CTL_PopupMenuItem_PullOther"), ctx
)); // NOI18N
378 actions
.add(new MergeAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Merge"), ctx
)); // NOI18N
380 AnnotateAction tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_ShowAnnotations"), ctx
); // NOI18N
381 if (tempA
.visible(nodes
)) {
382 tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_HideAnnotations"), ctx
); // NOI18N
385 actions
.add(new LogAction(loc
.getString("CTL_PopupMenuItem_Log"), ctx
)); // NOI18N
386 actions
.add(new IncomingAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowIncoming"), ctx
)); // NOI18N
387 actions
.add(new OutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowOut"), ctx
)); // NOI18N
388 actions
.add(new ViewAction(loc
.getString("CTL_PopupMenuItem_View"), ctx
)); // NOI18N
390 actions
.add(new RevertModificationsAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Revert"), ctx
)); // NOI18N
391 actions
.add(new StashAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Stash"), ctx
));
392 actions
.add(new StripAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Strip"), ctx
)); // NOI18N
393 actions
.add(new BackoutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Backout"), ctx
)); // NOI18N
394 actions
.add(new RollbackAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Rollback"), ctx
)); // NOI18N
395 actions
.add(new ResolveConflictsAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Resolve"), ctx
)); // NOI18N
396 if (!onlyProjects
&& !onlyFolders
) {
397 IgnoreAction tempIA
= new IgnoreAction(loc
.getString("CTL_PopupMenuItem_Ignore"), ctx
); // NOI18N
401 actions
.add(new PropertiesAction(loc
.getString("CTL_PopupMenuItem_Properties"), ctx
)); // NOI18N
404 actions
.add(new CreateAction(loc
.getString("CTL_PopupMenuItem_Create"), ctx
)); // NOI18N
406 actions
.add(new StatusAction(loc
.getString("CTL_PopupMenuItem_Status"), ctx
)); // NOI18N
407 actions
.add(new DiffAction(loc
.getString("CTL_PopupMenuItem_Diff"), ctx
)); // NOI18N
408 actions
.add(new UpdateAction(loc
.getString("CTL_PopupMenuItem_Update"), ctx
)); // NOI18N
409 actions
.add(new CommitAction(loc
.getString("CTL_PopupMenuItem_Commit"), ctx
)); // NOI18N
412 actions
.add(new CloneAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_CloneLocal", // NOI18N
413 root
.getName()), ctx
));
417 actions
.add(new FetchAction(NbBundle
.getMessage(GitAnnotator
.class,
418 "CTL_PopupMenuItem_FetchLocal"), ctx
)); // NOI18N
419 actions
.add(new PushAction(NbBundle
.getMessage(GitAnnotator
.class,
420 "CTL_PopupMenuItem_PushLocal"), ctx
)); // NOI18N
421 actions
.add(new PullAction(NbBundle
.getMessage(GitAnnotator
.class,
422 "CTL_PopupMenuItem_PullLocal"), ctx
)); // NOI18N
423 actions
.add(new MergeAction(NbBundle
.getMessage(GitAnnotator
.class,
424 "CTL_PopupMenuItem_Merge"), ctx
)); // NOI18N
428 AnnotateAction tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_ShowAnnotations"), ctx
); // NOI18N
429 if (tempA
.visible(nodes
)) {
430 tempA
= new AnnotateAction(loc
.getString("CTL_PopupMenuItem_HideAnnotations"), ctx
); // NOI18N
434 actions
.add(new LogAction(loc
.getString("CTL_PopupMenuItem_Log"), ctx
)); // NOI18N
435 actions
.add(new IncomingAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowIncoming"), ctx
)); // NOI18N
436 actions
.add(new OutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_ShowOut"), ctx
)); // NOI18N
437 actions
.add(new ViewAction(loc
.getString("CTL_PopupMenuItem_View"), ctx
)); // NOI18N
439 actions
.add(new RevertModificationsAction(NbBundle
.getMessage(GitAnnotator
.class,
440 "CTL_PopupMenuItem_Revert"), ctx
)); // NOI18N
441 actions
.add(new StripAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Strip"), ctx
)); // NOI18N
442 actions
.add(new BackoutAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Backout"), ctx
)); // NOI18N
443 actions
.add(new RollbackAction(NbBundle
.getMessage(GitAnnotator
.class, "CTL_PopupMenuItem_Rollback"), ctx
)); // NOI18N
444 actions
.add(new ResolveConflictsAction(NbBundle
.getMessage(GitAnnotator
.class,
445 "CTL_PopupMenuItem_Resolve"), ctx
)); // NOI18N
446 if (!onlyProjects
&& !onlyFolders
) {
447 actions
.add(new ConflictResolvedAction(NbBundle
.getMessage(GitAnnotator
.class,
448 "CTL_PopupMenuItem_MarkResolved"), ctx
)); // NOI18N
450 IgnoreAction tempIA
= new IgnoreAction(loc
.getString("CTL_PopupMenuItem_Ignore"), ctx
); // NOI18N
454 actions
.add(new PropertiesAction(loc
.getString("CTL_PopupMenuItem_Properties"), ctx
)); // NOI18N
457 return actions
.toArray(new Action
[actions
.size()]);
461 * Applies custom format.
463 private String
formatAnnotation(FileInformation info
, File file
) {
464 String statusString
= ""; // NOI18N
465 int status
= info
.getStatus();
466 if (status
!= FileInformation
.STATUS_VERSIONED_UPTODATE
) {
467 statusString
= info
.getShortStatusText();
470 String revisionString
= ""; // NOI18N
471 String binaryString
= ""; // NOI18N
473 if (needRevisionForFormat
) {
474 if ((status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
) == 0) {
476 File repository
= Git
.getInstance().getTopmostManagedParent(file
);
477 String revStr
= GitCommand
.getLastRevision(repository
, file
);
478 if (revStr
!= null) {
479 revisionString
= revStr
;
481 } catch (GitException ex
) {
482 NotifyDescriptor
.Exception e
= new NotifyDescriptor
.Exception(ex
);
483 DialogDisplayer
.getDefault().notifyLater(e
);
488 //String stickyString = SvnUtils.getCopy(file);
489 String stickyString
= null;
490 if (stickyString
== null) {
491 stickyString
= ""; // NOI18N
494 Object
[] arguments
= new Object
[] {
500 String annotation
= format
.format(arguments
, new StringBuffer(), null).toString().trim();
501 if(annotation
.equals(emptyFormat
)) {
504 return " " + annotation
; // NOI18N
508 public String
annotateNameHtml(File file
, FileInformation info
) {
509 return annotateNameHtml(file
.getName(), info
, file
);
512 public String
annotateNameHtml(String name
, FileInformation mostImportantInfo
, File mostImportantFile
) {
513 // Git: The codes used to show the status of files are:
518 // ! = deleted, but still tracked
520 // I = ignored (not shown by default)
522 name
= htmlEncode(name
);
524 String textAnnotation
;
525 boolean annotationsVisible
= VersioningSupport
.getPreferences().getBoolean(VersioningSupport
.PREF_BOOLEAN_TEXT_ANNOTATIONS_VISIBLE
, false);
526 int status
= mostImportantInfo
.getStatus();
528 if (annotationsVisible
&& mostImportantFile
!= null && (status
& STATUS_TEXT_ANNOTABLE
) != 0) {
529 if (format
!= null) {
530 textAnnotation
= formatAnnotation(mostImportantInfo
, mostImportantFile
);
532 //String sticky = SvnUtils.getCopy(mostImportantFile);
533 String sticky
= null;
534 if (status
== FileInformation
.STATUS_VERSIONED_UPTODATE
&& sticky
== null) {
535 textAnnotation
= ""; // NOI18N
536 } else if (status
== FileInformation
.STATUS_VERSIONED_UPTODATE
) {
537 textAnnotation
= " [" + sticky
+ "]"; // NOI18N
538 } else if (sticky
== null) {
539 String statusText
= mostImportantInfo
.getShortStatusText();
540 if(!statusText
.equals("")) { // NOI18N
541 textAnnotation
= " [" + mostImportantInfo
.getShortStatusText() + "]"; // NOI18N
543 textAnnotation
= ""; // NOI18N
546 textAnnotation
= " [" + mostImportantInfo
.getShortStatusText() + "; " + sticky
+ "]"; // NOI18N
550 textAnnotation
= ""; // NOI18N
553 if (textAnnotation
.length() > 0) {
554 textAnnotation
= NbBundle
.getMessage(GitAnnotator
.class, "textAnnotation", textAnnotation
); // NOI18N
557 if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
)) {
558 return excludedFormat
.format(new Object
[] { name
, textAnnotation
});
559 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_DELETEDLOCALLY
)) {
560 return deletedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
561 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDLOCALLY
)) {
562 return removedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
563 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
)) {
564 return newLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
565 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
)) {
566 return addedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
567 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
)) {
568 return modifiedLocallyFormat
.format(new Object
[] { name
, textAnnotation
});
569 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_UPTODATE
)) {
570 return uptodateFormat
.format(new Object
[] { name
, textAnnotation
});
571 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_CONFLICT
)) {
572 return conflictFormat
.format(new Object
[] { name
, textAnnotation
});
573 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NOTMANAGED
)) {
575 } else if (status
== FileInformation
.STATUS_UNKNOWN
) {
578 throw new IllegalArgumentException("Uncomparable status: " + status
); // NOI18N
582 private String
htmlEncode(String name
) {
583 if (name
.indexOf('<') == -1) return name
;
584 return lessThan
.matcher(name
).replaceAll("<"); // NOI18N
587 private String
annotateFolderNameHtml(String name
, FileInformation mostImportantInfo
, File mostImportantFile
) {
588 String nameHtml
= htmlEncode(name
);
589 if (mostImportantInfo
.getStatus() == FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
){
590 return excludedFormat
.format(new Object
[] { nameHtml
, ""}); // NOI18N
592 String fileName
= mostImportantFile
.getName();
593 if (fileName
.equals(name
)){
594 return uptodateFormat
.format(new Object
[] { nameHtml
, "" }); // NOI18N
597 // Label top level repository nodes with a repository name label when:
598 // Display Name (name) is different from its repo name (repo.getName())
600 File repo
= Git
.getInstance().getTopmostManagedParent(mostImportantFile
);
601 if(repo
!= null && repo
.equals(mostImportantFile
)){
602 if (!repo
.getName().equals(name
)){
603 fileName
= repo
.getName();
606 if (fileName
!= null)
607 return uptodateFormat
.format(new Object
[] { nameHtml
, " [" + fileName
+ "]" }); // NOI18N
609 return uptodateFormat
.format(new Object
[] { nameHtml
, "" }); // NOI18N
612 private boolean isMoreImportant(FileInformation a
, FileInformation b
) {
613 if (b
== null) return true;
614 if (a
== null) return false;
615 return getComparableStatus(a
.getStatus()) < getComparableStatus(b
.getStatus());
619 * Gets integer status that can be used in comparators. The more important the status is for the user,
620 * the lower value it has. Conflict is 0, unknown status is 100.
622 * @return status constant suitable for 'by importance' comparators
624 public static int getComparableStatus(int status
) {
625 if (0 != (status
& FileInformation
.STATUS_VERSIONED_CONFLICT
)) {
627 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MERGE
)) {
629 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_DELETEDLOCALLY
)) {
631 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDLOCALLY
)) {
633 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
)) {
635 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_ADDEDLOCALLY
)) {
637 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
)) {
639 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_REMOVEDINREPOSITORY
)) {
641 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_NEWINREPOSITORY
)) {
643 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_MODIFIEDINREPOSITORY
)) {
645 } else if (0 != (status
& FileInformation
.STATUS_VERSIONED_UPTODATE
)) {
647 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
)) {
649 } else if (0 != (status
& FileInformation
.STATUS_NOTVERSIONED_NOTMANAGED
)) {
651 } else if (status
== FileInformation
.STATUS_UNKNOWN
) {
654 throw new IllegalArgumentException("Uncomparable status: " + status
); // NOI18N
658 private boolean isExcludedFromCommit(String absolutePath
) {
662 private boolean isNothingVersioned(File
[] files
) {
663 for (File file
: files
) {
664 if ((cache
.getStatus(file
).getStatus() & FileInformation
.STATUS_MANAGED
) != 0) return false;
669 private static boolean onlyProjects(Node
[] nodes
) {
670 if (nodes
== null) return false;
671 for (Node node
: nodes
) {
672 if (node
.getLookup().lookup(Project
.class) == null) return false;
677 private boolean onlyFolders(File
[] files
) {
678 for (int i
= 0; i
< files
.length
; i
++) {
679 if (files
[i
].isFile()) return false;
680 if (!files
[i
].exists() && !cache
.getStatus(files
[i
]).isDirectory()) return false;
685 private void reScheduleScan(int delayMillis
) {
686 File dirToScan
= dirsToScan
.peek();
687 if (!folderToScan
.equals(dirToScan
)) {
688 if (!dirsToScan
.offer(folderToScan
)) {
689 Git
.LOG
.log(Level
.FINE
, "reScheduleScan failed to add to dirsToScan queue: {0} ", folderToScan
);
692 scanTask
.schedule(delayMillis
);
695 private class ScanTask
implements Runnable
{
697 Thread
.interrupted();
698 File dirToScan
= dirsToScan
.poll();
699 if (dirToScan
!= null) {
700 cache
.getScannedFiles(dirToScan
, null);
701 dirToScan
= dirsToScan
.peek();
702 if (dirToScan
!= null) {
703 scanTask
.schedule(1000);