1 /*
2  * SVNCommandHandler.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 package org.rstudio.studio.client.workbench.views.vcs.svn;
16 
17 import java.util.ArrayList;
18 
19 import com.google.inject.Inject;
20 import com.google.inject.Provider;
21 import org.rstudio.core.client.command.CommandBinder;
22 import org.rstudio.core.client.command.Handler;
23 import org.rstudio.core.client.files.FileSystemItem;
24 import org.rstudio.core.client.widget.MessageDialog;
25 import org.rstudio.core.client.widget.Operation;
26 import org.rstudio.core.client.widget.OperationWithInput;
27 import org.rstudio.studio.client.RStudioGinjector;
28 import org.rstudio.studio.client.common.GlobalDisplay;
29 import org.rstudio.studio.client.common.SimpleRequestCallback;
30 import org.rstudio.studio.client.common.console.ConsoleProcess;
31 import org.rstudio.studio.client.common.vcs.ProcessResult;
32 import org.rstudio.studio.client.common.vcs.SVNServerOperations;
33 import org.rstudio.studio.client.common.vcs.StatusAndPath;
34 import org.rstudio.studio.client.common.vcs.ignore.Ignore;
35 import org.rstudio.studio.client.common.vcs.ignore.IgnoreList;
36 import org.rstudio.studio.client.server.ServerRequestCallback;
37 import org.rstudio.studio.client.workbench.commands.Commands;
38 import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
39 import org.rstudio.studio.client.workbench.views.vcs.common.ProcessCallback;
40 import org.rstudio.studio.client.workbench.views.vcs.common.VCSFileOpener;
41 import org.rstudio.studio.client.workbench.views.vcs.svn.commit.SVNCommitDialog;
42 import org.rstudio.studio.client.workbench.views.vcs.svn.model.SVNState;
43 
44 import com.google.gwt.core.client.GWT;
45 import com.google.gwt.user.client.Command;
46 
47 public class SVNCommandHandler
48 {
49    public interface Binder extends CommandBinder<Commands, SVNCommandHandler>
50    {
51    }
52 
SVNCommandHandler(SVNPresenterDisplay display, GlobalDisplay globalDisplay, Commands commands, SVNServerOperations server, SVNState svnState, VCSFileOpener vcsFileOpener)53    public SVNCommandHandler(SVNPresenterDisplay display,
54                             GlobalDisplay globalDisplay,
55                             Commands commands,
56                             SVNServerOperations server,
57                             SVNState svnState,
58                             VCSFileOpener vcsFileOpener)
59    {
60       display_ = display;
61       globalDisplay_ = globalDisplay;
62       commands_ = commands;
63       server_ = server;
64       svnState_ = svnState;
65       vcsFileOpener_ = vcsFileOpener;
66       GWT.<Binder>create(Binder.class).bind(commands, this);
67 
68       RStudioGinjector.INSTANCE.injectMembers(this);
69    }
70 
71    @Inject
initialize(Provider<SVNCommitDialog> pCommitDialog, Provider<Ignore> pIgnore)72    void initialize(Provider<SVNCommitDialog> pCommitDialog,
73                    Provider<Ignore> pIgnore)
74    {
75       pCommitDialog_ = pCommitDialog;
76       pIgnore_ = pIgnore;
77    }
78 
setFilesCommandsEnabled(boolean enabled)79    public void setFilesCommandsEnabled(boolean enabled)
80    {
81       commands_.vcsAddFiles().setEnabled(enabled);
82       commands_.vcsRemoveFiles().setEnabled(enabled);
83       commands_.vcsRevert().setEnabled(enabled);
84       commands_.vcsIgnore().setEnabled(enabled);
85       commands_.vcsResolve().setEnabled(enabled);
86    }
87 
88    // onVcsPull and onVcsCommit and not direct  command handlers because they
89    // are handled globally in the main frame (by BaseVcsPresenter or the
90    // satellite frame)
91 
onVcsPull()92    public void onVcsPull()
93    {
94       server_.svnUpdate(new SimpleRequestCallback<ConsoleProcess>()
95          {
96             @Override
97             public void onResponseReceived(ConsoleProcess response)
98             {
99                new ConsoleProgressDialog(response, server_).showModal();
100             }
101          });
102    }
103 
onVcsPullRebase()104    public void onVcsPullRebase()
105    {
106       server_.svnUpdate(new SimpleRequestCallback<ConsoleProcess>()
107          {
108             @Override
109             public void onResponseReceived(ConsoleProcess response)
110             {
111                new ConsoleProgressDialog(response, server_).showModal();
112             }
113          });
114    }
115 
116 
onVcsCommit()117    public void onVcsCommit()
118    {
119       pCommitDialog_.get().showModal();
120    }
121 
onVcsIgnore()122    public void onVcsIgnore()
123    {
124       // special case for a single directory with property changes
125       ArrayList<StatusAndPath> items = display_.getSelectedItems();
126       if (items.size() == 1)
127       {
128          StatusAndPath item = items.get(0);
129          if (item.isDirectory() && item.getStatus() == "M")
130          {
131             String path = item.getPath();
132             if (path == ".")
133                path = "";
134             IgnoreList ignoreList = new IgnoreList(path, new ArrayList<>());
135             pIgnore_.get().showDialog(ignoreList, ignoreStrategy_);
136             return;
137          }
138       }
139 
140       // standard case
141       ArrayList<String> paths = getPathArray();
142       pIgnore_.get().showDialog(paths, ignoreStrategy_);
143 
144    }
145 
146    private final Ignore.Strategy ignoreStrategy_ = new Ignore.Strategy() {
147 
148       @Override
149       public String getDialogCaption()
150       {
151          return "SVN Ignore";
152       }
153 
154       @Override
155       public String getIgnoresCaption()
156       {
157          return "svn:ignore";
158       }
159 
160       @Override
161       public String getHelpLinkName()
162       {
163          return "svn_ignore_help";
164       }
165 
166       @Override
167       public Filter getFilter()
168       {
169          return null;
170       }
171 
172 
173       @Override
174       public void getIgnores(String path,
175             ServerRequestCallback<ProcessResult> requestCallback)
176       {
177          server_.svnGetIgnores(path, requestCallback);
178       }
179 
180       @Override
181       public void setIgnores(String path, String ignores,
182             ServerRequestCallback<ProcessResult> requestCallback)
183       {
184          server_.svnSetIgnores(path, ignores, requestCallback);
185       }
186    };
187 
188    @Handler
onVcsOpen()189    void onVcsOpen()
190    {
191       vcsFileOpener_.openFiles(display_.getSelectedItems());
192    }
193 
194    @Handler
onVcsRefresh()195    void onVcsRefresh()
196    {
197       display_.getChangelistTable().showProgress();
198       svnState_.refresh(true);
199    }
200 
201    @Handler
onVcsRefreshNoError()202    void onVcsRefreshNoError()
203    {
204       display_.getChangelistTable().showProgress();
205       svnState_.refresh(false);
206    }
207 
208    @Handler
onVcsAddFiles()209    void onVcsAddFiles()
210    {
211       ArrayList<String> paths = getPathArray();
212 
213       if (paths.size() > 0)
214          server_.svnAdd(paths, new ProcessCallback("SVN Add"));
215    }
216 
217    @Handler
onVcsRemoveFiles()218    void onVcsRemoveFiles()
219    {
220       ArrayList<String> paths = getPathArray();
221 
222       if (paths.size() > 0)
223          server_.svnDelete(paths, new ProcessCallback("SVN Delete"));
224    }
225 
226    @Handler
onVcsRevert()227    void onVcsRevert()
228    {
229       final ArrayList<String> paths = getPathArray();
230       if (paths.size() == 0)
231          return;
232 
233       doRevert(paths, new Command() {
234          @Override
235          public void execute()
236          {
237             display_.getChangelistTable().selectNextUnselectedItem();
238             display_.getChangelistTable().focus();
239          }
240 
241       });
242    }
243 
244    @Handler
onVcsResolve()245    void onVcsResolve()
246    {
247       ArrayList<StatusAndPath> items = display_.getSelectedItems();
248 
249       if (items.size() == 0)
250          return;
251 
252       final ArrayList<String> paths = new ArrayList<>();
253 
254       boolean conflict = false;
255       for (StatusAndPath item : items)
256       {
257          paths.add(item.getPath());
258          if ("C".equals(item.getStatus()))
259             conflict = true;
260          else if (item.isDirectory())
261             conflict = true;
262       }
263 
264       Operation resolveOperation = new Operation()
265       {
266          @Override
267          public void execute()
268          {
269             new SVNResolveDialog(
270                   paths.size(),
271                   "Resolve",
272                   new OperationWithInput<String>()
273                   {
274                      @Override
275                      public void execute(String input)
276                      {
277                         server_.svnResolve(
278                               input, paths,
279                               new ProcessCallback("SVN Resolve"));
280                      }
281                   }).showModal();
282          }
283       };
284 
285       if (conflict)
286       {
287          resolveOperation.execute();
288       }
289       else
290       {
291          String message =
292                (paths.size() > 1 ?
293                "None of the selected paths appear to have conflicts." :
294                "The selected path does not appear to have conflicts.") +
295                "\n\nDo you want to resolve anyway?";
296 
297          globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_WARNING,
298                                          "No Conflicts Detected",
299                                          message,
300                                          resolveOperation,
301                                          true);
302       }
303    }
304 
revertFile(FileSystemItem file)305    public void revertFile(FileSystemItem file)
306    {
307       // build an ArrayList<String> so we can call the core helper
308       ArrayList<String> revertList = new ArrayList<>();
309       for (StatusAndPath item :  svnState_.getStatus())
310       {
311          if (item.getRawPath() == file.getPath())
312          {
313             revertList.add(item.getPath());
314             break;
315          }
316       }
317 
318       if (revertList.size() > 0)
319       {
320          doRevert(revertList, null);
321       }
322       else
323       {
324          globalDisplay_.showMessage(MessageDialog.INFO,
325                                     "No Changes to Revert",
326                                     "There are no changes to the file \"" +
327                                     file.getName() + "\" to revert.");
328       }
329 
330    }
331 
doRevert(final ArrayList<String> revertList, final Command onRevertConfirmed)332    private void doRevert(final ArrayList<String> revertList,
333                          final Command onRevertConfirmed)
334    {
335       String noun = revertList.size() == 1 ? "file" : "files";
336       globalDisplay_.showYesNoMessage(
337             GlobalDisplay.MSG_WARNING,
338             "Revert Changes",
339             "Changes to the selected " + noun + " will be reverted.\n\n" +
340                   "Are you sure you want to continue?",
341                   new Operation()
342             {
343                @Override
344                public void execute()
345                {
346                   if (onRevertConfirmed != null)
347                      onRevertConfirmed.execute();
348 
349                   server_.svnRevert(revertList,
350                                     new ProcessCallback("SVN Revert"));
351 
352                }
353             },
354             false);
355    }
356 
357 
getPathArray()358    private ArrayList<String> getPathArray()
359    {
360       ArrayList<StatusAndPath> items = display_.getSelectedItems();
361       ArrayList<String> paths = new ArrayList<>();
362       for (StatusAndPath item : items)
363          paths.add(item.getPath());
364       return paths;
365    }
366 
367    private final SVNPresenterDisplay display_;
368    private final GlobalDisplay globalDisplay_;
369    private final Commands commands_;
370    private final SVNServerOperations server_;
371    private final SVNState svnState_;
372    private final VCSFileOpener vcsFileOpener_;
373    private Provider<SVNCommitDialog> pCommitDialog_;
374    private Provider<Ignore> pIgnore_;
375 }