001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.mercurial.ui.commit;
042:
043: import org.netbeans.modules.mercurial.HgException;
044: import org.netbeans.modules.mercurial.HgProgressSupport;
045: import org.netbeans.modules.versioning.util.DialogBoundsPreserver;
046: import org.netbeans.modules.versioning.spi.VCSContext;
047: import org.netbeans.modules.versioning.util.Utils;
048: import org.netbeans.modules.mercurial.Mercurial;
049: import org.netbeans.modules.mercurial.OutputLogger;
050: import org.netbeans.modules.mercurial.FileStatusCache;
051: import org.netbeans.modules.mercurial.FileInformation;
052: import org.netbeans.modules.mercurial.HgFileNode;
053: import org.netbeans.modules.mercurial.HgModuleConfig;
054: import org.netbeans.modules.mercurial.ui.actions.ContextAction;
055: import org.netbeans.modules.mercurial.util.HgUtils;
056: import org.netbeans.modules.mercurial.util.HgRepositoryContextCache;
057: import org.netbeans.modules.mercurial.util.HgProjectUtils;
058: import org.openide.DialogDescriptor;
059: import org.openide.DialogDisplayer;
060: import org.openide.util.HelpCtx;
061: import org.netbeans.modules.versioning.util.VersioningListener;
062: import org.netbeans.modules.versioning.util.VersioningEvent;
063: import javax.swing.*;
064: import java.awt.*;
065: import java.io.File;
066: import java.util.ArrayList;
067: import java.util.List;
068: import java.util.Iterator;
069: import java.util.Map;
070: import javax.swing.event.TableModelEvent;
071: import javax.swing.event.TableModelListener;
072: import java.awt.event.ActionEvent;
073: import org.openide.nodes.Node;
074: import java.text.MessageFormat;
075: import java.util.ResourceBundle;
076: import java.util.Set;
077: import java.util.HashSet;
078: import org.netbeans.modules.mercurial.util.HgCommand;
079: import org.openide.util.RequestProcessor;
080: import org.openide.util.NbBundle;
081: import org.openide.DialogDisplayer;
082: import org.openide.NotifyDescriptor;
083:
084: /**
085: * Commit action for mercurial:
086: * hg commit - commit the specified files or all outstanding changes
087: *
088: * @author John Rice
089: */
090: public class CommitAction extends ContextAction {
091:
092: static final String RECENT_COMMIT_MESSAGES = "recentCommitMessage"; // NOI18N
093:
094: private final VCSContext context;
095:
096: public CommitAction(String name, VCSContext context) {
097: this .context = context;
098: putValue(Action.NAME, name);
099: }
100:
101: public boolean isEnabled() {
102: FileStatusCache cache = Mercurial.getInstance()
103: .getFileStatusCache();
104: return cache.containsFileOfStatus(context,
105: FileInformation.STATUS_LOCAL_CHANGE);
106: }
107:
108: public void performAction(ActionEvent e) {
109: final File root = HgUtils.getRootFile(context);
110: if (root == null) {
111: OutputLogger logger = OutputLogger
112: .getLogger(Mercurial.MERCURIAL_OUTPUT_TAB_TITLE);
113: logger.outputInRed(NbBundle.getMessage(CommitAction.class,
114: "MSG_COMMIT_TITLE")); // NOI18N
115: logger.outputInRed(NbBundle.getMessage(CommitAction.class,
116: "MSG_COMMIT_TITLE_SEP")); // NOI18N
117: logger.outputInRed(NbBundle.getMessage(CommitAction.class,
118: "MSG_COMMIT_NOT_SUPPORTED_INVIEW_INFO")); // NOI18N
119: logger.output(""); // NOI18N
120: logger.closeLog();
121: JOptionPane.showMessageDialog(null, NbBundle.getMessage(
122: CommitAction.class,
123: "MSG_COMMIT_NOT_SUPPORTED_INVIEW"),// NOI18N
124: NbBundle.getMessage(CommitAction.class,
125: "MSG_COMMIT_NOT_SUPPORTED_INVIEW_TITLE"),// NOI18N
126: JOptionPane.INFORMATION_MESSAGE);
127: return;
128: }
129: String contentTitle = Utils.getContextDisplayName(context);
130:
131: commit(contentTitle, context);
132: }
133:
134: public static void commit(String contentTitle, final VCSContext ctx) {
135: FileStatusCache cache = Mercurial.getInstance()
136: .getFileStatusCache();
137: File[] roots = ctx.getRootFiles().toArray(
138: new File[ctx.getRootFiles().size()]);
139: if (roots == null || roots.length == 0) {
140: return;
141: }
142:
143: final File repository = HgUtils.getRootFile(ctx);
144: if (repository == null)
145: return;
146: String projName = HgProjectUtils.getProjectName(repository);
147: if (projName == null) {
148: File projFile = HgUtils.getProjectFile(ctx);
149: projName = HgProjectUtils.getProjectName(projFile);
150: }
151: final String prjName = projName;
152:
153: File[][] split = Utils.splitFlatOthers(roots);
154: List<File> fileList = new ArrayList<File>();
155: for (int c = 0; c < split.length; c++) {
156: roots = split[c];
157: boolean recursive = c == 1;
158: if (recursive) {
159: File[] files = cache.listFiles(ctx,
160: FileInformation.STATUS_LOCAL_CHANGE);
161: for (int i = 0; i < files.length; i++) {
162: for (int r = 0; r < roots.length; r++) {
163: if (HgUtils.isParentOrEqual(roots[r], files[i])) {
164: if (!fileList.contains(files[i])) {
165: fileList.add(files[i]);
166: }
167: }
168: }
169: }
170: } else {
171: File[] files = HgUtils.flatten(roots,
172: FileInformation.STATUS_LOCAL_CHANGE);
173: for (int i = 0; i < files.length; i++) {
174: if (!fileList.contains(files[i])) {
175: fileList.add(files[i]);
176: }
177: }
178: }
179: }
180:
181: if (fileList.size() == 0) {
182: return;
183: }
184:
185: // show commit dialog
186: final CommitPanel panel = new CommitPanel();
187: final CommitTable data = new CommitTable(panel.filesLabel,
188: CommitTable.COMMIT_COLUMNS,
189: new String[] { CommitTableModel.COLUMN_NAME_PATH });
190:
191: panel.setCommitTable(data);
192:
193: HgFileNode[] nodes;
194: ArrayList<HgFileNode> nodesList = new ArrayList<HgFileNode>(
195: fileList.size());
196:
197: for (Iterator<File> it = fileList.iterator(); it.hasNext();) {
198: File file = it.next();
199: HgFileNode node = new HgFileNode(file);
200: nodesList.add(node);
201: }
202: nodes = nodesList.toArray(new HgFileNode[fileList.size()]);
203: data.setNodes(nodes);
204:
205: JComponent component = data.getComponent();
206: panel.filesPanel.setLayout(new BorderLayout());
207: panel.filesPanel.add(component, BorderLayout.CENTER);
208:
209: DialogDescriptor dd = new DialogDescriptor(panel,
210: org.openide.util.NbBundle.getMessage(
211: CommitAction.class, "CTL_CommitDialog_Title",
212: contentTitle)); // NOI18N
213: dd.setModal(true);
214: final JButton commitButton = new JButton();
215: org.openide.awt.Mnemonics
216: .setLocalizedText(commitButton,
217: org.openide.util.NbBundle.getMessage(
218: CommitAction.class,
219: "CTL_Commit_Action_Commit"));
220: commitButton.getAccessibleContext().setAccessibleName(
221: org.openide.util.NbBundle
222: .getMessage(CommitAction.class,
223: "ACSN_Commit_Action_Commit"));
224: commitButton.getAccessibleContext().setAccessibleDescription(
225: org.openide.util.NbBundle
226: .getMessage(CommitAction.class,
227: "ACSD_Commit_Action_Commit"));
228: final JButton cancelButton = new JButton(
229: org.openide.util.NbBundle.getMessage(
230: CommitAction.class, "CTL_Commit_Action_Cancel")); // NOI18N
231: org.openide.awt.Mnemonics
232: .setLocalizedText(cancelButton,
233: org.openide.util.NbBundle.getMessage(
234: CommitAction.class,
235: "CTL_Commit_Action_Cancel"));
236: cancelButton.getAccessibleContext().setAccessibleName(
237: org.openide.util.NbBundle
238: .getMessage(CommitAction.class,
239: "ACSN_Commit_Action_Cancel"));
240: cancelButton.getAccessibleContext().setAccessibleDescription(
241: org.openide.util.NbBundle
242: .getMessage(CommitAction.class,
243: "ACSD_Commit_Action_Cancel"));
244:
245: commitButton.setEnabled(false);
246: dd.setOptions(new Object[] { commitButton, cancelButton });
247: dd.setHelpCtx(new HelpCtx(CommitAction.class));
248: panel.addVersioningListener(new VersioningListener() {
249: public void versioningEvent(VersioningEvent event) {
250: refreshCommitDialog(panel, data, commitButton);
251: }
252: });
253: data.getTableModel().addTableModelListener(
254: new TableModelListener() {
255: public void tableChanged(TableModelEvent e) {
256: refreshCommitDialog(panel, data, commitButton);
257: }
258: });
259: commitButton.setEnabled(containsCommitable(data));
260:
261: panel.putClientProperty("contentTitle", contentTitle); // NOI18N
262: panel.putClientProperty("DialogDescriptor", dd); // NOI18N
263: final Dialog dialog = DialogDisplayer.getDefault()
264: .createDialog(dd);
265:
266: dialog.addWindowListener(new DialogBoundsPreserver(
267: HgModuleConfig.getDefault().getPreferences(),
268: "hg.commit.dialog")); // NOI18N
269: dialog.pack();
270: dialog.setVisible(true);
271:
272: if (dd.getValue() == commitButton) {
273:
274: final Map<HgFileNode, CommitOptions> commitFiles = data
275: .getCommitFiles();
276: final String message = panel.messageTextArea.getText();
277: org.netbeans.modules.versioning.util.Utils.insert(
278: HgModuleConfig.getDefault().getPreferences(),
279: RECENT_COMMIT_MESSAGES, message, 20);
280: RequestProcessor rp = Mercurial.getInstance()
281: .getRequestProcessor(repository.getAbsolutePath());
282: HgProgressSupport support = new HgProgressSupport() {
283: public void perform() {
284: OutputLogger logger = getLogger();
285: performCommit(message, commitFiles, ctx, this ,
286: prjName, logger);
287: }
288: };
289: support.start(rp, repository.getAbsolutePath(),
290: org.openide.util.NbBundle.getMessage(
291: CommitAction.class, "LBL_Commit_Progress")); // NOI18N
292: }
293: }
294:
295: private static boolean containsCommitable(CommitTable data) {
296: Map<HgFileNode, CommitOptions> map = data.getCommitFiles();
297: for (CommitOptions co : map.values()) {
298: if (co != CommitOptions.EXCLUDE) {
299: return true;
300: }
301: }
302: return false;
303: }
304:
305: /**
306: * User changed a commit action.
307: *
308: * @param panel
309: * @param commit
310: */
311: private static void refreshCommitDialog(CommitPanel panel,
312: CommitTable table, JButton commit) {
313: ResourceBundle loc = NbBundle.getBundle(CommitAction.class);
314: Map<HgFileNode, CommitOptions> files = table.getCommitFiles();
315: Set<String> stickyTags = new HashSet<String>();
316: boolean conflicts = false;
317:
318: boolean enabled = commit.isEnabled();
319:
320: for (HgFileNode fileNode : files.keySet()) {
321:
322: CommitOptions options = files.get(fileNode);
323: if (options == CommitOptions.EXCLUDE)
324: continue;
325: //stickyTags.add(HgUtils.getCopy(fileNode.getFile()));
326: int status = fileNode.getInformation().getStatus();
327: if ((status & FileInformation.STATUS_REMOTE_CHANGE) != 0
328: || status == FileInformation.STATUS_VERSIONED_CONFLICT) {
329: enabled = false;
330: String msg = (status == FileInformation.STATUS_VERSIONED_CONFLICT) ? loc
331: .getString("MSG_CommitForm_ErrorConflicts")
332: : // NOI18N
333: loc
334: .getString("MSG_CommitForm_ErrorRemoteChanges"); // NOI18N
335: panel.setErrorLabel("<html><font color=\"#002080\">"
336: + msg + "</font></html>"); // NOI18N
337: conflicts = true;
338: }
339: //stickyTags.add(HgUtils.getCopy(fileNode.getFile()));
340:
341: }
342:
343: if (stickyTags.size() > 1) {
344: table.setColumns(new String[] {
345: CommitTableModel.COLUMN_NAME_NAME,
346: CommitTableModel.COLUMN_NAME_BRANCH,
347: CommitTableModel.COLUMN_NAME_STATUS,
348: CommitTableModel.COLUMN_NAME_ACTION,
349: CommitTableModel.COLUMN_NAME_PATH });
350: } else {
351: table.setColumns(new String[] {
352: CommitTableModel.COLUMN_NAME_NAME,
353: CommitTableModel.COLUMN_NAME_STATUS,
354: CommitTableModel.COLUMN_NAME_ACTION,
355: CommitTableModel.COLUMN_NAME_PATH });
356: }
357:
358: String contentTitle = (String) panel
359: .getClientProperty("contentTitle"); // NOI18N
360: // NOI18N
361: DialogDescriptor dd = (DialogDescriptor) panel
362: .getClientProperty("DialogDescriptor"); // NOI18N
363: String errorLabel;
364: if (stickyTags.size() <= 1) {
365: String stickyTag = stickyTags.size() == 0 ? null
366: : (String) stickyTags.iterator().next();
367: if (stickyTag == null) {
368: dd.setTitle(MessageFormat.format(loc
369: .getString("CTL_CommitDialog_Title"),
370: new Object[] { contentTitle })); // NOI18N
371: errorLabel = ""; // NOI18N
372: } else {
373: dd.setTitle(MessageFormat.format(loc
374: .getString("CTL_CommitDialog_Title_Branch"),
375: new Object[] { contentTitle, stickyTag })); // NOI18N
376: String msg = MessageFormat.format(loc
377: .getString("MSG_CommitForm_InfoBranch"),
378: new Object[] { stickyTag }); // NOI18N
379: errorLabel = "<html><font color=\"#002080\">" + msg
380: + "</font></html>"; // NOI18N
381: }
382: } else {
383: dd.setTitle(MessageFormat.format(loc
384: .getString("CTL_CommitDialog_Title_Branches"),
385: new Object[] { contentTitle })); // NOI18N
386: String msg = loc
387: .getString("MSG_CommitForm_ErrorMultipleBranches"); // NOI18N
388: errorLabel = "<html><font color=\"#CC0000\">" + msg
389: + "</font></html>"; // NOI18N
390: }
391: if (!conflicts) {
392: panel.setErrorLabel(errorLabel);
393: enabled = true;
394: }
395: commit.setEnabled(enabled && containsCommitable(table));
396: }
397:
398: private static void performCommit(String message,
399: Map<HgFileNode, CommitOptions> commitFiles, VCSContext ctx,
400: HgProgressSupport support, String prjName,
401: OutputLogger logger) {
402: FileStatusCache cache = Mercurial.getInstance()
403: .getFileStatusCache();
404: final File repository = HgUtils.getRootFile(ctx);
405: List<File> addCandidates = new ArrayList<File>();
406: List<File> deleteCandidates = new ArrayList<File>();
407: List<File> commitCandidates = new ArrayList<File>();
408: Iterator<HgFileNode> it = commitFiles.keySet().iterator();
409:
410: List<String> excPaths = new ArrayList<String>();
411: List<String> incPaths = new ArrayList<String>();
412: while (it.hasNext()) {
413: if (support.isCanceled()) {
414: return;
415: }
416: HgFileNode node = it.next();
417: CommitOptions option = commitFiles.get(node);
418: if (option != CommitOptions.EXCLUDE) {
419: int status = cache.getStatus(node.getFile())
420: .getStatus();
421: if ((status & FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY) != 0) {
422: addCandidates.add(node.getFile());
423: } else if ((status & FileInformation.STATUS_VERSIONED_DELETEDLOCALLY) != 0) {
424: deleteCandidates.add(node.getFile());
425: }
426: commitCandidates.add(node.getFile());
427: incPaths.add(node.getFile().getAbsolutePath());
428: } else {
429: excPaths.add(node.getFile().getAbsolutePath());
430: }
431: }
432: if (support.isCanceled()) {
433: return;
434: }
435:
436: if (!excPaths.isEmpty()) {
437: HgModuleConfig.getDefault().addExclusionPaths(excPaths);
438: }
439: if (!incPaths.isEmpty()) {
440: HgModuleConfig.getDefault().removeExclusionPaths(incPaths);
441: }
442:
443: try {
444: logger.outputInRed(NbBundle.getMessage(CommitAction.class,
445: "MSG_COMMIT_TITLE")); // NOI18N
446: logger.outputInRed(NbBundle.getMessage(CommitAction.class,
447: "MSG_COMMIT_TITLE_SEP")); // NOI18N
448: logger.output(message); // NOI18N
449: if (addCandidates.size() > 0) {
450: HgCommand.doAdd(repository, addCandidates, logger);
451: for (File f : addCandidates) {
452: logger.output("hg add " + f.getName()); //NOI18N
453: }
454: }
455: if (deleteCandidates.size() > 0) {
456: HgCommand
457: .doRemove(repository, deleteCandidates, logger);
458: for (File f : deleteCandidates) {
459: logger.output("hg delete " + f.getName()); //NOI18N
460: }
461: }
462: HgCommand.doCommit(repository, commitCandidates, message,
463: logger);
464: HgRepositoryContextCache.setHasHistory(ctx);
465:
466: if (commitCandidates.size() == 1) {
467: logger.output(NbBundle.getMessage(CommitAction.class,
468: "MSG_COMMIT_INIT_SEP_ONE", commitCandidates
469: .size(), prjName)); // NOI18N
470: } else {
471: logger.output(NbBundle.getMessage(CommitAction.class,
472: "MSG_COMMIT_INIT_SEP", commitCandidates.size(),
473: prjName)); // NOI18N
474: }
475: for (File f : commitCandidates) {
476: logger.output("\t" + f.getAbsolutePath()); // NOI18N
477: }
478: } catch (HgException ex) {
479: NotifyDescriptor.Exception e = new NotifyDescriptor.Exception(
480: ex);
481: DialogDisplayer.getDefault().notifyLater(e);
482: } finally {
483: cache.refreshCached(ctx);
484: logger.outputInRed(NbBundle.getMessage(CommitAction.class,
485: "MSG_COMMIT_DONE")); // NOI18N
486: logger.output(""); // NOI18N
487: }
488: }
489: }
|