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
;
74 import javax
.swing
.tree
.TreeNode
;
76 import java
.awt
.datatransfer
.StringSelection
;
77 import java
.awt
.event
.KeyEvent
;
78 import java
.awt
.event
.MouseAdapter
;
79 import java
.awt
.event
.MouseEvent
;
81 import java
.util
.ArrayList
;
82 import java
.util
.Collection
;
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
) {
108 public RepositoryBrowserDialog(Project project
, final boolean showFiles
) {
109 super(project
, true);
110 myShowFiles
= showFiles
;
112 myVCS
= SvnVcs
.getInstance(project
);
113 setTitle("SVN Repository Browser");
115 setOKButtonText(CommonBundle
.getCloseButtonText());
116 getHelpAction().setEnabled(true);
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() {
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() {
164 public void collapseAll() {
165 JTree tree
= getRepositoryBrowser().getRepositoryTree();
166 int row
= tree
.getRowCount() - 1;
168 tree
.collapseRow(row
);
173 public boolean canCollapse() {
176 }, getRepositoryBrowser());
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());
191 group
.addSeparator();
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
);
229 public JComponent
createBrowserComponent(final boolean toolWindow
) {
230 JPanel panel
= new JPanel();
231 panel
.setLayout(new GridBagLayout());
233 GridBagConstraints gc
= new GridBagConstraints();
245 gc
.fill
= GridBagConstraints
.BOTH
;
246 gc
.anchor
= GridBagConstraints
.WEST
;
248 panel
.add(getRepositoryBrowser(), gc
);
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
) {
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
) {
271 public void mousePressed(MouseEvent e
) {
274 public void mouseReleased(MouseEvent 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());
283 tree
.setSelectionRow(row
);
285 JPopupMenu popupMenu
= createPopup(toolWindow
);
286 if (popupMenu
!= null) {
287 popupMenu
.show(e
.getComponent(), e
.getX(), e
.getY());
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() {
311 disposeRepositoryBrowser();
314 public JComponent
getPreferredFocusedComponent() {
315 return getRepositoryBrowser();
318 public boolean shouldCloseOnCross() {
322 public boolean isOKActionEnabled() {
326 public String
getSelectedURL() {
327 return getRepositoryBrowser().getSelectedURL();
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();
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());
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());
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
))) {
422 final String oldUrl
= node
.getURL().toString();
423 final SvnApplicationSettings settings
= SvnApplicationSettings
.getInstance();
424 final AddRepositoryLocationDialog dialog
= new AddRepositoryLocationDialog(myProject
, settings
.getTypedUrlsListCopy()) {
426 protected String
initText() {
431 public String
getTitle() {
432 return SvnBundle
.message("repository.browser.edit.location.dialog.title");
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
);
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();
463 SVNURL url
= node
.getURL();
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());
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);
481 SVNDirEntry entry
= node
.getSVNDirEntry();
482 e
.getPresentation().setEnabled(entry
== null || entry
.getKind() == SVNNodeKind
.DIR
);
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();
495 MkdirOptionsDialog dialog
= new MkdirOptionsDialog(myProject
, node
.getURL());
497 VcsConfiguration
.getInstance(myProject
).saveCommitMessage(dialog
.getCommitMessage());
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);
512 protected class DiffAction
extends AnAction
{
513 public void update(AnActionEvent e
) {
514 RepositoryTreeNode node
= getRepositoryBrowser().getSelectedNode();
515 e
.getPresentation().setText("Compare With...", true);
517 SVNDirEntry entry
= node
.getSVNDirEntry();
518 e
.getPresentation().setEnabled(entry
== null || entry
.getKind() == SVNNodeKind
.DIR
);
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.
528 RepositoryTreeNode node
= getRepositoryBrowser().getSelectedNode();
532 while (node
.getSVNDirEntry() != null) {
533 node
= (RepositoryTreeNode
) node
.getParent();
535 root
= node
.getURL();
536 final RepositoryTreeNode selectedNode
= getSelectedNode();
537 if (selectedNode
== null) {
540 SVNURL sourceURL
= selectedNode
.getURL();
541 DiffOptionsDialog dialog
= new DiffOptionsDialog(myProject
, root
, sourceURL
);
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
;
555 if (dialog
.isUnifiedDiff()) {
556 final File targetFile
= dialog
.getTargetFile();
557 command
= new Runnable() {
559 targetFile
.getParentFile().mkdirs();
560 doUnifiedDiff(targetFile
, sURL
, tURL
);
565 command
= new Runnable() {
568 doGraphicalDiff(sURL
, tURL
);
570 catch(SVNCancelException ex
) {
573 catch (final SVNException e1
) {
574 ApplicationManager
.getApplication().invokeLater(new Runnable() {
576 Messages
.showErrorDialog(myProject
, e1
.getErrorMessage().getFullMessage(), "Error");
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
;
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();
612 RepositoryTreeNode rootNode
= node
;
613 while (! rootNode
.isRepositoryRoot()) {
614 rootNode
= (RepositoryTreeNode
) rootNode
.getParent();
617 CopyOptionsDialog dialog
= new CopyOptionsDialog(SvnBundle
.message(myDialogTitleKey
), myProject
, rootNode
, node
);
619 VcsConfiguration
.getInstance(myProject
).saveCommitMessage(dialog
.getCommitMessage());
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();
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
) {
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());
694 public SVNURL
parent() {
695 return myParent
.getURL();
699 private interface CopyMoveReloadHelper
{
705 CopyMoveReloadHelper EMPTY
= new CopyMoveReloadHelper() {
706 public void doRefresh() {
708 public void doSynthetic() {
711 public SVNURL
parent() {
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();
745 VcsConfiguration
.getInstance(myProject
).saveCommitMessage(dialog
.getCommitMessage());
747 SVNURL url
= node
.getURL();
748 String message
= dialog
.getCommitMessage();
749 final boolean successful
= doDelete(url
, message
);
752 final SyntheticWorker worker
= new SyntheticWorker(url
);
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();
789 SVNDirEntry entry
= node
.getSVNDirEntry();
790 e
.getPresentation().setEnabled((entry
== null || entry
.getKind() == SVNNodeKind
.DIR
) && (! running
));
792 e
.getPresentation().setEnabled(false);
796 public void actionPerformed(AnActionEvent e
) {
797 // get directory, then import.
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) {
812 SVNURL url
= selectedNode
.getURL();
813 final File dir
= selectFile("Destination directory", "Select export destination directory");
817 Project p
= e
.getData(PlatformDataKeys
.PROJECT
);
818 ExportOptionsDialog dialog
= new ExportOptionsDialog(p
, url
, dir
);
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();
831 SVNDirEntry entry
= node
.getSVNDirEntry();
832 e
.getPresentation().setEnabled(entry
== null || entry
.getKind() == SVNNodeKind
.DIR
);
834 e
.getPresentation().setEnabled(false);
837 public void actionPerformed(AnActionEvent e
) {
838 final RepositoryTreeNode selectedNode
= getSelectedNode();
839 if (! ModalityState
.NON_MODAL
.equals(ModalityState
.current())) {
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();
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);
874 e
.getPresentation().setText("_Open", true);
875 e
.getPresentation().setEnabled(getRepositoryBrowser().getSelectedVcsFile() != null);
877 public void actionPerformed(AnActionEvent e
) {
878 VirtualFile vcsVF
= getRepositoryBrowser().getSelectedVcsFile();
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"));
896 public boolean isSelected(AnActionEvent e
) {
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
);
909 private File
selectFile(String title
, String description
) {
910 FileChooserDescriptor fcd
= new FileChooserDescriptor(false, true, false, false, false, false);
911 fcd
.setShowFileSystemRoots(true);
913 fcd
.setDescription(description
);
914 fcd
.setHideIgnored(false);
915 VirtualFile
[] files
= FileChooser
.chooseFiles(myProject
, fcd
, null);
916 if (files
.length
!= 1 || files
[0] == 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() {
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
);
936 SVNCommitClient committer
= vcs
.createCommitClient();
937 committer
.doMkDir(new SVNURL
[] {url
}, comment
);
939 catch (SVNException 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() {
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
);
959 SVNCommitClient committer
= vcs
.createCommitClient();
960 committer
.doDelete(new SVNURL
[]{url
}, comment
);
962 catch (SVNException 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() {
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
);
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
) {
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) {
1005 SVNURL url
= selectedNode
.getURL();
1007 final String relativePath
;
1008 if (selectedNode
.isRepositoryRoot()) {
1011 final SVNDirEntry dirEntry
= selectedNode
.getSVNDirEntry();
1012 if (dirEntry
== null) {
1015 if (dirEntry
.getRepositoryRoot() != null) {
1016 relativePath
= SVNPathUtil
.getRelativePath(dirEntry
.getRepositoryRoot().toString(), url
.toString());
1018 relativePath
= dirEntry
.getRelativePath();
1022 File dir
= selectFile(SvnBundle
.message("svn.checkout.destination.directory.title"),
1023 SvnBundle
.message("svn.checkout.destination.directory.description"));
1028 Project p
= myProject
;
1029 CheckoutOptionsDialog dialog
= new CheckoutOptionsDialog(p
, url
, dir
, SvnUtil
.getVirtualFile(dir
.getAbsolutePath()), relativePath
);
1031 dir
= dialog
.getTarget();
1032 if (dialog
.isOK() && dir
!= null) {
1033 final SVNRevision revision
;
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"));
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");
1054 final RepositoryTreeNode selectedNode
= getSelectedNode();
1055 if (selectedNode
== null) {
1058 SVNURL url
= selectedNode
.getURL();
1059 ImportOptionsDialog dialog
= new ImportOptionsDialog(myProject
, url
, dir
);
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;
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
) {
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();
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
);
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() {
1123 Messages
.showInfoMessage(myProject
, text
, SvnBundle
.message("repository.browser.compare.no.difference.title"));
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() {
1143 final ChangeListViewerDialog dlg
= new ChangeListViewerDialog(myRepositoryBrowser
, myProject
, changesList
, true);
1144 dlg
.setTitle(title
);
1145 dlg
.setConvertor(new NotNullFunction
<Change
, Change
>() {
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
));
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
;
1188 myNumber
= new SvnRevisionNumber(SVNRevision
.create(revision
));
1191 public String
getContent() throws VcsException
{
1192 return (myContentRevision
== null) ?
"" : myContentRevision
.getContent();
1196 public FilePath
getFile() {
1201 public VcsRevisionNumber
getRevisionNumber() {