update copyright
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / dialogs / RepositoryBrowserDialog.java
blob54f7cd6a07e0aebadfc052ee8791b3b2b3d65f7c
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org.jetbrains.idea.svn.dialogs;
18 import com.intellij.CommonBundle;
19 import com.intellij.ide.CommonActionsManager;
20 import com.intellij.ide.TreeExpander;
21 import com.intellij.openapi.actionSystem.*;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.application.ModalityState;
24 import com.intellij.openapi.fileChooser.FileChooser;
25 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
26 import com.intellij.openapi.fileEditor.FileEditorManager;
27 import com.intellij.openapi.help.HelpManager;
28 import com.intellij.openapi.ide.CopyPasteManager;
29 import com.intellij.openapi.options.ConfigurationException;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.progress.ProgressManager;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.ui.DialogWrapper;
34 import com.intellij.openapi.ui.Messages;
35 import com.intellij.openapi.util.Comparing;
36 import com.intellij.openapi.util.Disposer;
37 import com.intellij.openapi.util.IconLoader;
38 import com.intellij.openapi.vcs.*;
39 import com.intellij.openapi.vcs.changes.Change;
40 import com.intellij.openapi.vcs.changes.ChangesUtil;
41 import com.intellij.openapi.vcs.changes.ContentRevision;
42 import com.intellij.openapi.vcs.changes.ui.ChangeListViewerDialog;
43 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
44 import com.intellij.openapi.vfs.VirtualFile;
45 import com.intellij.openapi.wm.ToolWindowManager;
46 import com.intellij.util.NotNullFunction;
47 import com.intellij.vcsUtil.VcsUtil;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51 import org.jetbrains.idea.svn.*;
52 import org.jetbrains.idea.svn.actions.BrowseRepositoryAction;
53 import org.jetbrains.idea.svn.checkout.SvnCheckoutProvider;
54 import org.jetbrains.idea.svn.dialogs.browser.*;
55 import org.jetbrains.idea.svn.dialogs.browserCache.Expander;
56 import org.jetbrains.idea.svn.dialogs.browserCache.KeepingExpandedExpander;
57 import org.jetbrains.idea.svn.dialogs.browserCache.SyntheticWorker;
58 import org.jetbrains.idea.svn.history.SvnHistoryProvider;
59 import org.jetbrains.idea.svn.history.SvnRepositoryLocation;
60 import org.jetbrains.idea.svn.status.SvnDiffEditor;
61 import org.tmatesoft.svn.core.*;
62 import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
63 import org.tmatesoft.svn.core.internal.wc.SVNCancellableEditor;
64 import org.tmatesoft.svn.core.io.ISVNEditor;
65 import org.tmatesoft.svn.core.io.ISVNReporter;
66 import org.tmatesoft.svn.core.io.ISVNReporterBaton;
67 import org.tmatesoft.svn.core.io.SVNRepository;
68 import org.tmatesoft.svn.core.wc.SVNCommitClient;
69 import org.tmatesoft.svn.core.wc.SVNCopyClient;
70 import org.tmatesoft.svn.core.wc.SVNCopySource;
71 import org.tmatesoft.svn.core.wc.SVNRevision;
73 import javax.swing.*;
74 import javax.swing.tree.TreeNode;
75 import java.awt.*;
76 import java.awt.datatransfer.StringSelection;
77 import java.awt.event.KeyEvent;
78 import java.awt.event.MouseAdapter;
79 import java.awt.event.MouseEvent;
80 import java.io.*;
81 import java.util.ArrayList;
82 import java.util.Collection;
83 import java.util.Map;
85 public class RepositoryBrowserDialog extends DialogWrapper {
87 private final Project myProject;
88 private final SvnVcs myVCS;
89 private RepositoryBrowserComponent myRepositoryBrowser;
91 @NonNls private static final String HELP_ID = "vcs.subversion.browseSVN";
92 @NonNls public static final String COPY_OF_PREFIX = "CopyOf";
93 @NonNls public static final String NEW_FOLDER_POSTFIX = "NewFolder";
95 private final DeleteAction myDeleteAction = new DeleteAction();
96 private AnAction copyUrlAction;
97 private AnAction mkDirAction;
99 private final boolean myShowFiles;
101 @NonNls private static final String PLACE_TOOLBAR = "RepositoryBrowser.Toolbar";
102 @NonNls private static final String PLACE_MENU = "RepositoryBrowser.Menu";
104 public RepositoryBrowserDialog(Project project) {
105 this(project, true);
108 public RepositoryBrowserDialog(Project project, final boolean showFiles) {
109 super(project, true);
110 myShowFiles = showFiles;
111 myProject = project;
112 myVCS = SvnVcs.getInstance(project);
113 setTitle("SVN Repository Browser");
114 setResizable(true);
115 setOKButtonText(CommonBundle.getCloseButtonText());
116 getHelpAction().setEnabled(true);
117 init();
120 protected void doHelpAction() {
121 HelpManager.getInstance().invokeHelp(HELP_ID);
124 protected Action[] createActions() {
125 return new Action[] {getOKAction(), getHelpAction()};
128 protected String getDimensionServiceKey() {
129 return "svn.repositoryBrowser";
132 protected boolean showImportAction() {
133 return true;
136 public JComponent createToolbar(boolean horizontal) {
137 DefaultActionGroup group = new DefaultActionGroup();
138 group.add(new AddLocationAction());
139 group.add(new EditLocationAction());
140 group.add(new DiscardLocationAction());
141 group.add(new DetailsAction());
142 group.addSeparator();
143 final RefreshAction refreshAction = new RefreshAction();
144 refreshAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), getRepositoryBrowser());
145 group.add(refreshAction);
147 myDeleteAction.registerCustomShortcutSet(CommonShortcuts.DELETE, getRepositoryBrowser());
148 copyUrlAction = new CopyUrlAction();
149 copyUrlAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT,
150 KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK|
151 KeyEvent.ALT_MASK|KeyEvent.ALT_DOWN_MASK)), getRepositoryBrowser());
152 mkDirAction = new MkDirAction();
153 mkDirAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT,
154 KeyEvent.ALT_MASK|KeyEvent.ALT_DOWN_MASK)), getRepositoryBrowser());
156 AnAction action = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() {
157 public void expandAll() {
160 public boolean canExpand() {
161 return false;
164 public void collapseAll() {
165 JTree tree = getRepositoryBrowser().getRepositoryTree();
166 int row = tree.getRowCount() - 1;
167 while (row >= 0) {
168 tree.collapseRow(row);
169 row--;
173 public boolean canCollapse() {
174 return true;
176 }, getRepositoryBrowser());
177 group.add(action);
178 if (!horizontal) {
179 group.addSeparator();
180 group.add(new CloseToolWindowAction());
182 return ActionManager.getInstance().createActionToolbar(PLACE_TOOLBAR, group, horizontal).getComponent();
185 protected JPopupMenu createPopup(boolean toolWindow) {
186 DefaultActionGroup group = new DefaultActionGroup();
187 DefaultActionGroup newGroup = new DefaultActionGroup("_New", true);
188 newGroup.add(new AddLocationAction());
189 newGroup.add(new MkDirAction());
190 group.add(newGroup);
191 group.addSeparator();
192 if (toolWindow) {
193 group.add(new OpenAction());
194 group.add(new HistoryAction());
196 group.add(new CheckoutAction());
197 group.add(new DiffAction());
198 group.add(new BrowseChangesAction());
199 group.addSeparator();
200 group.add(new ImportAction());
201 group.add(new ExportAction());
202 group.addSeparator();
203 group.add(new CopyOrMoveAction("Branch or Tag...", "copy.dialog.title", false));
204 group.add(new CopyOrMoveAction("_Move or Rename...", "move.dialog.title", true));
205 group.add(myDeleteAction);
206 group.add(copyUrlAction);
207 group.addSeparator();
208 group.add(new RefreshAction());
209 group.add(new EditLocationAction());
210 group.add(new DiscardLocationAction());
211 ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(PLACE_MENU, group);
212 return menu.getComponent();
215 public JComponent createCenterPanel() {
216 JPanel parentPanel = new JPanel(new BorderLayout());
217 JPanel top = new JPanel(new BorderLayout());
219 top.add(new JLabel("Repositories:"), BorderLayout.WEST);
220 top.add(createToolbar(true), BorderLayout.EAST);
221 parentPanel.add(top, BorderLayout.NORTH);
223 JComponent panel = createBrowserComponent(false);
224 parentPanel.add(panel, BorderLayout.CENTER);
226 return parentPanel;
229 public JComponent createBrowserComponent(final boolean toolWindow) {
230 JPanel panel = new JPanel();
231 panel.setLayout(new GridBagLayout());
233 GridBagConstraints gc = new GridBagConstraints();
234 gc.gridx = 0;
235 gc.gridwidth = 1;
236 gc.gridy = 0;
237 gc.gridheight = 1;
239 gc.gridx = 0;
240 gc.gridwidth = 2;
241 gc.gridy += 1;
242 gc.gridheight = 1;
243 gc.weightx = 1;
244 gc.weighty = 1;
245 gc.fill = GridBagConstraints.BOTH;
246 gc.anchor = GridBagConstraints.WEST;
248 panel.add(getRepositoryBrowser(), gc);
250 gc.gridy += 1;
251 gc.weighty = 0;
252 gc.fill = GridBagConstraints.HORIZONTAL;
254 panel.add(new JLabel(), gc);
256 Collection<String> urls = SvnApplicationSettings.getInstance().getCheckoutURLs();
257 ArrayList<SVNURL> svnURLs = new ArrayList<SVNURL>();
258 for (final String url : urls) {
259 try {
260 svnURLs.add(SVNURL.parseURIEncoded(url));
262 catch (SVNException e) {
266 getRepositoryBrowser().setRepositoryURLs(svnURLs.toArray(new SVNURL[svnURLs.size()]), myShowFiles);
267 getRepositoryBrowser().getRepositoryTree().addMouseListener(new MouseAdapter() {
268 public void mouseClicked(MouseEvent e) {
269 showPopup(e);
271 public void mousePressed(MouseEvent e) {
272 showPopup(e);
274 public void mouseReleased(MouseEvent e) {
275 showPopup(e);
278 private void showPopup(MouseEvent e) {
279 if (e.isPopupTrigger()) {
280 JTree tree = getRepositoryBrowser().getRepositoryTree();
281 int row = tree.getRowForLocation(e.getX(), e.getY());
282 if (row >= 0) {
283 tree.setSelectionRow(row);
285 JPopupMenu popupMenu = createPopup(toolWindow);
286 if (popupMenu != null) {
287 popupMenu.show(e.getComponent(), e.getX(), e.getY());
292 return panel;
295 protected RepositoryBrowserComponent getRepositoryBrowser() {
296 if (myRepositoryBrowser == null) {
297 myRepositoryBrowser = new RepositoryBrowserComponent(SvnVcs.getInstance(myProject));
299 return myRepositoryBrowser;
302 public void disposeRepositoryBrowser() {
303 if (myRepositoryBrowser != null) {
304 Disposer.dispose(myRepositoryBrowser);
305 myRepositoryBrowser = null;
309 protected void dispose() {
310 super.dispose();
311 disposeRepositoryBrowser();
314 public JComponent getPreferredFocusedComponent() {
315 return getRepositoryBrowser();
318 public boolean shouldCloseOnCross() {
319 return true;
322 public boolean isOKActionEnabled() {
323 return true;
326 public String getSelectedURL() {
327 return getRepositoryBrowser().getSelectedURL();
330 @Nullable
331 protected RepositoryTreeNode getSelectedNode() {
332 return getRepositoryBrowser().getSelectedNode();
335 protected class HistoryAction extends AnAction {
336 public void update(AnActionEvent e) {
337 e.getPresentation().setText(SvnBundle.message("repository.browser.history.action"));
338 e.getPresentation().setDescription(SvnBundle.message("repository.browser.history.action"));
339 final RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
340 e.getPresentation().setEnabled(node != null && node.getURL() != null && !myProject.isDefault());
343 public void actionPerformed(AnActionEvent e) {
344 final RepositoryTreeNode node = getSelectedNode();
345 if (node == null) {
346 return;
348 boolean isDirectory = node.getUserObject() instanceof SVNURL ||
349 (node.getSVNDirEntry() != null && node.getSVNDirEntry().getKind() == SVNNodeKind.DIR);
350 AbstractVcsHelper.getInstance(myProject).showFileHistory(
351 new SvnHistoryProvider(myVCS, node.getURL(), SVNRevision.HEAD, isDirectory),
352 VcsUtil.getFilePath(node.getURL().toString()), myVCS, node.getURL().toString());
353 node.reload(false);
357 protected class RefreshAction extends AnAction {
358 public void update(AnActionEvent e) {
359 e.getPresentation().setText(SvnBundle.message("action.name.refresh"));
360 e.getPresentation().setDescription(SvnBundle.message("repository.browser.refresh.action"));
361 e.getPresentation().setIcon(IconLoader.getIcon("/actions/sync.png"));
362 e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedNode() != null);
365 public void actionPerformed(AnActionEvent e) {
366 final RepositoryTreeNode selectedNode = getSelectedNode();
367 if (selectedNode != null) {
368 selectedNode.reload(true);
373 protected class AddLocationAction extends AnAction {
375 public AddLocationAction() {
376 super(SvnBundle.message("repository.browser.add.location.menu.item"));
379 public void update(AnActionEvent e) {
380 if (e.getPlace().equals(PLACE_TOOLBAR)) {
381 e.getPresentation().setDescription(SvnBundle.message("repository.browser.add.location.action"));
382 e.getPresentation().setText(SvnBundle.message("repository.browser.add.location.action"));
383 e.getPresentation().setIcon(IconLoader.getIcon("/general/add.png"));
387 public void actionPerformed(AnActionEvent e) {
388 final SvnApplicationSettings settings = SvnApplicationSettings.getInstance();
389 final AddRepositoryLocationDialog dialog = new AddRepositoryLocationDialog(myProject, settings.getTypedUrlsListCopy());
390 dialog.show();
391 if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
392 final String url = dialog.getSelected();
393 if (url != null && url.length() > 0) {
394 settings.addTypedUrl(url);
395 settings.addCheckoutURL(url);
396 getRepositoryBrowser().addURL(url);
402 protected class EditLocationAction extends AnAction {
403 public EditLocationAction() {
404 super(SvnBundle.message("repository.browser.edit.location.menu.item"));
407 public void update(AnActionEvent e) {
408 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
409 if (e.getPlace().equals(PLACE_TOOLBAR)) {
410 e.getPresentation().setDescription(SvnBundle.message("repository.browser.edit.location.menu.item"));
411 e.getPresentation().setText(SvnBundle.message("repository.browser.edit.location.menu.item"));
412 e.getPresentation().setIcon(IconLoader.getIcon("/actions/editSource.png"));
414 e.getPresentation().setEnabled(node != null && node.getParent() instanceof RepositoryTreeRootNode);
417 public void actionPerformed(AnActionEvent e) {
418 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
419 if (node == null || (! (node.getParent() instanceof RepositoryTreeRootNode))) {
420 return;
422 final String oldUrl = node.getURL().toString();
423 final SvnApplicationSettings settings = SvnApplicationSettings.getInstance();
424 final AddRepositoryLocationDialog dialog = new AddRepositoryLocationDialog(myProject, settings.getTypedUrlsListCopy()) {
425 @Override
426 protected String initText() {
427 return oldUrl;
430 @Override
431 public String getTitle() {
432 return SvnBundle.message("repository.browser.edit.location.dialog.title");
435 dialog.show();
436 if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
437 final String url = dialog.getSelected();
438 if (url != null && url.length() > 0) {
439 settings.addTypedUrl(url);
440 settings.removeCheckoutURL(oldUrl);
441 settings.addCheckoutURL(url);
442 final RepositoryBrowserComponent browser = getRepositoryBrowser();
443 browser.removeURL(oldUrl);
444 browser.addURL(url);
450 protected class DiscardLocationAction extends AnAction {
451 public void update(AnActionEvent e) {
452 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
453 e.getPresentation().setText(SvnBundle.message("repository.browser.discard.location.action"), true);
454 e.getPresentation().setIcon(IconLoader.getIcon("/general/remove.png"));
455 e.getPresentation().setEnabled(node != null && node.getParent() instanceof RepositoryTreeRootNode);
458 public void actionPerformed(AnActionEvent e) {
459 RepositoryTreeNode node = getSelectedNode();
460 if (node == null) {
461 return;
463 SVNURL url = node.getURL();
464 if (url != null) {
465 int rc = Messages.showYesNoDialog(myProject, SvnBundle.message("repository.browser.discard.location.prompt", url.toString()),
466 SvnBundle.message("repository.browser.discard.location.title"), Messages.getQuestionIcon());
467 if (rc != 0) {
468 return;
470 SvnApplicationSettings.getInstance().removeCheckoutURL(url.toString());
471 getRepositoryBrowser().removeURL(url.toString());
476 protected class MkDirAction extends AnAction {
477 public void update(AnActionEvent e) {
478 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
479 e.getPresentation().setText(SvnBundle.message("repository.browser.new.folder.action"), true);
480 if (node != null) {
481 SVNDirEntry entry = node.getSVNDirEntry();
482 e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR);
483 } else {
484 e.getPresentation().setEnabled(false);
488 public void actionPerformed(AnActionEvent e) {
489 // show dialog for comment and folder name, then create folder
490 // then refresh selected node.
491 final RepositoryTreeNode node = getSelectedNode();
492 if (node == null) {
493 return;
495 MkdirOptionsDialog dialog = new MkdirOptionsDialog(myProject, node.getURL());
496 dialog.show();
497 VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage());
498 if (dialog.isOK()) {
499 SVNURL url = dialog.getURL();
500 String message = dialog.getCommitMessage();
501 doMkdir(url, message);
503 final SVNURL repositoryUrl = (node.getSVNDirEntry() == null) ? node.getURL() : node.getSVNDirEntry().getRepositoryRoot();
504 final SyntheticWorker worker = new SyntheticWorker(node.getURL());
505 worker.addSyntheticChildToSelf(url, repositoryUrl, dialog.getName(), true);
507 node.reload(false);
512 protected class DiffAction extends AnAction {
513 public void update(AnActionEvent e) {
514 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
515 e.getPresentation().setText("Compare With...", true);
516 if (node != null) {
517 SVNDirEntry entry = node.getSVNDirEntry();
518 e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR);
519 } else {
520 e.getPresentation().setEnabled(false);
524 public void actionPerformed(AnActionEvent e) {
525 // show dialog for comment and folder name, then create folder
526 // then refresh selected node.
527 SVNURL root;
528 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
529 if (node == null) {
530 return;
532 while (node.getSVNDirEntry() != null) {
533 node = (RepositoryTreeNode) node.getParent();
535 root = node.getURL();
536 final RepositoryTreeNode selectedNode = getSelectedNode();
537 if (selectedNode == null) {
538 return;
540 SVNURL sourceURL = selectedNode.getURL();
541 DiffOptionsDialog dialog = new DiffOptionsDialog(myProject, root, sourceURL);
542 dialog.show();
543 if (dialog.isOK()) {
544 SVNURL targetURL = dialog.getTargetURL();
545 if (dialog.isReverseDiff()) {
546 targetURL = sourceURL;
547 sourceURL = dialog.getTargetURL();
550 final SVNURL sURL = sourceURL;
551 final SVNURL tURL = targetURL;
553 Runnable command;
554 boolean cancelable;
555 if (dialog.isUnifiedDiff()) {
556 final File targetFile = dialog.getTargetFile();
557 command = new Runnable() {
558 public void run() {
559 targetFile.getParentFile().mkdirs();
560 doUnifiedDiff(targetFile, sURL, tURL);
563 cancelable = false;
564 } else {
565 command = new Runnable() {
566 public void run() {
567 try {
568 doGraphicalDiff(sURL, tURL);
570 catch(SVNCancelException ex) {
571 // ignore
573 catch (final SVNException e1) {
574 ApplicationManager.getApplication().invokeLater(new Runnable() {
575 public void run() {
576 Messages.showErrorDialog(myProject, e1.getErrorMessage().getFullMessage(), "Error");
582 cancelable = true;
584 ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.computing.difference"),
585 cancelable, myProject);
590 protected class CopyOrMoveAction extends AnAction {
591 private final String myActionName;
592 private final String myDialogTitleKey;
593 private final boolean myMove;
595 public CopyOrMoveAction(final String actionName, final String dialogTitleKey, final boolean move) {
596 myActionName = actionName;
597 myDialogTitleKey = dialogTitleKey;
598 myMove = move;
601 public void update(AnActionEvent e) {
602 e.getPresentation().setText(myActionName);
603 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
604 e.getPresentation().setEnabled(node != null && node.getSVNDirEntry() != null);
607 public void actionPerformed(final AnActionEvent e) {
608 final RepositoryTreeNode node = getSelectedNode();
609 if (node == null) {
610 return;
612 RepositoryTreeNode rootNode = node;
613 while (! rootNode.isRepositoryRoot()) {
614 rootNode = (RepositoryTreeNode) rootNode.getParent();
617 CopyOptionsDialog dialog = new CopyOptionsDialog(SvnBundle.message(myDialogTitleKey), myProject, rootNode, node);
618 dialog.show();
619 VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage());
620 if (dialog.isOK()) {
621 SVNURL dst = dialog.getTargetURL();
622 SVNURL src = dialog.getSourceURL();
623 String message = dialog.getCommitMessage();
624 doCopy(src, dst, myMove, message);
626 final CopyMoveReloadHelper sourceReloader = myMove ? new MoveSourceReloader(node) : CopyMoveReloadHelper.EMPTY;
627 final TargetReloader destinationReloader = new TargetReloader(dialog, node, rootNode, myRepositoryBrowser);
629 sourceReloader.doSynthetic();
630 destinationReloader.doSynthetic();
631 if ((! myMove) || (! Comparing.equal(sourceReloader.parent(), destinationReloader.parent()))) {
632 destinationReloader.doRefresh();
634 sourceReloader.doRefresh();
639 private static class TargetReloader implements CopyMoveReloadHelper {
640 private final RepositoryTreeNode myDialogParent;
641 private final SVNURL myDst;
642 private final RepositoryTreeNode mySourceNode;
643 private final RepositoryTreeNode myRoot;
644 private final RepositoryBrowserComponent myBrowserComponent;
645 private final String myNewName;
647 private TargetReloader(final CopyOptionsDialog dialog, final RepositoryTreeNode node,
648 final RepositoryTreeNode root, final RepositoryBrowserComponent browserComponent) {
649 myDialogParent = dialog.getTargetParentNode();
650 myDst = dialog.getTargetURL();
651 mySourceNode = node;
652 myRoot = root;
653 myBrowserComponent = browserComponent;
654 myNewName = dialog.getName();
657 public void doRefresh() {
658 final TreeNode[] oldPath = myDialogParent.getSelfPath();
659 final TreeNode[] correctedPath = new TreeNode[oldPath.length + 1];
660 System.arraycopy(oldPath, 0, correctedPath, 1, oldPath.length);
662 myRoot.reload(new OpeningExpander(oldPath, myBrowserComponent, myDialogParent), false);
665 public void doSynthetic() {
666 final SyntheticWorker parentWorker = new SyntheticWorker(myDialogParent.getURL());
667 parentWorker.addSyntheticChildToSelf(myDst, myRoot.getURL(), myNewName, ! mySourceNode.isLeaf());
668 parentWorker.copyTreeToSelf(mySourceNode);
671 public SVNURL parent() {
672 return myDialogParent.getURL();
676 private static class MoveSourceReloader implements CopyMoveReloadHelper {
677 private final RepositoryTreeNode mySource;
678 private final RepositoryTreeNode myParent;
680 private MoveSourceReloader(final RepositoryTreeNode source) {
681 mySource = source;
682 myParent = (RepositoryTreeNode) source.getParent();
685 public void doRefresh() {
686 myParent.reload(false);
689 public void doSynthetic() {
690 final SyntheticWorker worker = new SyntheticWorker(mySource.getURL());
691 worker.removeSelf();
694 public SVNURL parent() {
695 return myParent.getURL();
699 private interface CopyMoveReloadHelper {
700 void doRefresh();
701 void doSynthetic();
702 @Nullable
703 SVNURL parent();
705 CopyMoveReloadHelper EMPTY = new CopyMoveReloadHelper() {
706 public void doRefresh() {
708 public void doSynthetic() {
710 @Nullable
711 public SVNURL parent() {
712 return null;
717 protected class CopyUrlAction extends AnAction {
718 public void update(AnActionEvent e) {
719 e.getPresentation().setText("Copy URL...");
720 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
721 e.getPresentation().setEnabled(node != null);
724 public void actionPerformed(final AnActionEvent e) {
725 final RepositoryTreeNode treeNode = getRepositoryBrowser().getSelectedNode();
726 if (treeNode != null) {
727 final String url = treeNode.getURL().toString();
728 CopyPasteManager.getInstance().setContents(new StringSelection(url));
733 protected class DeleteAction extends AnAction {
735 public void update(AnActionEvent e) {
736 e.getPresentation().setText("_Delete...");
737 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
738 e.getPresentation().setEnabled(node != null && node.getSVNDirEntry() != null);
741 public void actionPerformed(AnActionEvent e) {
742 DeleteOptionsDialog dialog = new DeleteOptionsDialog(myProject);
743 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
744 dialog.show();
745 VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage());
746 if (dialog.isOK()) {
747 SVNURL url = node.getURL();
748 String message = dialog.getCommitMessage();
749 final boolean successful = doDelete(url, message);
751 if (successful) {
752 final SyntheticWorker worker = new SyntheticWorker(url);
753 worker.removeSelf();
754 final RepositoryTreeNode parentNode = (RepositoryTreeNode) node.getParent();
755 parentNode.reload(new KeepingExpandedExpander(myRepositoryBrowser, new AfterDeletionSelectionInstaller(node)), false);
761 private class AfterDeletionSelectionInstaller implements Expander {
762 private final RepositoryTreeNode myParentNode;
763 private final String myDeletedNodeName;
764 private final boolean myIsFolder;
766 private AfterDeletionSelectionInstaller(final RepositoryTreeNode deletedNode) {
767 myParentNode = (RepositoryTreeNode) deletedNode.getParent();
768 myDeletedNodeName = deletedNode.toString();
769 myIsFolder = ! deletedNode.isLeaf();
772 public void onBeforeRefresh(final RepositoryTreeNode node) {
775 public void onAfterRefresh(final RepositoryTreeNode node) {
776 TreeNode nodeToSelect = myParentNode.getNextChildByKey(myDeletedNodeName, myIsFolder);
777 nodeToSelect = (nodeToSelect == null) ? myParentNode : nodeToSelect;
778 getRepositoryBrowser().setSelectedNode(nodeToSelect);
782 protected class ImportAction extends AnAction {
783 public void update(AnActionEvent e) {
784 e.getPresentation().setVisible(showImportAction());
785 e.getPresentation().setText(SvnBundle.message("repository.browser.import.action"));
786 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
787 final boolean running = ProjectLevelVcsManager.getInstance(myProject).isBackgroundVcsOperationRunning();
788 if (node != null) {
789 SVNDirEntry entry = node.getSVNDirEntry();
790 e.getPresentation().setEnabled((entry == null || entry.getKind() == SVNNodeKind.DIR) && (! running));
791 } else {
792 e.getPresentation().setEnabled(false);
796 public void actionPerformed(AnActionEvent e) {
797 // get directory, then import.
798 doImport();
802 protected class ExportAction extends AnAction {
803 public void update(AnActionEvent e) {
804 e.getPresentation().setText("_Export...");
805 e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedNode() != null);
807 public void actionPerformed(AnActionEvent e) {
808 final RepositoryTreeNode selectedNode = getSelectedNode();
809 if (selectedNode == null) {
810 return;
812 SVNURL url = selectedNode.getURL();
813 final File dir = selectFile("Destination directory", "Select export destination directory");
814 if (dir == null) {
815 return;
817 Project p = e.getData(PlatformDataKeys.PROJECT);
818 ExportOptionsDialog dialog = new ExportOptionsDialog(p, url, dir);
819 dialog.show();
820 if (dialog.isOK()) {
821 SvnCheckoutProvider.doExport(myProject, dir, url.toString(), dialog.getDepth(),
822 dialog.isIgnoreExternals(), dialog.isForce(), dialog.getEOLStyle());
826 protected class CheckoutAction extends AnAction {
827 public void update(AnActionEvent e) {
828 e.getPresentation().setText("_Checkout...", true);
829 RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
830 if (node != null) {
831 SVNDirEntry entry = node.getSVNDirEntry();
832 e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR);
833 } else {
834 e.getPresentation().setEnabled(false);
837 public void actionPerformed(AnActionEvent e) {
838 final RepositoryTreeNode selectedNode = getSelectedNode();
839 if (! ModalityState.NON_MODAL.equals(ModalityState.current())) {
840 doCancelAction();
842 doCheckout(ProjectLevelVcsManager.getInstance(myProject).getCompositeCheckoutListener(), selectedNode);
846 protected class BrowseChangesAction extends AnAction {
847 public BrowseChangesAction() {
848 super(SvnBundle.message("repository.browser.browse.changes.action"),
849 SvnBundle.message("repository.browser.browse.changes.description"), null);
852 public void actionPerformed(AnActionEvent e) {
853 RepositoryTreeNode node = getSelectedNode();
854 if (node == null) {
855 return;
857 SVNURL url = node.getURL();
858 AbstractVcsHelper.getInstance(myProject).showChangesBrowser(myVCS.getCommittedChangesProvider(),
859 new SvnRepositoryLocation(url.toString()),
860 "Changes in " + url.toString(), null);
863 public void update(final AnActionEvent e) {
864 e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedNode() != null);
868 protected class OpenAction extends AnAction {
869 public void update(AnActionEvent e) {
870 e.getPresentation().setEnabled(false);
871 if (myVCS == null) {
872 return;
874 e.getPresentation().setText("_Open", true);
875 e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedVcsFile() != null);
877 public void actionPerformed(AnActionEvent e) {
878 VirtualFile vcsVF = getRepositoryBrowser().getSelectedVcsFile();
879 if (vcsVF != null) {
880 FileEditorManager.getInstance(myVCS.getProject()).openFile(vcsVF, true);
885 protected class DetailsAction extends ToggleAction {
887 private boolean myIsSelected;
889 public void update(final AnActionEvent e) {
890 e.getPresentation().setDescription(SvnBundle.message("repository.browser.details.action"));
891 e.getPresentation().setText(SvnBundle.message("repository.browser.details.action"));
892 e.getPresentation().setIcon(IconLoader.getIcon("/actions/annotate.png"));
893 super.update(e);
896 public boolean isSelected(AnActionEvent e) {
897 return myIsSelected;
900 public void setSelected(AnActionEvent e, boolean state) {
901 myIsSelected = state;
902 SvnRepositoryTreeCellRenderer r = new SvnRepositoryTreeCellRenderer();
903 r.setShowDetails(state);
904 getRepositoryBrowser().getRepositoryTree().setCellRenderer(r);
908 @Nullable
909 private File selectFile(String title, String description) {
910 FileChooserDescriptor fcd = new FileChooserDescriptor(false, true, false, false, false, false);
911 fcd.setShowFileSystemRoots(true);
912 fcd.setTitle(title);
913 fcd.setDescription(description);
914 fcd.setHideIgnored(false);
915 VirtualFile[] files = FileChooser.chooseFiles(myProject, fcd, null);
916 if (files.length != 1 || files[0] == null) {
917 return null;
919 final String path = files[0].getPath();
920 if (path.endsWith(":")) { // workaround for VFS oddities with drive root (IDEADEV-20870)
921 return new File(path + "/");
923 return new File(path);
926 protected void doMkdir(final SVNURL url, final String comment) {
927 final SVNException[] exception = new SVNException[1];
928 Runnable command = new Runnable() {
929 public void run() {
930 ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
931 if (progress != null) {
932 progress.setText(SvnBundle.message("progress.text.browser.creating", url.toString()));
934 SvnVcs vcs = SvnVcs.getInstance(myProject);
935 try {
936 SVNCommitClient committer = vcs.createCommitClient();
937 committer.doMkDir(new SVNURL[] {url}, comment);
939 catch (SVNException e) {
940 exception[0] = e;
944 ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.text.create.remote.folder"), false, myProject);
945 if (exception[0] != null) {
946 Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error"));
949 private boolean doDelete(final SVNURL url, final String comment) {
950 final SVNException[] exception = new SVNException[1];
951 Runnable command = new Runnable() {
952 public void run() {
953 ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
954 if (progress != null) {
955 progress.setText(SvnBundle.message("progres.text.deleting", url.toString()));
957 SvnVcs vcs = SvnVcs.getInstance(myProject);
958 try {
959 SVNCommitClient committer = vcs.createCommitClient();
960 committer.doDelete(new SVNURL[]{url}, comment);
962 catch (SVNException e) {
963 exception[0] = e;
967 ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.title.browser.delete"), false, myProject);
968 if (exception[0] != null) {
969 Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error"));
971 return exception[0] == null;
974 private void doCopy(final SVNURL src, final SVNURL dst, final boolean move, final String comment) {
975 final SVNException[] exception = new SVNException[1];
976 Runnable command = new Runnable() {
977 public void run() {
978 ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
979 if (progress != null) {
980 progress.setText((move ? SvnBundle.message("progress.text.browser.moving", src) : SvnBundle.message("progress.text.browser.copying", src)));
981 progress.setText2(SvnBundle.message("progress.text.browser.remote.destination", dst));
983 SvnVcs vcs = SvnVcs.getInstance(myProject);
984 try {
985 SVNCopyClient committer = vcs.createCopyClient();
986 final SVNCopySource[] copySource = new SVNCopySource[] {new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, src)};
987 committer.doCopy(copySource, dst, move, true, true, comment, null);
989 catch (SVNException e) {
990 exception[0] = e;
994 String progressTitle = move ? SvnBundle.message("progress.title.browser.move") : SvnBundle.message("progress.title.browser.copy");
995 ProgressManager.getInstance().runProcessWithProgressSynchronously(command, progressTitle, false, myProject);
996 if (exception[0] != null) {
997 Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error"));
1001 protected void doCheckout(@Nullable final CheckoutProvider.Listener listener, final RepositoryTreeNode selectedNode) {
1002 if (selectedNode == null) {
1003 return;
1005 SVNURL url = selectedNode.getURL();
1007 final String relativePath;
1008 if (selectedNode.isRepositoryRoot()) {
1009 relativePath = "";
1010 } else {
1011 final SVNDirEntry dirEntry = selectedNode.getSVNDirEntry();
1012 if (dirEntry == null) {
1013 return;
1015 if (dirEntry.getRepositoryRoot() != null) {
1016 relativePath = SVNPathUtil.getRelativePath(dirEntry.getRepositoryRoot().toString(), url.toString());
1017 } else {
1018 relativePath = dirEntry.getRelativePath();
1022 File dir = selectFile(SvnBundle.message("svn.checkout.destination.directory.title"),
1023 SvnBundle.message("svn.checkout.destination.directory.description"));
1024 if (dir == null) {
1025 return;
1028 Project p = myProject;
1029 CheckoutOptionsDialog dialog = new CheckoutOptionsDialog(p, url, dir, SvnUtil.getVirtualFile(dir.getAbsolutePath()), relativePath);
1030 dialog.show();
1031 dir = dialog.getTarget();
1032 if (dialog.isOK() && dir != null) {
1033 final SVNRevision revision;
1034 try {
1035 revision = dialog.getRevision();
1036 } catch (ConfigurationException e) {
1037 Messages.showErrorDialog(SvnBundle.message("message.text.cannot.checkout", e.getMessage()), SvnBundle.message("message.title.check.out"));
1038 return;
1041 SvnCheckoutProvider.doCheckout(myProject, dir, url.toString(), revision, dialog.getDepth(), dialog.isIgnoreExternals(), listener);
1046 * @return true only if import was called
1048 protected boolean doImport() {
1049 File dir = selectFile("Import Directory", "Select directory to import into repository");
1050 if (dir == null) {
1051 return false;
1054 final RepositoryTreeNode selectedNode = getSelectedNode();
1055 if (selectedNode == null) {
1056 return false;
1058 SVNURL url = selectedNode.getURL();
1059 ImportOptionsDialog dialog = new ImportOptionsDialog(myProject, url, dir);
1060 dialog.show();
1061 VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage());
1062 if (dialog.isOK()) {
1063 File src = dialog.getTarget();
1064 boolean ignored = dialog.isIncludeIgnored();
1065 String message = dialog.getCommitMessage();
1066 SvnCheckoutProvider.doImport(myProject, src, url, dialog.getDepth(), ignored, message);
1067 selectedNode.reload(false);
1069 return dialog.isOK();
1072 private void doUnifiedDiff(File targetFile, SVNURL sourceURL, SVNURL targetURL) {
1073 OutputStream os = null;
1074 try {
1075 os = new BufferedOutputStream(new FileOutputStream(targetFile));
1076 myVCS.createDiffClient().doDiff(sourceURL, SVNRevision.HEAD, targetURL, SVNRevision.HEAD, true, false, os);
1077 } catch (IOException e1) {
1079 } catch (SVNException e1) {
1081 } finally {
1082 if (os != null) {
1083 try {
1084 os.close();
1085 } catch (IOException e1) {
1092 private void doGraphicalDiff(SVNURL sourceURL, SVNURL targetURL) throws SVNException {
1093 SVNRepository sourceRepository = myVCS.createRepository(sourceURL.toString());
1094 sourceRepository.setCanceller(new SvnProgressCanceller());
1095 SvnDiffEditor diffEditor;
1096 final long rev = sourceRepository.getLatestRevision();
1097 try {
1098 // generate Map of path->Change
1099 diffEditor = new SvnDiffEditor(sourceRepository, myVCS.createRepository(targetURL.toString()), -1, false);
1100 final ISVNEditor cancellableEditor = SVNCancellableEditor.newInstance(diffEditor, new SvnProgressCanceller(), null);
1101 sourceRepository.diff(targetURL, rev, rev, null, true, true, false, new ISVNReporterBaton() {
1102 public void report(ISVNReporter reporter) throws SVNException {
1103 reporter.setPath("", null, rev, false);
1104 reporter.finishReport();
1106 }, cancellableEditor);
1108 finally {
1109 sourceRepository.closeSession();
1111 final String sourceTitle = SVNPathUtil.tail(sourceURL.toString());
1112 final String targetTitle = SVNPathUtil.tail(targetURL.toString());
1113 showDiffEditorResults(diffEditor.getChangesMap(), sourceTitle, targetTitle, sourceURL, targetURL, rev);
1116 private void showDiffEditorResults(final Map<String, Change> changes, String sourceTitle, String targetTitle,
1117 final SVNURL sourceUrl, final SVNURL targetUrl, final long revision) {
1118 if (changes.isEmpty()) {
1119 // display no changes dialog.
1120 final String text = SvnBundle.message("repository.browser.compare.no.difference.message", sourceTitle, targetTitle);
1121 SwingUtilities.invokeLater(new Runnable() {
1122 public void run() {
1123 Messages.showInfoMessage(myProject, text, SvnBundle.message("repository.browser.compare.no.difference.title"));
1126 return;
1128 final Collection<Change> changesList = changes.values();
1129 /*final Collection<Change> changesListConverted = new ArrayList<Change>(changesList.size());
1130 for (Change change : changesList) {
1131 final FilePath path = ChangesUtil.getFilePath(change);
1132 final Change newChange = new Change(
1133 new UrlContentRevision(change.getBeforeRevision(),
1134 FilePathImpl.createNonLocal(SVNPathUtil.append(sourceUrl.toString(), path.getName()), path.isDirectory()), revision),
1135 new UrlContentRevision(change.getAfterRevision(),
1136 FilePathImpl.createNonLocal(SVNPathUtil.append(targetUrl.toString(), path.getName()), path.isDirectory()), revision));
1137 changesListConverted.add(newChange);
1140 final String title = SvnBundle.message("repository.browser.compare.title", sourceTitle, targetTitle);
1141 SwingUtilities.invokeLater(new Runnable() {
1142 public void run() {
1143 final ChangeListViewerDialog dlg = new ChangeListViewerDialog(myRepositoryBrowser, myProject, changesList, true);
1144 dlg.setTitle(title);
1145 dlg.setConvertor(new NotNullFunction<Change, Change>() {
1146 @NotNull
1147 public Change fun(final Change change) {
1148 final FilePath path = ChangesUtil.getFilePath(change);
1150 return new Change(new UrlContentRevision(change.getBeforeRevision(),
1151 FilePathImpl.createNonLocal(SVNPathUtil.append(sourceUrl.toString(), path.getPath()), path.isDirectory()), revision),
1152 new UrlContentRevision(change.getAfterRevision(),
1153 FilePathImpl.createNonLocal(SVNPathUtil.append(targetUrl.toString(), path.getPath()), path.isDirectory()), revision));
1156 dlg.show();
1161 private class CloseToolWindowAction extends AnAction {
1162 public void actionPerformed(AnActionEvent e) {
1163 disposeRepositoryBrowser();
1164 Project p = e.getData(PlatformDataKeys.PROJECT);
1165 ToolWindowManager.getInstance(p).unregisterToolWindow(BrowseRepositoryAction.REPOSITORY_BROWSER_TOOLWINDOW);
1169 public void update(AnActionEvent e) {
1170 e.getPresentation().setText("Close");
1171 e.getPresentation().setDescription("Close this tool window");
1172 e.getPresentation().setIcon(IconLoader.getIcon("/actions/cancel.png"));
1176 public void setDefaultExpander(final NotNullFunction<RepositoryBrowserComponent, Expander> expanderFactory) {
1177 myRepositoryBrowser.setLazyLoadingExpander(expanderFactory);
1180 private static class UrlContentRevision implements ContentRevision {
1181 private final ContentRevision myContentRevision;
1182 private final FilePath myPath;
1183 private final SvnRevisionNumber myNumber;
1185 private UrlContentRevision(final ContentRevision contentRevision, final FilePath path, final long revision) {
1186 myContentRevision = contentRevision;
1187 myPath = path;
1188 myNumber = new SvnRevisionNumber(SVNRevision.create(revision));
1191 public String getContent() throws VcsException {
1192 return (myContentRevision == null) ? "" : myContentRevision.getContent();
1195 @NotNull
1196 public FilePath getFile() {
1197 return myPath;
1200 @NotNull
1201 public VcsRevisionNumber getRevisionNumber() {
1202 return myNumber;