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
.ui
.annotate
;
44 import java
.awt
.event
.ActionEvent
;
46 import java
.util
.ArrayList
;
47 import java
.util
.Arrays
;
48 import java
.util
.Date
;
49 import java
.util
.Iterator
;
50 import java
.util
.List
;
51 import java
.util
.logging
.Level
;
52 import java
.util
.regex
.Matcher
;
53 import java
.util
.regex
.Pattern
;
54 import javax
.swing
.Action
;
55 import javax
.swing
.JEditorPane
;
56 import javax
.swing
.SwingUtilities
;
57 import org
.netbeans
.modules
.git
.FileInformation
;
58 import org
.netbeans
.modules
.git
.FileStatusCache
;
59 import org
.netbeans
.modules
.git
.Git
;
60 import org
.netbeans
.modules
.git
.GitException
;
61 import org
.netbeans
.modules
.git
.GitProgressSupport
;
62 import org
.netbeans
.modules
.git
.OutputLogger
;
63 import org
.netbeans
.modules
.git
.ui
.actions
.ContextAction
;
64 import org
.netbeans
.modules
.git
.util
.GitCommand
;
65 import org
.netbeans
.modules
.git
.util
.GitLogMessage
;
66 import org
.netbeans
.modules
.git
.util
.GitUtils
;
67 import org
.netbeans
.modules
.versioning
.spi
.VCSContext
;
68 import org
.openide
.DialogDisplayer
;
69 import org
.openide
.NotifyDescriptor
;
70 import org
.openide
.cookies
.EditorCookie
;
71 import org
.openide
.filesystems
.FileObject
;
72 import org
.openide
.filesystems
.FileUtil
;
73 import org
.openide
.loaders
.DataObject
;
74 import org
.openide
.nodes
.Node
;
75 import org
.openide
.util
.NbBundle
;
76 import org
.openide
.util
.RequestProcessor
;
77 import org
.openide
.windows
.TopComponent
;
78 import org
.openide
.windows
.WindowManager
;
81 * Annotate action for Git:
82 * git annotate - show changeset information per file line
86 public class AnnotateAction
extends ContextAction
{
88 private final VCSContext context
;
90 public AnnotateAction(String name
, VCSContext context
) {
91 this.context
= context
;
92 putValue(Action
.NAME
, name
);
96 public boolean isEnabled() {
97 File repository
= GitUtils
.getRootFile(context
);
98 if (repository
== null) return false;
100 Node
[] nodes
= context
.getElements().lookupAll(Node
.class).toArray(new Node
[0]);
101 if (context
.getRootFiles().size() > 0 && activatedEditorCookie(nodes
) != null) {
102 FileStatusCache cache
= Git
.getInstance().getFileStatusCache();
103 File file
= activatedFile(nodes
);
104 int status
= cache
.getStatus(file
).getStatus();
105 if (status
== FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
||
106 status
== FileInformation
.STATUS_NOTVERSIONED_EXCLUDED
) {
116 public void performAction(ActionEvent e
) {
117 Node
[] nodes
= context
.getElements().lookupAll(Node
.class).toArray(new Node
[0]);
118 if (visible(nodes
)) {
119 JEditorPane pane
= activatedEditorPane(nodes
);
120 AnnotationBarManager
.hideAnnotationBar(pane
);
122 EditorCookie ec
= activatedEditorCookie(nodes
);
123 if (ec
== null) return;
125 final File file
= activatedFile(nodes
);
127 JEditorPane
[] panes
= ec
.getOpenedPanes();
132 panes
= ec
.getOpenedPanes();
136 final JEditorPane currentPane
= panes
[0];
137 final TopComponent tc
= (TopComponent
) SwingUtilities
.getAncestorOfClass(TopComponent
.class, currentPane
);
140 final AnnotationBar ab
= AnnotationBarManager
.showAnnotationBar(currentPane
);
141 ab
.setAnnotationMessage(NbBundle
.getMessage(AnnotateAction
.class, "CTL_AnnotationSubstitute")); // NOI18N;
143 final File repository
= GitUtils
.getRootFile(context
);
144 if (repository
== null) return;
146 RequestProcessor rp
= Git
.getInstance().getRequestProcessor(repository
);
147 GitProgressSupport support
= new GitProgressSupport() {
148 public void perform() {
149 OutputLogger logger
= getLogger();
151 NbBundle
.getMessage(AnnotateAction
.class,
152 "MSG_ANNOTATE_TITLE")); // NOI18N
154 NbBundle
.getMessage(AnnotateAction
.class,
155 "MSG_ANNOTATE_TITLE_SEP")); // NOI18N
156 computeAnnotations(repository
, file
, this, ab
);
157 logger
.output("\t" + file
.getAbsolutePath()); // NOI18N
159 NbBundle
.getMessage(AnnotateAction
.class,
160 "MSG_ANNOTATE_DONE")); // NOI18N
163 support
.start(rp
, repository
.getAbsolutePath(), NbBundle
.getMessage(AnnotateAction
.class, "MSG_Annotation_Progress")); // NOI18N
167 private void computeAnnotations(File repository
, File file
, GitProgressSupport progress
, AnnotationBar ab
) {
168 List
<String
> list
= null;
170 list
= GitCommand
.doAnnotate(repository
, file
, progress
.getLogger());
171 } catch (GitException ex
) {
172 NotifyDescriptor
.Exception e
= new NotifyDescriptor
.Exception(ex
);
173 DialogDisplayer
.getDefault().notifyLater(e
);
175 if (progress
.isCanceled()) {
176 ab
.setAnnotationMessage(NbBundle
.getMessage(AnnotateAction
.class, "CTL_AnnotationFailed")); // NOI18N;
179 if (list
== null) return;
180 AnnotateLine
[] lines
= toAnnotateLines(list
);
182 list
= GitCommand
.doLogShort(repository
, file
, progress
.getLogger());
183 } catch (GitException ex
) {
184 NotifyDescriptor
.Exception e
= new NotifyDescriptor
.Exception(ex
);
185 DialogDisplayer
.getDefault().notifyLater(e
);
187 if (progress
.isCanceled()) {
190 GitLogMessage
[] logs
= toGitLogMessages(list
);
191 if (logs
== null) return;
192 fillCommitMessages(lines
, logs
);
194 ab
.annotationLines(file
, Arrays
.asList(lines
));
197 private static void fillCommitMessages(AnnotateLine
[] annotations
, GitLogMessage
[] logs
) {
198 long lowestRevisionNumber
= Long
.MAX_VALUE
;
199 for (int i
= 0; i
< annotations
.length
; i
++) {
200 AnnotateLine annotation
= annotations
[i
];
201 for (int j
= 0; j
< logs
.length
; j
++) {
202 GitLogMessage log
= logs
[j
];
203 if (log
.getRevision() < lowestRevisionNumber
) {
204 lowestRevisionNumber
= log
.getRevision();
206 if (annotation
.getRevision().equals(log
.getRevision().toString()
208 annotation
.setDate(log
.getDate());
209 annotation
.setCommitMessage(log
.getCommitMessage());
213 String lowestRev
= Long
.toString(lowestRevisionNumber
);
214 for (int i
= 0; i
< annotations
.length
; i
++) {
215 AnnotateLine annotation
= annotations
[i
];
216 annotation
.setCanBeRolledBack(!annotation
.getRevision().equals(lowestRev
));
220 private static GitLogMessage
[] toGitLogMessages(List
<String
> lines
) {
221 List
<GitLogMessage
> logs
= new ArrayList
<GitLogMessage
>();
222 GitLogMessage log
= null;
225 for (Iterator j
= lines
.iterator(); j
.hasNext();) {
226 String line
= (String
) j
.next();
228 log
= new GitLogMessage();
230 log
.setRevision(Long
.parseLong(line
));
231 } catch (java
.lang
.Exception e
) {
232 Git
.LOG
.log(Level
.SEVERE
, "Caught Exception while parsing revision", e
); // NOI18N
234 } else if (i
% 4 == 1) {
235 log
.setCommitMessage(line
);
236 } else if (i
% 4 == 2) {
237 String splits
[] = line
.split(" ", 2); // NOI18N
239 log
.setDate(new Date(Long
.parseLong(splits
[0].trim()) * 1000));
240 } catch (java
.lang
.Exception e
) {
241 Git
.LOG
.log(Level
.SEVERE
, "Caught Exception while parsing date", e
); // NOI18N
243 log
.setTimeZoneOffset(splits
[1]);
244 } else if (i
% 4 == 3) {
245 log
.setChangeSet(line
);
250 return logs
.toArray(new GitLogMessage
[logs
.size()]);
253 private static AnnotateLine
[] toAnnotateLines(List
<String
> annotations
)
255 final int GROUP_AUTHOR
= 1;
256 final int GROUP_REVISION
= 2;
257 final int GROUP_FILENAME
= 3;
258 final int GROUP_CONTENT
= 4;
260 AnnotateLine
[] lines
= new AnnotateLine
[annotations
.size()];
262 Pattern p
= Pattern
.compile("^\\s*(\\w+\\b)\\s+(\\d+)\\s+(\\b\\S*):\\s(.*)$"); //NOI18N
263 for (String line
: annotations
) {
264 Matcher m
= p
.matcher(line
);
266 Git
.LOG
.log(Level
.WARNING
, "AnnotateAction: toAnnotateLines(): Failed when matching: {0}", new Object
[] {line
}); //NOI18N
269 lines
[i
] = new AnnotateLine();
270 lines
[i
].setAuthor(m
.group(GROUP_AUTHOR
));
271 lines
[i
].setRevision(m
.group(GROUP_REVISION
));
272 lines
[i
].setFileName(m
.group(GROUP_FILENAME
));
273 lines
[i
].setContent(m
.group(GROUP_CONTENT
));
274 lines
[i
].setLineNum(i
+ 1);
281 * @param nodes or null (then taken from windowsystem, it may be wrong on editor tabs #66700).
283 public boolean visible(Node
[] nodes
) {
284 JEditorPane currentPane
= activatedEditorPane(nodes
);
285 return AnnotationBarManager
.annotationBarVisible(currentPane
);
290 * @return active editor pane or null if selected node
291 * does not have any or more nodes selected.
293 private JEditorPane
activatedEditorPane(Node
[] nodes
) {
294 EditorCookie ec
= activatedEditorCookie(nodes
);
296 JEditorPane
[] panes
= ec
.getOpenedPanes();
297 if (panes
!= null && panes
.length
> 0) {
304 private EditorCookie
activatedEditorCookie(Node
[] nodes
) {
306 nodes
= WindowManager
.getDefault().getRegistry().getActivatedNodes();
308 if (nodes
.length
== 1) {
309 Node node
= nodes
[0];
310 return (EditorCookie
) node
.getCookie(EditorCookie
.class);
315 private File
activatedFile(Node
[] nodes
) {
316 if (nodes
.length
== 1) {
317 Node node
= nodes
[0];
318 DataObject dobj
= (DataObject
) node
.getCookie(DataObject
.class);
320 FileObject fo
= dobj
.getPrimaryFile();
321 return FileUtil
.toFile(fo
);