Adding Git source, NetBeans project files, GPL v2 LICENSE, ant build file.
[nbgit.git] / src / org / netbeans / modules / git / ui / annotate / AnnotateAction.java
blobaaee894d736986bf62edc9d67a49ca7afebf9deb
1 /*
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]"
24 * Contributor(s):
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;
45 import java.io.File;
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;
80 /**
81 * Annotate action for Git:
82 * git annotate - show changeset information per file line
84 * @author John Rice
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);
95 @Override
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) {
107 return false;
108 } else {
109 return true;
111 } else {
112 return false;
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);
121 } else {
122 EditorCookie ec = activatedEditorCookie(nodes);
123 if (ec == null) return;
125 final File file = activatedFile(nodes);
127 JEditorPane[] panes = ec.getOpenedPanes();
128 if (panes == null) {
129 ec.open();
132 panes = ec.getOpenedPanes();
133 if (panes == null) {
134 return;
136 final JEditorPane currentPane = panes[0];
137 final TopComponent tc = (TopComponent) SwingUtilities.getAncestorOfClass(TopComponent.class, currentPane);
138 tc.requestActive();
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();
150 logger.outputInRed(
151 NbBundle.getMessage(AnnotateAction.class,
152 "MSG_ANNOTATE_TITLE")); // NOI18N
153 logger.outputInRed(
154 NbBundle.getMessage(AnnotateAction.class,
155 "MSG_ANNOTATE_TITLE_SEP")); // NOI18N
156 computeAnnotations(repository, file, this, ab);
157 logger.output("\t" + file.getAbsolutePath()); // NOI18N
158 logger.outputInRed(
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;
169 try {
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;
177 return;
179 if (list == null) return;
180 AnnotateLine [] lines = toAnnotateLines(list);
181 try {
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()) {
188 return;
190 GitLogMessage [] logs = toGitLogMessages(list);
191 if (logs == null) return;
192 fillCommitMessages(lines, logs);
193 ab.setLogs(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()
207 )) {
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;
224 int i = 0;
225 for (Iterator j = lines.iterator(); j.hasNext();) {
226 String line = (String) j.next();
227 if (i % 4 == 0) {
228 log = new GitLogMessage();
229 try {
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
238 try {
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);
246 logs.add(log);
248 i++;
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()];
261 int i = 0;
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);
265 if (!m.matches()){
266 Git.LOG.log(Level.WARNING, "AnnotateAction: toAnnotateLines(): Failed when matching: {0}", new Object[] {line}); //NOI18N
267 continue;
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);
275 i++;
277 return lines;
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);
295 if (ec != null) {
296 JEditorPane[] panes = ec.getOpenedPanes();
297 if (panes != null && panes.length > 0) {
298 return panes[0];
301 return null;
304 private EditorCookie activatedEditorCookie(Node[] nodes) {
305 if (nodes == null) {
306 nodes = WindowManager.getDefault().getRegistry().getActivatedNodes();
308 if (nodes.length == 1) {
309 Node node = nodes[0];
310 return (EditorCookie) node.getCookie(EditorCookie.class);
312 return null;
315 private File activatedFile(Node[] nodes) {
316 if (nodes.length == 1) {
317 Node node = nodes[0];
318 DataObject dobj = (DataObject) node.getCookie(DataObject.class);
319 if (dobj != null) {
320 FileObject fo = dobj.getPrimaryFile();
321 return FileUtil.toFile(fo);
324 return null;