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.pull;
042:
043: import org.netbeans.modules.versioning.spi.VCSContext;
044: import javax.swing.*;
045: import java.awt.event.ActionEvent;
046: import java.io.File;
047: import java.util.ArrayList;
048: import java.util.HashSet;
049: import java.util.List;
050: import java.util.Set;
051: import org.netbeans.modules.mercurial.FileInformation;
052: import org.netbeans.modules.mercurial.FileStatusCache;
053: import org.netbeans.modules.mercurial.HgException;
054: import org.netbeans.modules.mercurial.HgProgressSupport;
055: import org.netbeans.modules.mercurial.Mercurial;
056: import org.netbeans.modules.mercurial.OutputLogger;
057: import org.netbeans.modules.mercurial.ui.merge.MergeAction;
058: import org.netbeans.modules.mercurial.ui.actions.ContextAction;
059: import org.netbeans.modules.mercurial.util.HgCommand;
060: import org.netbeans.modules.mercurial.util.HgProjectUtils;
061: import org.netbeans.modules.mercurial.util.HgRepositoryContextCache;
062: import org.netbeans.modules.mercurial.util.HgUtils;
063: import org.openide.util.NbBundle;
064: import org.openide.util.RequestProcessor;
065: import org.openide.DialogDisplayer;
066: import org.openide.NotifyDescriptor;
067: import org.openide.windows.IOProvider;
068: import org.openide.windows.InputOutput;
069: import org.openide.windows.OutputWriter;
070: import org.openide.filesystems.FileObject;
071: import org.openide.filesystems.FileUtil;
072: import org.netbeans.api.project.Project;
073:
074: /**
075: * Pull action for mercurial:
076: * hg pull - pull changes from the specified source
077: *
078: * @author John Rice
079: */
080: public class PullAction extends ContextAction {
081: private static final String CHANGESET_FILES_PREFIX = "files:"; //NOI18N
082:
083: public enum PullType {
084:
085: LOCAL, OTHER
086: }
087:
088: {
089: }
090:
091: private final VCSContext context;
092:
093: public PullAction(String name, VCSContext context) {
094: this .context = context;
095: putValue(Action.NAME, name);
096: }
097:
098: public void performAction(ActionEvent e) {
099: final File root = HgUtils.getRootFile(context);
100: if (root == null) {
101: OutputLogger logger = OutputLogger
102: .getLogger(Mercurial.MERCURIAL_OUTPUT_TAB_TITLE);
103: logger.outputInRed(NbBundle.getMessage(PullAction.class,
104: "MSG_PULL_TITLE")); // NOI18N
105: logger.outputInRed(NbBundle.getMessage(PullAction.class,
106: "MSG_PULL_TITLE_SEP")); // NOI18N
107: logger.outputInRed(NbBundle.getMessage(PullAction.class,
108: "MSG_PULL_NOT_SUPPORTED_INVIEW_INFO")); // NOI18N
109: logger.output(""); // NOI18N
110: JOptionPane.showMessageDialog(null, NbBundle.getMessage(
111: PullAction.class, "MSG_PULL_NOT_SUPPORTED_INVIEW"),// NOI18N
112: NbBundle.getMessage(PullAction.class,
113: "MSG_PULL_NOT_SUPPORTED_INVIEW_TITLE"),// NOI18N
114: JOptionPane.INFORMATION_MESSAGE);
115: logger.closeLog();
116: return;
117: }
118: pull(context);
119: }
120:
121: public static boolean confirmWithLocalChanges(File rootFile,
122: Class bundleLocation, String title, String query,
123: List<String> listIncoming, OutputLogger logger) {
124: FileStatusCache cache = Mercurial.getInstance()
125: .getFileStatusCache();
126: File[] roots = new File[1];
127: roots[0] = rootFile;
128: File[] localModNewFiles = cache
129: .listFiles(
130: roots,
131: FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY
132: | FileInformation.STATUS_VERSIONED_CONFLICT
133: | FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY);
134: List<String> listIncomingAndLocalMod = new ArrayList<String>();
135: Set<String> setFiles = new HashSet<String>();
136: String filesStr;
137: String[] aFileStr;
138: String root = rootFile.getAbsolutePath();
139:
140: for (String s : listIncoming) {
141: if (s.indexOf(CHANGESET_FILES_PREFIX) == 0) {
142: filesStr = (s
143: .substring(CHANGESET_FILES_PREFIX.length()))
144: .trim();
145: aFileStr = filesStr.split(" ");
146: for (String fileStr : aFileStr) {
147: setFiles.add(root + File.separator + fileStr);
148: break;
149: }
150: }
151: }
152: for (File f : localModNewFiles) {
153: for (String s : setFiles) {
154: if (s.equals(f.getAbsolutePath())) {
155: listIncomingAndLocalMod.add(s);
156: }
157: }
158: }
159:
160: if (listIncomingAndLocalMod != null
161: && listIncomingAndLocalMod.size() > 0) {
162: logger.outputInRed(NbBundle.getMessage(PullAction.class,
163: "MSG_PULL_OVERWRITE_LOCAL")); // NOI18N
164: logger.output(listIncomingAndLocalMod);
165: int response = JOptionPane.showOptionDialog(null, NbBundle
166: .getMessage(bundleLocation, query), NbBundle
167: .getMessage(bundleLocation, title),
168: JOptionPane.YES_NO_OPTION,
169: JOptionPane.QUESTION_MESSAGE, null, null, null);
170:
171: if (response == JOptionPane.NO_OPTION) {
172: return false;
173: }
174: }
175: return true;
176: }
177:
178: static void annotateChangeSets(List<String> list,
179: Class bundleLocation, String title) {
180: InputOutput io = IOProvider.getDefault().getIO(
181: Mercurial.MERCURIAL_OUTPUT_TAB_TITLE, false);
182: io.select();
183: OutputWriter out = io.getOut();
184: OutputWriter outRed = io.getErr();
185: outRed.println(NbBundle.getMessage(bundleLocation, title));
186: for (String s : list) {
187: if (s.indexOf(Mercurial.CHANGESET_STR) == 0) {
188: outRed.println(s);
189: } else if (!s.equals("")) {
190: out.println(s);
191: }
192: }
193: out.println("");
194: out.close();
195: outRed.close();
196: }
197:
198: public static void pull(final VCSContext ctx) {
199: final File root = HgUtils.getRootFile(ctx);
200: if (root == null)
201: return;
202: String repository = root.getAbsolutePath();
203:
204: RequestProcessor rp = Mercurial.getInstance()
205: .getRequestProcessor(repository);
206: HgProgressSupport support = new HgProgressSupport() {
207: public void perform() {
208: getDefaultAndPerformPull(ctx, root, this .getLogger());
209: }
210: };
211:
212: support.start(rp, repository, org.openide.util.NbBundle
213: .getMessage(PullAction.class, "MSG_PULL_PROGRESS")); // NOI18N
214: }
215:
216: public boolean isEnabled() {
217: return HgUtils.getRootFile(context) != null;
218: }
219:
220: static void getDefaultAndPerformPull(VCSContext ctx, File root,
221: OutputLogger logger) {
222: final String pullPath = HgCommand.getPullDefault(root);
223: // If the repository has no default pull path then inform user
224: if (pullPath == null) {
225: logger.outputInRed(NbBundle.getMessage(PullAction.class,
226: "MSG_PULL_TITLE")); // NOI18N
227: logger.outputInRed(NbBundle.getMessage(PullAction.class,
228: "MSG_PULL_TITLE_SEP")); // NOI18N
229: logger.output(NbBundle.getMessage(PullAction.class,
230: "MSG_NO_DEFAULT_PULL_SET_MSG")); // NOI18N
231: logger.outputInRed(NbBundle.getMessage(PullAction.class,
232: "MSG_PULL_DONE")); // NOI18N
233: logger.output(""); // NOI18N
234: JOptionPane.showMessageDialog(null, NbBundle.getMessage(
235: PullAction.class, "MSG_NO_DEFAULT_PULL_SET"),
236: NbBundle.getMessage(PullAction.class,
237: "MSG_PULL_TITLE"),
238: JOptionPane.INFORMATION_MESSAGE);
239: return;
240: }
241: // We assume that if fromPrjName is null that it is a remote pull.
242: // This is not true as a project which is in a subdirectory of a
243: // repository will report a project name of null. This does no harm.
244: final String fromPrjName = HgProjectUtils
245: .getProjectName(new File(pullPath));
246: Project proj = HgUtils.getProject(ctx);
247: final String toPrjName = HgProjectUtils.getProjectName(proj);
248: performPull(fromPrjName != null ? PullType.LOCAL
249: : PullType.OTHER, ctx, root, pullPath, fromPrjName,
250: toPrjName, logger);
251: }
252:
253: static void performPull(PullType type, VCSContext ctx, File root,
254: String pullPath, String fromPrjName, String toPrjName,
255: OutputLogger logger) {
256: if (root == null || pullPath == null)
257: return;
258: File bundleFile = null;
259:
260: try {
261: logger.outputInRed(NbBundle.getMessage(PullAction.class,
262: "MSG_PULL_TITLE")); // NOI18N
263: logger.outputInRed(NbBundle.getMessage(PullAction.class,
264: "MSG_PULL_TITLE_SEP")); // NOI18N
265:
266: List<String> listIncoming;
267: if (type == PullType.LOCAL) {
268: listIncoming = HgCommand.doIncoming(root, logger);
269: } else {
270: for (int i = 0; i < 10000; i++) {
271: if (!new File(root.getParentFile(), root.getName()
272: + "_bundle" + i).exists()) { // NOI18N
273: bundleFile = new File(root.getParentFile(),
274: root.getName() + "_bundle" + i); // NOI18N
275: break;
276: }
277: }
278: listIncoming = HgCommand.doIncoming(root, pullPath,
279: bundleFile, logger);
280: }
281: if (listIncoming == null || listIncoming.isEmpty())
282: return;
283:
284: boolean bNoChanges = HgCommand.isNoChanges(listIncoming
285: .get(listIncoming.size() - 1));
286:
287: // Warn User when there are Local Changes present that Pull will overwrite
288: if (!bNoChanges
289: && !confirmWithLocalChanges(root, PullAction.class,
290: "MSG_PULL_LOCALMODS_CONFIRM_TITLE",
291: "MSG_PULL_LOCALMODS_CONFIRM_QUERY",
292: listIncoming, logger)) { // NOI18N
293: logger.outputInRed(NbBundle.getMessage(
294: PullAction.class, "MSG_PULL_LOCALMODS_CANCEL")); // NOI18N
295: logger.output(""); // NOI18N
296: return;
297: }
298:
299: // Do Pull if there are changes to be pulled
300: List<String> list;
301: if (bNoChanges) {
302: list = listIncoming;
303: } else {
304: if (type == PullType.LOCAL) {
305: list = HgCommand.doPull(root, logger);
306: } else {
307: list = HgCommand.doUnbundle(root, bundleFile,
308: logger);
309: }
310: }
311:
312: if (list != null && !list.isEmpty()) {
313:
314: if (!bNoChanges) {
315: annotateChangeSets(HgUtils
316: .replaceHttpPassword(listIncoming),
317: PullAction.class, "MSG_CHANGESETS_TO_PULL"); // NOI18N
318: }
319:
320: logger.output(HgUtils.replaceHttpPassword(list));
321: if (fromPrjName != null) {
322: logger.outputInRed(NbBundle.getMessage(
323: PullAction.class, "MSG_PULL_FROM",
324: fromPrjName,
325: HgUtils.stripDoubleSlash(HgUtils
326: .replaceHttpPassword(pullPath)))); // NOI18N
327: } else {
328: logger.outputInRed(NbBundle.getMessage(
329: PullAction.class, "MSG_PULL_FROM_NONAME",
330: HgUtils.stripDoubleSlash(HgUtils
331: .replaceHttpPassword(pullPath)))); // NOI18N
332: }
333: if (toPrjName != null) {
334: logger.outputInRed(NbBundle.getMessage(
335: PullAction.class, "MSG_PULL_TO", toPrjName,
336: root)); // NOI18N
337: } else {
338: logger.outputInRed(NbBundle.getMessage(
339: PullAction.class, "MSG_PULL_TO_NONAME",
340: root)); // NOI18N
341: }
342:
343: // Handle Merge - both automatic and merge with conflicts
344: boolean bMergeNeededDueToPull = HgCommand
345: .isMergeNeededMsg(list.get(list.size() - 1));
346: boolean bConfirmMerge = false;
347: if (bMergeNeededDueToPull) {
348: bConfirmMerge = HgUtils.confirmDialog(
349: PullAction.class,
350: "MSG_PULL_MERGE_CONFIRM_TITLE",
351: "MSG_PULL_MERGE_CONFIRM_QUERY"); // NOI18N
352: } else {
353: boolean bOutStandingUncommittedMerges = HgCommand
354: .isMergeAbortUncommittedMsg(list.get(list
355: .size() - 1));
356: if (bOutStandingUncommittedMerges) {
357: bConfirmMerge = HgUtils
358: .confirmDialog(PullAction.class,
359: "MSG_PULL_MERGE_CONFIRM_TITLE",
360: "MSG_PULL_MERGE_UNCOMMITTED_CONFIRM_QUERY"); // NOI18N
361: }
362: }
363: if (bConfirmMerge) {
364: logger.output(""); // NOI18N
365: logger.outputInRed(NbBundle.getMessage(
366: PullAction.class, "MSG_PULL_MERGE_DO")); // NOI18N
367: MergeAction.doMergeAction(root, null, logger);
368: } else {
369: List<String> headRevList = HgCommand
370: .getHeadRevisions(root);
371: if (headRevList != null && headRevList.size() > 1) {
372: MergeAction.printMergeWarning(headRevList,
373: logger);
374: }
375: }
376: }
377:
378: if (!bNoChanges) {
379: HgUtils.forceStatusRefreshProject(ctx);
380: // refresh filesystem to take account of deleted files.
381: FileObject rootObj = FileUtil.toFileObject(root);
382: try {
383: rootObj.getFileSystem().refresh(true);
384: } catch (java.lang.Exception ex) {
385: }
386: }
387:
388: } catch (HgException ex) {
389: NotifyDescriptor.Exception e = new NotifyDescriptor.Exception(
390: ex);
391: DialogDisplayer.getDefault().notifyLater(e);
392: } finally {
393: if (bundleFile != null) {
394: bundleFile.delete();
395: }
396: logger.outputInRed(NbBundle.getMessage(PullAction.class,
397: "MSG_PULL_DONE")); // NOI18N
398: logger.output(""); // NOI18N
399: }
400: }
401: }
|