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
.pull
;
45 import java
.awt
.event
.ActionEvent
;
47 import java
.util
.ArrayList
;
48 import java
.util
.HashSet
;
49 import java
.util
.List
;
51 import javax
.swing
.Action
;
52 import javax
.swing
.JOptionPane
;
53 import org
.netbeans
.api
.project
.Project
;
54 import org
.netbeans
.modules
.git
.FileInformation
;
55 import org
.netbeans
.modules
.git
.FileStatusCache
;
56 import org
.netbeans
.modules
.git
.Git
;
57 import org
.netbeans
.modules
.git
.GitException
;
58 import org
.netbeans
.modules
.git
.GitProgressSupport
;
59 import org
.netbeans
.modules
.git
.OutputLogger
;
60 import org
.netbeans
.modules
.git
.ui
.actions
.ContextAction
;
61 import org
.netbeans
.modules
.git
.ui
.merge
.MergeAction
;
62 import org
.netbeans
.modules
.git
.util
.GitCommand
;
63 import org
.netbeans
.modules
.git
.util
.GitProjectUtils
;
64 import org
.netbeans
.modules
.git
.util
.GitUtils
;
65 import org
.netbeans
.modules
.versioning
.spi
.VCSContext
;
66 import org
.openide
.DialogDisplayer
;
67 import org
.openide
.NotifyDescriptor
;
68 import org
.openide
.filesystems
.FileObject
;
69 import org
.openide
.filesystems
.FileUtil
;
70 import org
.openide
.util
.NbBundle
;
71 import org
.openide
.util
.RequestProcessor
;
72 import org
.openide
.windows
.IOProvider
;
73 import org
.openide
.windows
.InputOutput
;
74 import org
.openide
.windows
.OutputWriter
;
78 * Pull action for Git:
79 * git pull - fetch from, and merge changes with the specified repository and
84 public class PullAction
extends ContextAction
{
85 private static final String CHANGESET_FILES_PREFIX
= "files:"; //NOI18N
87 public enum PullType
{
91 private final VCSContext context
;
93 public PullAction(String name
, VCSContext context
) {
94 this.context
= context
;
95 putValue(Action
.NAME
, name
);
98 public void performAction(ActionEvent e
) {
99 final File root
= GitUtils
.getRootFile(context
);
101 OutputLogger logger
= OutputLogger
.getLogger(Git
.GIT_OUTPUT_TAB_TITLE
);
102 logger
.outputInRed( NbBundle
.getMessage(PullAction
.class,"MSG_PULL_TITLE")); // NOI18N
103 logger
.outputInRed( NbBundle
.getMessage(PullAction
.class,"MSG_PULL_TITLE_SEP")); // NOI18N
105 NbBundle
.getMessage(PullAction
.class, "MSG_PULL_NOT_SUPPORTED_INVIEW_INFO")); // NOI18N
106 logger
.output(""); // NOI18N
107 JOptionPane
.showMessageDialog(null,
108 NbBundle
.getMessage(PullAction
.class, "MSG_PULL_NOT_SUPPORTED_INVIEW"),// NOI18N
109 NbBundle
.getMessage(PullAction
.class, "MSG_PULL_NOT_SUPPORTED_INVIEW_TITLE"),// NOI18N
110 JOptionPane
.INFORMATION_MESSAGE
);
117 public static boolean confirmWithLocalChanges(File rootFile
, Class bundleLocation
, String title
, String query
,
118 List
<String
> listIncoming
, OutputLogger logger
) {
119 FileStatusCache cache
= Git
.getInstance().getFileStatusCache();
120 File
[] roots
= new File
[1];
122 File
[] localModNewFiles
= cache
.listFiles(roots
,
123 FileInformation
.STATUS_VERSIONED_MODIFIEDLOCALLY
|
124 FileInformation
.STATUS_VERSIONED_CONFLICT
|
125 FileInformation
.STATUS_NOTVERSIONED_NEWLOCALLY
);
126 List
<String
> listIncomingAndLocalMod
= new ArrayList
<String
>();
127 Set
<String
> setFiles
= new HashSet
<String
>();
130 String root
= rootFile
.getAbsolutePath();
132 for(String s
: listIncoming
){
133 if(s
.indexOf(CHANGESET_FILES_PREFIX
) == 0){
134 filesStr
= (s
.substring(CHANGESET_FILES_PREFIX
.length())).trim();
135 aFileStr
= filesStr
.split(" ");
136 for(String fileStr
: aFileStr
){
137 setFiles
.add(root
+ File
.separator
+ fileStr
);
142 for(File f
: localModNewFiles
){
143 for(String s
: setFiles
){
144 if( s
.equals(f
.getAbsolutePath())){
145 listIncomingAndLocalMod
.add(s
);
150 if (listIncomingAndLocalMod
!= null && listIncomingAndLocalMod
.size() > 0) {
151 logger
.outputInRed(NbBundle
.getMessage(PullAction
.class, "MSG_PULL_OVERWRITE_LOCAL")); // NOI18N
152 logger
.output(listIncomingAndLocalMod
);
153 int response
= JOptionPane
.showOptionDialog(null,
154 NbBundle
.getMessage(bundleLocation
, query
), NbBundle
.getMessage(bundleLocation
, title
),
155 JOptionPane
.YES_NO_OPTION
, JOptionPane
.QUESTION_MESSAGE
, null, null, null);
157 if (response
== JOptionPane
.NO_OPTION
) {
165 static void annotateChangeSets(List
<String
> list
, Class bundleLocation
, String title
) {
166 InputOutput io
= IOProvider
.getDefault().getIO(Git
.GIT_OUTPUT_TAB_TITLE
, false);
168 OutputWriter out
= io
.getOut();
169 OutputWriter outRed
= io
.getErr();
170 outRed
.println(NbBundle
.getMessage(bundleLocation
, title
));
171 for (String s
: list
) {
172 if (s
.indexOf(Git
.CHANGESET_STR
) == 0) {
174 } else if (!s
.equals("")) {
183 public static void pull(final VCSContext ctx
) {
184 final File root
= GitUtils
.getRootFile(ctx
);
185 if (root
== null) return;
186 String repository
= root
.getAbsolutePath();
188 RequestProcessor rp
= Git
.getInstance().getRequestProcessor(repository
);
189 GitProgressSupport support
= new GitProgressSupport() {
190 public void perform() { getDefaultAndPerformPull(ctx
, root
, this.getLogger()); } };
192 support
.start(rp
, repository
, org
.openide
.util
.NbBundle
.getMessage(PullAction
.class, "MSG_PULL_PROGRESS")); // NOI18N
196 public boolean isEnabled() {
197 return GitUtils
.getRootFile(context
) != null;
200 static void getDefaultAndPerformPull(VCSContext ctx
, File root
, OutputLogger logger
) {
201 final String pullPath
= GitCommand
.getPullDefault(root
);
202 // If the repository has no default pull path then inform user
203 if(pullPath
== null) {
204 logger
.outputInRed( NbBundle
.getMessage(PullAction
.class,"MSG_PULL_TITLE")); // NOI18N
205 logger
.outputInRed( NbBundle
.getMessage(PullAction
.class,"MSG_PULL_TITLE_SEP")); // NOI18N
206 logger
.output(NbBundle
.getMessage(PullAction
.class, "MSG_NO_DEFAULT_PULL_SET_MSG")); // NOI18N
207 logger
.outputInRed(NbBundle
.getMessage(PullAction
.class, "MSG_PULL_DONE")); // NOI18N
208 logger
.output(""); // NOI18N
209 JOptionPane
.showMessageDialog(null,
210 NbBundle
.getMessage(PullAction
.class,"MSG_NO_DEFAULT_PULL_SET"),
211 NbBundle
.getMessage(PullAction
.class,"MSG_PULL_TITLE"),
212 JOptionPane
.INFORMATION_MESSAGE
);
215 // We assume that if fromPrjName is null that it is a remote pull.
216 // This is not true as a project which is in a subdirectory of a
217 // repository will report a project name of null. This does no harm.
218 final String fromPrjName
= GitProjectUtils
.getProjectName(new File(pullPath
));
219 Project proj
= GitUtils
.getProject(ctx
);
220 final String toPrjName
= GitProjectUtils
.getProjectName(proj
);
221 performPull(fromPrjName
!= null ? PullType
.LOCAL
: PullType
.OTHER
, ctx
, root
, pullPath
, fromPrjName
, toPrjName
, logger
);
224 static void performPull(PullType type
, VCSContext ctx
, File root
, String pullPath
, String fromPrjName
, String toPrjName
, OutputLogger logger
) {
225 if(root
== null || pullPath
== null) return;
226 File bundleFile
= null;
229 logger
.outputInRed(NbBundle
.getMessage(PullAction
.class, "MSG_PULL_TITLE")); // NOI18N
230 logger
.outputInRed(NbBundle
.getMessage(PullAction
.class, "MSG_PULL_TITLE_SEP")); // NOI18N
232 List
<String
> listIncoming
;
233 if(type
== PullType
.LOCAL
){
234 listIncoming
= GitCommand
.doIncoming(root
, logger
);
236 for (int i
= 0; i
< 10000; i
++) {
237 if (!new File(root
.getParentFile(), root
.getName() + "_bundle" + i
).exists()) { // NOI18N
238 bundleFile
= new File(root
.getParentFile(), root
.getName() + "_bundle" + i
); // NOI18N
242 listIncoming
= GitCommand
.doIncoming(root
, pullPath
, bundleFile
, logger
);
244 if (listIncoming
== null || listIncoming
.isEmpty()) return;
246 boolean bNoChanges
= GitCommand
.isNoChanges(listIncoming
.get(listIncoming
.size() - 1));
248 // Warn User when there are Local Changes present that Pull will overwrite
249 if (!bNoChanges
&& !confirmWithLocalChanges(root
, PullAction
.class, "MSG_PULL_LOCALMODS_CONFIRM_TITLE", "MSG_PULL_LOCALMODS_CONFIRM_QUERY", listIncoming
, logger
)) { // NOI18N
250 logger
.outputInRed(NbBundle
.getMessage(PullAction
.class, "MSG_PULL_LOCALMODS_CANCEL")); // NOI18N
251 logger
.output(""); // NOI18N
255 // Do Pull if there are changes to be pulled
260 if(type
== PullType
.LOCAL
){
261 list
= GitCommand
.doPull(root
, logger
);
263 //list = GitCommand.doUnbundle(root, bundleFile, logger);
264 list
= GitCommand
.doPull(root
, logger
);
268 if (list
!= null && !list
.isEmpty()) {
271 annotateChangeSets(GitUtils
.replaceHttpPassword(listIncoming
), PullAction
.class, "MSG_CHANGESETS_TO_PULL"); // NOI18N
274 logger
.output(GitUtils
.replaceHttpPassword(list
));
275 if (fromPrjName
!= null) {
276 logger
.outputInRed(NbBundle
.getMessage(
277 PullAction
.class, "MSG_PULL_FROM", fromPrjName
, GitUtils
.stripDoubleSlash(GitUtils
.replaceHttpPassword(pullPath
)))); // NOI18N
279 logger
.outputInRed(NbBundle
.getMessage(
280 PullAction
.class, "MSG_PULL_FROM_NONAME", GitUtils
.stripDoubleSlash(GitUtils
.replaceHttpPassword(pullPath
)))); // NOI18N
282 if (toPrjName
!= null) {
283 logger
.outputInRed(NbBundle
.getMessage(
284 PullAction
.class, "MSG_PULL_TO", toPrjName
, root
)); // NOI18N
286 logger
.outputInRed(NbBundle
.getMessage(
287 PullAction
.class, "MSG_PULL_TO_NONAME", root
)); // NOI18N
290 // Handle Merge - both automatic and merge with conflicts
291 boolean bMergeNeededDueToPull
= GitCommand
.isMergeNeededMsg(list
.get(list
.size() - 1));
292 boolean bConfirmMerge
= false;
293 if(bMergeNeededDueToPull
){
294 bConfirmMerge
= GitUtils
.confirmDialog(
295 PullAction
.class, "MSG_PULL_MERGE_CONFIRM_TITLE", "MSG_PULL_MERGE_CONFIRM_QUERY"); // NOI18N
297 boolean bOutStandingUncommittedMerges
= GitCommand
.isMergeAbortUncommittedMsg(list
.get(list
.size() - 1));
298 if(bOutStandingUncommittedMerges
){
299 bConfirmMerge
= GitUtils
.confirmDialog(
300 PullAction
.class, "MSG_PULL_MERGE_CONFIRM_TITLE", "MSG_PULL_MERGE_UNCOMMITTED_CONFIRM_QUERY"); // NOI18N
304 logger
.output(""); // NOI18N
305 logger
.outputInRed(NbBundle
.getMessage(PullAction
.class, "MSG_PULL_MERGE_DO")); // NOI18N
306 MergeAction
.doMergeAction(root
, null, logger
);
308 List
<String
> headRevList
= GitCommand
.getHeadRevisions(root
);
309 if (headRevList
!= null && headRevList
.size() > 1){
310 MergeAction
.printMergeWarning(headRevList
, logger
);
316 GitUtils
.forceStatusRefreshProject(ctx
);
317 // refresh filesystem to take account of deleted files.
318 FileObject rootObj
= FileUtil
.toFileObject(root
);
320 rootObj
.getFileSystem().refresh(true);
321 } catch (java
.lang
.Exception ex
) {
325 } catch (GitException ex
) {
326 NotifyDescriptor
.Exception e
= new NotifyDescriptor
.Exception(ex
);
327 DialogDisplayer
.getDefault().notifyLater(e
);
329 if (bundleFile
!= null) {
332 logger
.outputInRed(NbBundle
.getMessage(PullAction
.class, "MSG_PULL_DONE")); // NOI18N
333 logger
.output(""); // NOI18N