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:
042: package org.netbeans.modules.subversion.ui.commit;
043:
044: import org.netbeans.modules.versioning.util.DialogBoundsPreserver;
045: import org.netbeans.modules.subversion.client.SvnClient;
046: import org.netbeans.modules.subversion.ui.actions.ContextAction;
047: import org.netbeans.modules.subversion.util.Context;
048: import org.netbeans.modules.subversion.*;
049: import org.openide.DialogDescriptor;
050: import org.openide.DialogDisplayer;
051: import org.openide.nodes.Node;
052: import org.tigris.subversion.svnclientadapter.SVNBaseDir;
053: import org.tigris.subversion.svnclientadapter.SVNClientException;
054: import javax.swing.*;
055: import java.awt.*;
056: import java.io.File;
057: import java.util.*;
058: import java.util.List;
059: import java.text.MessageFormat;
060: import javax.swing.event.TableModelEvent;
061: import javax.swing.event.TableModelListener;
062: import org.netbeans.modules.subversion.client.SvnClientExceptionHandler;
063: import org.netbeans.modules.subversion.client.SvnProgressSupport;
064: import org.netbeans.modules.subversion.util.SvnUtils;
065: import org.netbeans.modules.versioning.util.VersioningListener;
066: import org.netbeans.modules.versioning.util.VersioningEvent;
067: import org.netbeans.modules.versioning.util.Utils;
068: import org.openide.util.HelpCtx;
069: import org.openide.util.RequestProcessor;
070: import org.openide.util.NbBundle;
071: import org.tigris.subversion.svnclientadapter.ISVNProperty;
072: import org.tigris.subversion.svnclientadapter.SVNRevision;
073: import org.tigris.subversion.svnclientadapter.SVNUrl;
074:
075: /**
076: * Commit action
077: *
078: * @author Petr Kuzel
079: */
080: public class CommitAction extends ContextAction {
081:
082: static final String RECENT_COMMIT_MESSAGES = "recentCommitMessage";
083:
084: protected String getBaseName(Node[] nodes) {
085: return "CTL_MenuItem_Commit"; // NOI18N
086: }
087:
088: protected boolean enable(Node[] nodes) {
089: if (isDeepRefresh()) {
090: // allway true as we have will accept and check for external changes
091: // and we don't about them yet
092: return true;
093: }
094: // XXX could be a performace issue, maybe a msg box in commit would be enough
095: FileStatusCache cache = Subversion.getInstance()
096: .getStatusCache();
097: File[] files = cache.listFiles(getContext(nodes),
098: FileInformation.STATUS_LOCAL_CHANGE);
099: return files.length > 0;
100: }
101:
102: /** Run commit action. Shows UI */
103: public static void commit(String contentTitle, final Context ctx) {
104: if (!Subversion.getInstance().checkClientAvailable()) {
105: return;
106: }
107:
108: if (isDeepRefresh()) {
109: commitAllChanges(contentTitle, ctx);
110: } else {
111: commitKnownChanges(contentTitle, ctx);
112: }
113: }
114:
115: private static boolean isDeepRefresh() {
116: String noDeepRefresh = System
117: .getProperty("netbeans.subversion.commit.deepStatusRefresh"); // NOI18N
118: return noDeepRefresh != null
119: && !noDeepRefresh.trim().equals("");
120: }
121:
122: /**
123: * Opens the commit dialog displaying all changed files from the status cache which belong to the given context.
124: * There is no guarantee that changes made outside of the IDE will be recognized
125: *
126: * @param contentTitle
127: * @param ctx
128: */
129: public static void commitKnownChanges(String contentTitle,
130: final Context ctx) {
131:
132: // get files list
133: List<File> fileList = getFiles(ctx);
134: if (fileList.size() == 0) {
135: return;
136: }
137:
138: // show commit dialog
139: final CommitPanel panel = new CommitPanel();
140: final CommitTable data = new CommitTable(panel.filesLabel,
141: CommitTable.COMMIT_COLUMNS,
142: new String[] { CommitTableModel.COLUMN_NAME_PATH });
143: panel.setCommitTable(data);
144:
145: data.setNodes(getFileNodes(fileList));
146:
147: final JButton commitButton = new JButton();
148: if (showCommitDialog(panel, data, commitButton, contentTitle,
149: ctx) == commitButton) {
150: // if OK setup sequence of add, remove and commit calls
151: startCommitTask(panel, data, ctx);
152: }
153:
154: }
155:
156: /**
157: * Opens the commit dialog displaying all changed files from the status cache which belong to the given context.
158: * The status for all files will be refrehed first and the commit button in the dialog stays disabled until then.
159: * It may take a while until the dialog is setup.
160: *
161: * @param contentTitle
162: * @param ctx
163: */
164: public static void commitAllChanges(String contentTitle,
165: final Context ctx) {
166:
167: final CommitPanel panel = new CommitPanel();
168: final CommitTable data = new CommitTable(panel.filesLabel,
169: CommitTable.COMMIT_COLUMNS,
170: new String[] { CommitTableModel.COLUMN_NAME_PATH });
171: panel.setCommitTable(data);
172: final JButton commitButton = new JButton();
173:
174: // start backround prepare
175: SVNUrl repository = null;
176: try {
177: repository = getSvnUrl(ctx);
178: } catch (SVNClientException ex) {
179: SvnClientExceptionHandler.notifyException(ex, true, true);
180: }
181: SvnProgressSupport prepareSupport = getPrepareSupport(ctx,
182: data, commitButton, panel);
183: RequestProcessor rp = Subversion.getInstance()
184: .getRequestProcessor(repository);
185: prepareSupport.start(rp, repository, org.openide.util.NbBundle
186: .getMessage(CommitAction.class, "BK1009")); // NOI18N
187:
188: // show commit dialog
189: if (showCommitDialog(panel, data, commitButton, contentTitle,
190: ctx) == commitButton) {
191: // if OK setup sequence of add, remove and commit calls
192: startCommitTask(panel, data, ctx);
193: } else {
194: prepareSupport.cancel();
195: }
196: }
197:
198: /**
199: * Returns all files from the given context honoring the flat folder logic
200: *
201: * @param ctx
202: * @return
203: */
204: private static List<File> getFiles(Context ctx) {
205: List<File> fileList = new ArrayList<File>();
206: // get files without exclusions
207: File[] contextFiles = ctx.getFiles();
208: if (contextFiles.length == 0) {
209: return fileList;
210: }
211:
212: FileStatusCache cache = Subversion.getInstance()
213: .getStatusCache();
214:
215: // The commits are made non recursively, so
216: // add also the roots to the to be commited list.
217: List<File> rootFiles = ctx.getRoots();
218: Set<File> filesSet = new HashSet<File>();
219: for (File file : contextFiles) {
220: filesSet.add(file);
221: }
222: for (File file : rootFiles) {
223: filesSet.add(file);
224: }
225: contextFiles = filesSet.toArray(new File[filesSet.size()]);
226:
227: // get all changed files while honoring the flat folder logic
228: File[][] split = Utils.splitFlatOthers(contextFiles);
229: for (int c = 0; c < split.length; c++) {
230: contextFiles = split[c];
231: boolean recursive = c == 1;
232: if (recursive) {
233: File[] files = cache.listFiles(ctx,
234: FileInformation.STATUS_LOCAL_CHANGE);
235: for (int i = 0; i < files.length; i++) {
236: for (int r = 0; r < contextFiles.length; r++) {
237: if (SvnUtils.isParentOrEqual(contextFiles[r],
238: files[i])) {
239: if (!fileList.contains(files[i])) {
240: fileList.add(files[i]);
241: }
242: }
243: }
244: }
245: } else {
246: File[] files = SvnUtils.flatten(contextFiles,
247: FileInformation.STATUS_LOCAL_CHANGE);
248: for (int i = 0; i < files.length; i++) {
249: if (!fileList.contains(files[i])) {
250: fileList.add(files[i]);
251: }
252: }
253: }
254: }
255: return fileList;
256: }
257:
258: /**
259: * Returns a SvnFileNode for each given file
260: *
261: * @param fileList
262: * @return
263: */
264: private static SvnFileNode[] getFileNodes(List<File> fileList) {
265: SvnFileNode[] nodes;
266: ArrayList<SvnFileNode> nodesList = new ArrayList<SvnFileNode>(
267: fileList.size());
268:
269: for (Iterator<File> it = fileList.iterator(); it.hasNext();) {
270: File file = it.next();
271: SvnFileNode node = new SvnFileNode(file);
272: nodesList.add(node);
273: }
274: nodes = nodesList.toArray(new SvnFileNode[fileList.size()]);
275: return nodes;
276: }
277:
278: /**
279: * Opens the commit dlg
280: *
281: * @param panel
282: * @param data
283: * @param commitButton
284: * @param contentTitle
285: * @param ctx
286: * @return
287: */
288: private static Object showCommitDialog(final CommitPanel panel,
289: final CommitTable data, final JButton commitButton,
290: String contentTitle, final Context ctx) {
291: JComponent component = data.getComponent();
292: panel.filesPanel.setLayout(new BorderLayout());
293: panel.filesPanel.add(component, BorderLayout.CENTER);
294:
295: DialogDescriptor dd = new DialogDescriptor(panel,
296: org.openide.util.NbBundle.getMessage(
297: CommitAction.class, "CTL_CommitDialog_Title",
298: contentTitle)); // NOI18N
299: dd.setModal(true);
300: org.openide.awt.Mnemonics
301: .setLocalizedText(commitButton,
302: org.openide.util.NbBundle.getMessage(
303: CommitAction.class,
304: "CTL_Commit_Action_Commit"));
305: commitButton.getAccessibleContext().setAccessibleName(
306: org.openide.util.NbBundle
307: .getMessage(CommitAction.class,
308: "ACSN_Commit_Action_Commit"));
309: commitButton.getAccessibleContext().setAccessibleDescription(
310: org.openide.util.NbBundle
311: .getMessage(CommitAction.class,
312: "ACSD_Commit_Action_Commit"));
313: final JButton cancelButton = new JButton(
314: org.openide.util.NbBundle.getMessage(
315: CommitAction.class, "CTL_Commit_Action_Cancel")); // NOI18N
316: org.openide.awt.Mnemonics
317: .setLocalizedText(cancelButton,
318: org.openide.util.NbBundle.getMessage(
319: CommitAction.class,
320: "CTL_Commit_Action_Cancel"));
321: cancelButton.getAccessibleContext().setAccessibleName(
322: org.openide.util.NbBundle
323: .getMessage(CommitAction.class,
324: "ACSN_Commit_Action_Cancel"));
325: cancelButton.getAccessibleContext().setAccessibleDescription(
326: org.openide.util.NbBundle
327: .getMessage(CommitAction.class,
328: "ACSD_Commit_Action_Cancel"));
329:
330: commitButton.setEnabled(false);
331: dd.setOptions(new Object[] { commitButton, cancelButton }); // NOI18N
332: dd.setHelpCtx(new HelpCtx(CommitAction.class));
333: panel.addVersioningListener(new VersioningListener() {
334: public void versioningEvent(VersioningEvent event) {
335: refreshCommitDialog(panel, data, commitButton);
336: }
337: });
338: data.getTableModel().addTableModelListener(
339: new TableModelListener() {
340: public void tableChanged(TableModelEvent e) {
341: refreshCommitDialog(panel, data, commitButton);
342: }
343: });
344: commitButton.setEnabled(containsCommitable(data));
345:
346: panel.putClientProperty("contentTitle", contentTitle); // NOI18N
347: panel.putClientProperty("DialogDescriptor", dd); // NOI18N
348: final Dialog dialog = DialogDisplayer.getDefault()
349: .createDialog(dd);
350: dialog.addWindowListener(new DialogBoundsPreserver(
351: SvnModuleConfig.getDefault().getPreferences(),
352: "svn.commit.dialog")); // NOI18N
353: dialog.pack();
354: dialog.setVisible(true);
355:
356: return dd.getValue();
357: }
358:
359: private static void startCommitTask(final CommitPanel panel,
360: final CommitTable data, final Context ctx) {
361: final Map<SvnFileNode, CommitOptions> commitFiles = data
362: .getCommitFiles();
363: final String message = panel.messageTextArea.getText();
364: org.netbeans.modules.versioning.util.Utils.insert(
365: SvnModuleConfig.getDefault().getPreferences(),
366: RECENT_COMMIT_MESSAGES, message, 20);
367:
368: SVNUrl repository = null;
369: try {
370: repository = getSvnUrl(ctx);
371: } catch (SVNClientException ex) {
372: SvnClientExceptionHandler.notifyException(ex, true, true);
373: }
374: RequestProcessor rp = Subversion.getInstance()
375: .getRequestProcessor(repository);
376: SvnProgressSupport support = new SvnProgressSupport() {
377: public void perform() {
378: performCommit(message, commitFiles, ctx, this );
379: }
380: };
381: support.start(rp, repository, org.openide.util.NbBundle
382: .getMessage(CommitAction.class, "LBL_Commit_Progress")); // NOI18N
383: }
384:
385: private static SvnProgressSupport getPrepareSupport(
386: final Context ctx, final CommitTable data,
387: final JButton commitButton, final CommitPanel panel) {
388: SvnProgressSupport support = new SvnProgressSupport() {
389: public void perform() {
390: try {
391: // get files without exclusions
392: File[] contextFiles = ctx.getFiles();
393: if (contextFiles.length == 0) {
394: return;
395: }
396:
397: // The commits are made non recursively, so
398: // add also the roots to the to be commited list.
399: List<File> rootFiles = ctx.getRoots();
400: Set<File> filesSet = new HashSet<File>();
401: for (File file : contextFiles) {
402: filesSet.add(file);
403: }
404: for (File file : rootFiles) {
405: filesSet.add(file);
406: }
407: contextFiles = filesSet.toArray(new File[filesSet
408: .size()]);
409:
410: // make a deep refresh to get the not yet notified external changes
411: FileStatusCache cache = Subversion.getInstance()
412: .getStatusCache();
413: for (File f : contextFiles) {
414: SvnUtils.refreshRecursively(f);
415: }
416: // get all changed files while honoring the flat folder logic
417: File[][] split = Utils
418: .splitFlatOthers(contextFiles);
419: List<File> fileList = new ArrayList<File>();
420: for (int c = 0; c < split.length; c++) {
421: contextFiles = split[c];
422: boolean recursive = c == 1;
423: if (recursive) {
424: File[] files = cache
425: .listFiles(
426: ctx,
427: FileInformation.STATUS_LOCAL_CHANGE);
428: for (int i = 0; i < files.length; i++) {
429: for (int r = 0; r < contextFiles.length; r++) {
430: if (SvnUtils.isParentOrEqual(
431: contextFiles[r], files[i])) {
432: if (!fileList
433: .contains(files[i])) {
434: fileList.add(files[i]);
435: }
436: }
437: }
438: }
439: } else {
440: File[] files = SvnUtils
441: .flatten(
442: contextFiles,
443: FileInformation.STATUS_LOCAL_CHANGE);
444: for (int i = 0; i < files.length; i++) {
445: if (!fileList.contains(files[i])) {
446: fileList.add(files[i]);
447: }
448: }
449: }
450: }
451:
452: if (fileList.size() == 0) {
453: return;
454: }
455:
456: ArrayList<SvnFileNode> nodesList = new ArrayList<SvnFileNode>(
457: fileList.size());
458: SvnFileNode[] nodes;
459: for (Iterator<File> it = fileList.iterator(); it
460: .hasNext();) {
461: File file = it.next();
462: SvnFileNode node = new SvnFileNode(file);
463: nodesList.add(node);
464: }
465: nodes = nodesList.toArray(new SvnFileNode[fileList
466: .size()]);
467: data.setNodes(nodes);
468: } finally {
469: commitButton.setEnabled(containsCommitable(data));
470:
471: panel
472: .addVersioningListener(new VersioningListener() {
473: public void versioningEvent(
474: VersioningEvent event) {
475: refreshCommitDialog(panel, data,
476: commitButton);
477: }
478: });
479: data.getTableModel().addTableModelListener(
480: new TableModelListener() {
481: public void tableChanged(
482: TableModelEvent e) {
483: refreshCommitDialog(panel, data,
484: commitButton);
485: }
486: });
487: }
488: }
489: };
490: return support;
491: }
492:
493: private static boolean containsCommitable(CommitTable data) {
494: Map<SvnFileNode, CommitOptions> map = data.getCommitFiles();
495: for (CommitOptions co : map.values()) {
496: if (co != CommitOptions.EXCLUDE) {
497: return true;
498: }
499: }
500: return false;
501: }
502:
503: /**
504: * User changed a commit action.
505: *
506: * @param panel
507: * @param commit
508: */
509: private static void refreshCommitDialog(CommitPanel panel,
510: CommitTable table, JButton commit) {
511: ResourceBundle loc = NbBundle.getBundle(CommitAction.class);
512: Map<SvnFileNode, CommitOptions> files = table.getCommitFiles();
513: Set<String> stickyTags = new HashSet<String>();
514: boolean conflicts = false;
515:
516: boolean enabled = commit.isEnabled();
517:
518: for (SvnFileNode fileNode : files.keySet()) {
519: CommitOptions options = files.get(fileNode);
520: if (options == CommitOptions.EXCLUDE)
521: continue;
522: stickyTags.add(SvnUtils.getCopy(fileNode.getFile()));
523: int status = fileNode.getInformation().getStatus();
524: if ((status & FileInformation.STATUS_REMOTE_CHANGE) != 0
525: || status == FileInformation.STATUS_VERSIONED_CONFLICT) {
526: enabled = false;
527: String msg = (status == FileInformation.STATUS_VERSIONED_CONFLICT) ? loc
528: .getString("MSG_CommitForm_ErrorConflicts")
529: : loc
530: .getString("MSG_CommitForm_ErrorRemoteChanges");
531: panel.setErrorLabel("<html><font color=\"#002080\">"
532: + msg + "</font></html>"); // NOI18N
533: conflicts = true;
534: }
535: }
536:
537: if (stickyTags.size() > 1) {
538: table.setColumns(new String[] {
539: CommitTableModel.COLUMN_NAME_NAME,
540: CommitTableModel.COLUMN_NAME_BRANCH,
541: CommitTableModel.COLUMN_NAME_STATUS,
542: CommitTableModel.COLUMN_NAME_ACTION,
543: CommitTableModel.COLUMN_NAME_PATH });
544: } else {
545: table.setColumns(new String[] {
546: CommitTableModel.COLUMN_NAME_NAME,
547: CommitTableModel.COLUMN_NAME_STATUS,
548: CommitTableModel.COLUMN_NAME_ACTION,
549: CommitTableModel.COLUMN_NAME_PATH });
550: }
551:
552: String contentTitle = (String) panel
553: .getClientProperty("contentTitle"); // NOI18N
554: DialogDescriptor dd = (DialogDescriptor) panel
555: .getClientProperty("DialogDescriptor"); // NOI18N
556: String errorLabel;
557: if (stickyTags.size() <= 1) {
558: String stickyTag = stickyTags.size() == 0 ? null
559: : (String) stickyTags.iterator().next();
560: if (stickyTag == null) {
561: dd.setTitle(MessageFormat.format(loc
562: .getString("CTL_CommitDialog_Title"),
563: new Object[] { contentTitle }));
564: errorLabel = ""; // NOI18N
565: } else {
566: dd.setTitle(MessageFormat.format(loc
567: .getString("CTL_CommitDialog_Title_Branch"),
568: new Object[] { contentTitle, stickyTag }));
569: String msg = MessageFormat.format(loc
570: .getString("MSG_CommitForm_InfoBranch"),
571: new Object[] { stickyTag });
572: errorLabel = "<html><font color=\"#002080\">" + msg
573: + "</font></html>"; // NOI18N
574: }
575: } else {
576: dd.setTitle(MessageFormat.format(loc
577: .getString("CTL_CommitDialog_Title_Branches"),
578: new Object[] { contentTitle }));
579: String msg = loc
580: .getString("MSG_CommitForm_ErrorMultipleBranches");
581: errorLabel = "<html><font color=\"#CC0000\">" + msg
582: + "</font></html>"; // NOI18N
583: }
584: if (!conflicts) {
585: panel.setErrorLabel(errorLabel);
586: enabled = true;
587: }
588: commit.setEnabled(enabled && containsCommitable(table));
589: }
590:
591: protected void performContextAction(Node[] nodes) {
592: if (!Subversion.getInstance().checkClientAvailable()) {
593: return;
594: }
595: final Context ctx = getContext(nodes);
596: commit(getContextDisplayName(nodes), ctx);
597: }
598:
599: public static void performCommit(String message,
600: Map<SvnFileNode, CommitOptions> commitFiles, Context ctx,
601: SvnProgressSupport support) {
602: performCommit(message, commitFiles, ctx, support, false);
603: }
604:
605: public static void performCommit(String message,
606: Map<SvnFileNode, CommitOptions> commitFiles, Context ctx,
607: SvnProgressSupport support, boolean rootUpdate) {
608: try {
609:
610: SvnClient client;
611: try {
612: client = Subversion.getInstance().getClient(ctx,
613: support);
614: } catch (SVNClientException ex) {
615: SvnClientExceptionHandler.notifyException(ex, true,
616: true); // should not hapen
617: return;
618: }
619: support.setDisplayName(org.openide.util.NbBundle
620: .getMessage(CommitAction.class,
621: "LBL_Commit_Progress")); // NOI18N
622:
623: List<SvnFileNode> addCandidates = new ArrayList<SvnFileNode>();
624: List<File> removeCandidates = new ArrayList<File>();
625: Set<File> commitCandidates = new LinkedHashSet<File>();
626: Set<File> binnaryCandidates = new HashSet<File>();
627:
628: Iterator<SvnFileNode> it = commitFiles.keySet().iterator();
629: // XXX refactor the olowing loop. there seem to be redundant blocks
630: while (it.hasNext()) {
631: if (support.isCanceled()) {
632: return;
633: }
634: SvnFileNode node = it.next();
635: CommitOptions option = commitFiles.get(node);
636: if (CommitOptions.ADD_BINARY == option) {
637: List<File> l = listUnmanagedParents(node);
638: Iterator<File> dit = l.iterator();
639: while (dit.hasNext()) {
640: if (support.isCanceled()) {
641: return;
642: }
643: File file = dit.next();
644: addCandidates.add(new SvnFileNode(file));
645: commitCandidates.add(file);
646: }
647:
648: if (support.isCanceled()) {
649: return;
650: }
651: binnaryCandidates.add(node.getFile());
652:
653: addCandidates.add(node);
654: commitCandidates.add(node.getFile());
655: } else if (CommitOptions.ADD_TEXT == option
656: || CommitOptions.ADD_DIRECTORY == option) {
657: // assute no MIME property or startin gwith text
658: List<File> l = listUnmanagedParents(node);
659: Iterator<File> dit = l.iterator();
660: while (dit.hasNext()) {
661: if (support.isCanceled()) {
662: return;
663: }
664: File file = dit.next();
665: addCandidates.add(new SvnFileNode(file));
666: commitCandidates.add(file);
667: }
668: if (support.isCanceled()) {
669: return;
670: }
671: addCandidates.add(node);
672: commitCandidates.add(node.getFile());
673: } else if (CommitOptions.COMMIT_REMOVE == option) {
674: removeCandidates.add(node.getFile());
675: commitCandidates.add(node.getFile());
676: } else if (CommitOptions.COMMIT == option) {
677: commitCandidates.add(node.getFile());
678: }
679: }
680:
681: // perform adds
682: performAdds(client, support, addCandidates);
683: if (support.isCanceled()) {
684: return;
685: }
686:
687: // TODO perform removes. especialy package removes where
688: // metadata must be replied from SvnMetadata (hold by FileSyatemHandler)
689:
690: // set binary mimetype and group commitCandidates by managed trees
691: List<List<File>> managedTrees = getManagedTrees(client,
692: support, commitCandidates, binnaryCandidates);
693: if (support.isCanceled()) {
694: return;
695: }
696:
697: // finally commit
698: for (Iterator<List<File>> itCandidates = managedTrees
699: .iterator(); itCandidates.hasNext();) {
700:
701: // one commit for each wc
702: List<File> commitList = itCandidates.next();
703:
704: // handle recursive commits - deleted and copied folders can't be commited non recursively
705: List<File> recursiveCommits = getRecursiveCommits(
706: commitList, removeCandidates);
707: if (recursiveCommits.size() > 0) {
708: // remove from the commits list all files which are supposed to be commited recursively
709: // or are children from recursively commited folders
710: commitList.removeAll(getAllChildren(
711: recursiveCommits, commitList));
712:
713: // commit recursively
714: File[] files = recursiveCommits
715: .toArray(new File[recursiveCommits.size()]);
716: client.commit(files, message, true); // true = recursive
717:
718: if (support.isCanceled()) {
719: return;
720: }
721: }
722:
723: // commit the remaining files non recursively
724: if (commitList.size() > 0) {
725:
726: File[] files = commitList
727: .toArray(new File[commitList.size()]);
728: client.commit(files, message, false); // false = non recursive
729:
730: if (support.isCanceled()) {
731: return;
732: }
733: }
734:
735: // update and refresh
736: FileStatusCache cache = Subversion.getInstance()
737: .getStatusCache();
738: if (rootUpdate) {
739: File[] rootFiles = ctx.getRootFiles();
740: for (int i = 0; i < rootFiles.length; i++) {
741: client.update(rootFiles[i], SVNRevision.HEAD,
742: false);
743: }
744: for (int i = 0; i < rootFiles.length; i++) {
745: cache
746: .refresh(
747: rootFiles[i],
748: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
749: }
750: }
751:
752: // XXX it's probably already catched by cache's onNotify()
753: refreshFiles(cache, commitList);
754: if (support.isCanceled()) {
755: return;
756: }
757: refreshFiles(cache, recursiveCommits);
758: if (support.isCanceled()) {
759: return;
760: }
761: }
762:
763: } catch (SVNClientException ex) {
764: support.annotate(ex);
765: }
766: }
767:
768: /**
769: * Groups files by distinct working copies and sets the binary mimetypes
770: */
771: private static List<List<File>> getManagedTrees(SvnClient client,
772: SvnProgressSupport support, Set<File> commitCandidates,
773: Set<File> binnaryCandidates) throws SVNClientException {
774: FileStatusCache cache = Subversion.getInstance()
775: .getStatusCache();
776: List<List<File>> managedTrees = new ArrayList<List<File>>();
777: for (Iterator<File> itCommitCandidates = commitCandidates
778: .iterator(); itCommitCandidates.hasNext();) {
779: File commitCandidateFile = itCommitCandidates.next();
780:
781: // set MIME property application/octet-stream
782: if (binnaryCandidates.contains(commitCandidateFile)) {
783: ISVNProperty prop = client.propertyGet(
784: commitCandidateFile, ISVNProperty.MIME_TYPE);
785: if (prop != null) {
786: String s = prop.getValue();
787: if (s == null || s.startsWith("text/")) { // NOI18N
788: client.propertySet(commitCandidateFile,
789: ISVNProperty.MIME_TYPE,
790: "application/octet-stream", false); // NOI18N
791: }
792: } else {
793: client.propertySet(commitCandidateFile,
794: ISVNProperty.MIME_TYPE,
795: "application/octet-stream", false); // NOI18N
796: }
797: }
798: if (support.isCanceled()) {
799: return null;
800: }
801:
802: List<File> managedTreesList = null;
803: for (Iterator<List<File>> itManagedTrees = managedTrees
804: .iterator(); itManagedTrees.hasNext();) {
805: List<File> list = itManagedTrees.next();
806: File managedTreeFile = list.get(0);
807:
808: File base = SVNBaseDir.getRootDir(new File[] {
809: commitCandidateFile, managedTreeFile });
810: if (base != null) {
811: FileInformation status = cache.getStatus(base);
812: if ((status.getStatus() & FileInformation.STATUS_MANAGED) != 0) {
813: // found a list with files from the same working copy
814: managedTreesList = list;
815: break;
816: }
817: }
818: if (support.isCanceled()) {
819: return null;
820: }
821: }
822: if (managedTreesList == null) {
823: // no list for files from the same wc as commitCandidateFile created yet
824: managedTreesList = new ArrayList<File>();
825: managedTrees.add(managedTreesList);
826: }
827: managedTreesList.add(commitCandidateFile);
828: }
829:
830: return managedTrees;
831: }
832:
833: /**
834: * Calls the svn add command on not yet added files
835: */
836: private static void performAdds(SvnClient client,
837: SvnProgressSupport support, List<SvnFileNode> addCandidates)
838: throws SVNClientException {
839: List<File> addFiles = new ArrayList<File>();
840: List<File> addDirs = new ArrayList<File>();
841: // XXX waht if user denied directory add but wants to add a file in it?
842: Iterator<SvnFileNode> it = addCandidates.iterator();
843: while (it.hasNext()) {
844: if (support.isCanceled()) {
845: return;
846: }
847: SvnFileNode svnFileNode = it.next();
848: File file = svnFileNode.getFile();
849: if (file.isDirectory()) {
850: addDirs.add(file);
851: } else if (file.isFile()) {
852: addFiles.add(file);
853: }
854: }
855: if (support.isCanceled()) {
856: return;
857: }
858:
859: Iterator<File> itFiles = addDirs.iterator();
860: List<File> dirsToAdd = new ArrayList<File>();
861: while (itFiles.hasNext()) {
862: File dir = itFiles.next();
863: if (!dirsToAdd.contains(dir)) {
864: dirsToAdd.add(dir);
865: }
866: }
867: if (dirsToAdd.size() > 0) {
868: client.addFile(dirsToAdd
869: .toArray(new File[dirsToAdd.size()]), false);
870: }
871: if (support.isCanceled()) {
872: return;
873: }
874:
875: if (addFiles.size() > 0) {
876: client.addFile(addFiles.toArray(new File[addFiles.size()]),
877: false);
878: }
879: }
880:
881: /**
882: * Returns all files which have to be commited recursively (deleted and copied folders)
883: */
884: private static List<File> getRecursiveCommits(
885: List<File> nonRecursiveComits, List<File> removeCandidates) {
886: FileStatusCache cache = Subversion.getInstance()
887: .getStatusCache();
888: List<File> recursiveCommits = new ArrayList<File>();
889:
890: // 1. if there is at least one directory which isn't removed or copied
891: // we have to commit it nonrecursively ...
892: boolean nonRecursiveDirs = false;
893: for (File file : nonRecursiveComits) {
894: if (file.isDirectory()
895: && !(removeCandidates.contains(file) || cache
896: .getStatus(file).getEntry(file).isCopied())) {
897: nonRecursiveDirs = true;
898: break;
899: }
900: }
901: if (!nonRecursiveDirs) {
902: // 2. ... otherwise we may commit all files recursivelly
903: recursiveCommits.addAll(recursiveCommits);
904: recursiveCommits.addAll(nonRecursiveComits);
905: } else {
906: // 3. ... well, this is the worst case. we have folders which were deleted or copied
907: // and such have to be commited recursively (svn restriction). On the other hand,
908: // there are also folders which have to be commited and doing it recursivelly
909: // could cause that the commit would also apply to files which because of exclusion or
910: // the (bloody) flat-folder loginc aren't supposed to be commited at all =>
911: // => the commit has to be split in two parts.
912: for (File file : nonRecursiveComits) {
913: if (file.isDirectory()
914: && (removeCandidates.contains(file) || cache
915: .getStatus(file).getEntry(file)
916: .isCopied())) {
917: recursiveCommits.add(file);
918: }
919: }
920: }
921:
922: return recursiveCommits;
923: }
924:
925: /**
926: * Returns all files from the children list which have a parent in or are equal to a folder from the parents list
927: */
928: private static List<File> getAllChildren(List<File> parents,
929: List<File> children) {
930: List<File> ret = new ArrayList<File>();
931: if (parents.size() > 0) {
932: for (File child : children) {
933: File parent = child;
934: while (parent != null) {
935: if (parents.contains(parent)) {
936: ret.add(child);
937: }
938: parent = parent.getParentFile();
939: }
940: }
941: }
942: return ret;
943: }
944:
945: private static void refreshFiles(FileStatusCache cache,
946: List<File> files) {
947: for (File file : files) {
948: cache.refresh(file,
949: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
950: }
951: }
952:
953: private static List<File> listUnmanagedParents(SvnFileNode node) {
954: List<File> unmanaged = new ArrayList<File>();
955: File file = node.getFile();
956: File parent = file.getParentFile();
957: while (true) {
958: if (new File(parent, ".svn/entries").canRead()
959: || new File(parent, "_svn/entries").canRead()) { // NOI18N
960: break;
961: }
962: unmanaged.add(0, parent);
963: parent = parent.getParentFile();
964: if (parent == null) {
965: break;
966: }
967: }
968:
969: List<File> ret = new ArrayList<File>();
970: Iterator<File> it = unmanaged.iterator();
971: while (it.hasNext()) {
972: File un = it.next();
973: ret.add(un);
974: }
975:
976: return ret;
977: }
978: }
|