summaryrefslogtreecommitdiff
path: root/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig')
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchConfigurationDialog.form63
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchConfigurationDialog.java267
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java64
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/ConfigureBranchesAction.java66
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagAction.java187
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagDialog.form226
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagDialog.java378
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultBranchConfigInitializer.java155
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java113
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java14
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java186
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java320
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigManager.java47
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfiguration.java72
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationManager.java224
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java3
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchItem.java67
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchMapperManager.java101
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnLoadedBranchesStorage.java143
-rw-r--r--plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/UrlSerializationHelper.java111
20 files changed, 2500 insertions, 307 deletions
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchConfigurationDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchConfigurationDialog.form
new file mode 100644
index 000000000000..b68010c827db
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchConfigurationDialog.form
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.idea.svn.branchConfig.BranchConfigurationDialog">
+ <grid id="27dc6" binding="myTopPanel" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="500" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="c5128" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="configure.branches.branch.locations"/>
+ </properties>
+ </component>
+ <grid id="67204" binding="myListPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children/>
+ </grid>
+ <grid id="a677f" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="86684" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="configure.branches.trunk.location"/>
+ </properties>
+ </component>
+ <component id="9d545" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myTrunkLocationTextField">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ <component id="f6743" class="javax.swing.JLabel" binding="myErrorPrompt">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Label"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+</form>
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchConfigurationDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchConfigurationDialog.java
new file mode 100644
index 000000000000..0e0fce947088
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchConfigurationDialog.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.openapi.actionSystem.ActionToolbarPosition;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.MultiLineLabelUI;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.*;
+import com.intellij.ui.components.JBList;
+import com.intellij.util.ObjectUtils;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.svn.*;
+import org.jetbrains.idea.svn.commandLine.SvnBindException;
+import org.jetbrains.idea.svn.dialogs.SelectLocationDialog;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author yole
+ */
+public class BranchConfigurationDialog extends DialogWrapper {
+ private JPanel myTopPanel;
+ private TextFieldWithBrowseButton myTrunkLocationTextField;
+ private JList myLocationList;
+ private JPanel myListPanel;
+ private JLabel myErrorPrompt;
+ private final NewRootBunch mySvnBranchConfigManager;
+ private final VirtualFile myRoot;
+
+ public BranchConfigurationDialog(@NotNull final Project project,
+ @NotNull final SvnBranchConfigurationNew configuration,
+ final @NotNull SVNURL rootUrl,
+ @NotNull final VirtualFile root,
+ @NotNull String url) {
+ super(project, true);
+ myRoot = root;
+ init();
+ setTitle(SvnBundle.message("configure.branches.title"));
+
+ final String trunkUrl = configuration.getTrunkUrl();
+ if (trunkUrl == null || trunkUrl.trim().length() == 0) {
+ configuration.setTrunkUrl(url);
+ }
+
+ mySvnBranchConfigManager = SvnBranchConfigurationManager.getInstance(project).getSvnBranchConfigManager();
+
+ myTrunkLocationTextField.setText(configuration.getTrunkUrl());
+ myTrunkLocationTextField.addActionListener(new ActionListener() {
+ public void actionPerformed(final ActionEvent e) {
+ final String selectedUrl = SelectLocationDialog.selectLocation(project, myTrunkLocationTextField.getText());
+ if (selectedUrl != null) {
+ myTrunkLocationTextField.setText(selectedUrl);
+ }
+ }
+ });
+
+ final TrunkUrlValidator trunkUrlValidator = new TrunkUrlValidator(rootUrl, configuration);
+ myTrunkLocationTextField.getTextField().getDocument().addDocumentListener(trunkUrlValidator);
+ trunkUrlValidator.textChanged(null);
+
+ myErrorPrompt.setUI(new MultiLineLabelUI());
+ myErrorPrompt.setForeground(SimpleTextAttributes.ERROR_ATTRIBUTES.getFgColor());
+
+ final MyListModel listModel = new MyListModel(configuration);
+ myLocationList = new JBList(listModel);
+
+ myListPanel.add(
+ ToolbarDecorator.createDecorator(myLocationList)
+ .setAddAction(new AnActionButtonRunnable() {
+
+ @Nullable private SVNURL usedRootUrl;
+
+ @Override
+ public void run(AnActionButton button) {
+ Pair<String, SVNURL> result = SelectLocationDialog.selectLocation(project, ObjectUtils.notNull(usedRootUrl, rootUrl));
+ if (result != null) {
+ String selectedUrl = result.getFirst();
+ usedRootUrl = result.getSecond();
+ if (selectedUrl != null) {
+ if (!configuration.getBranchUrls().contains(selectedUrl)) {
+ configuration
+ .addBranches(selectedUrl, new InfoStorage<List<SvnBranchItem>>(new ArrayList<SvnBranchItem>(), InfoReliability.empty));
+ mySvnBranchConfigManager.reloadBranchesAsync(myRoot, selectedUrl, InfoReliability.setByUser);
+ listModel.fireItemAdded();
+ myLocationList.setSelectedIndex(listModel.getSize() - 1);
+ }
+ }
+ }
+ }
+ }).setRemoveAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton button) {
+ int selIndex = myLocationList.getSelectedIndex();
+ Object[] selection = myLocationList.getSelectedValues();
+ for (Object urlObj : selection) {
+ String url = (String)urlObj;
+ int index = configuration.getBranchUrls().indexOf(url);
+ configuration.removeBranch(url);
+ listModel.fireItemRemoved(index);
+ }
+ if (listModel.getSize() > 0) {
+ if (selIndex >= listModel.getSize())
+ selIndex = listModel.getSize() - 1;
+ myLocationList.setSelectedIndex(selIndex);
+ }
+ }
+ }).disableUpDownActions().setToolbarPosition(ActionToolbarPosition.BOTTOM).createPanel(), BorderLayout.CENTER);
+ }
+
+ private class TrunkUrlValidator extends DocumentAdapter {
+ private final SVNURL myRootUrl;
+ private final SvnBranchConfigurationNew myConfiguration;
+
+ private TrunkUrlValidator(final SVNURL rootUrl, final SvnBranchConfigurationNew configuration) {
+ myRootUrl = rootUrl;
+ myConfiguration = configuration;
+ }
+
+ protected void textChanged(final DocumentEvent e) {
+ SVNURL url = parseUrl(myTrunkLocationTextField.getText());
+
+ if (url != null) {
+ boolean isAncestor = SVNURLUtil.isAncestor(myRootUrl, url);
+ boolean areNotSame = isAncestor && !url.equals(myRootUrl);
+
+ myTrunkLocationTextField.getButton().setEnabled(isAncestor);
+ if (areNotSame) {
+ myConfiguration.setTrunkUrl(url.toDecodedString());
+ }
+ myErrorPrompt.setText(areNotSame ? "" : SvnBundle.message("configure.branches.error.wrong.url", myRootUrl));
+ }
+ }
+
+ @Nullable
+ private SVNURL parseUrl(@NotNull String url) {
+ SVNURL result = null;
+
+ try {
+ result = SvnUtil.createUrl(url);
+ }
+ catch (SvnBindException e) {
+ myErrorPrompt.setText(e.getMessage());
+ }
+
+ return result;
+ }
+ }
+
+ @Nullable
+ protected JComponent createCenterPanel() {
+ return myTopPanel;
+ }
+
+ @Override
+ @NonNls
+ protected String getDimensionServiceKey() {
+ return "Subversion.BranchConfigurationDialog";
+ }
+
+ public static void configureBranches(final Project project, final VirtualFile file) {
+ configureBranches(project, file, false);
+ }
+
+ public static void configureBranches(final Project project, final VirtualFile file, final boolean isRoot) {
+ final VirtualFile vcsRoot = (isRoot) ? file : getRoot(project, file);
+ if (vcsRoot == null) {
+ return;
+ }
+
+ final VirtualFile directory = SvnUtil.correctRoot(project, file);
+ if (directory == null) {
+ return;
+ }
+ final RootUrlInfo wcRoot = SvnVcs.getInstance(project).getSvnFileUrlMapping().getWcRootForFilePath(new File(directory.getPath()));
+ if (wcRoot == null) {
+ return;
+ }
+ final SVNURL rootUrl = wcRoot.getRepositoryUrlUrl();
+ if (rootUrl == null) {
+ Messages.showErrorDialog(project, SvnBundle.message("configure.branches.error.no.connection.title"),
+ SvnBundle.message("configure.branches.title"));
+ return;
+ }
+
+ SvnBranchConfigurationNew configuration;
+ try {
+ configuration = SvnBranchConfigurationManager.getInstance(project).get(vcsRoot);
+ }
+ catch (VcsException ex) {
+ Messages.showErrorDialog(project, "Error loading branch configuration: " + ex.getMessage(),
+ SvnBundle.message("configure.branches.title"));
+ return;
+ }
+
+ final SvnBranchConfigurationNew clonedConfiguration = configuration.copy();
+ BranchConfigurationDialog dlg = new BranchConfigurationDialog(project, clonedConfiguration, rootUrl, vcsRoot, wcRoot.getUrl());
+ dlg.show();
+ if (dlg.isOK()) {
+ SvnBranchConfigurationManager.getInstance(project).setConfiguration(vcsRoot, clonedConfiguration);
+ }
+ }
+
+ private static VirtualFile getRoot(Project project, VirtualFile file) {
+ RootUrlInfo path = SvnVcs.getInstance(project).getSvnFileUrlMapping().getWcRootForFilePath(new File(file.getPath()));
+ return path == null ? null : path.getVirtualFile();
+ }
+
+ private static class MyListModel extends AbstractListModel {
+ private final SvnBranchConfigurationNew myConfiguration;
+ private List<String> myBranchUrls;
+
+ public MyListModel(final SvnBranchConfigurationNew configuration) {
+ myConfiguration = configuration;
+ myBranchUrls = myConfiguration.getBranchUrls();
+ }
+
+ public int getSize() {
+ return myBranchUrls.size();
+ }
+
+ public Object getElementAt(final int index) {
+ return myBranchUrls.get(index);
+ }
+
+ public void fireItemAdded() {
+ final int index = myConfiguration.getBranchUrls().size() - 1;
+ myBranchUrls = myConfiguration.getBranchUrls();
+ super.fireIntervalAdded(this, index, index);
+ }
+
+ public void fireItemRemoved(final int index) {
+ myBranchUrls = myConfiguration.getBranchUrls();
+ super.fireIntervalRemoved(this, index, index);
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java
index 10f4574c7f39..ba0288da9699 100644
--- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,16 @@
package org.jetbrains.idea.svn.branchConfig;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
+import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.svn.SvnConfiguration;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.api.Depth;
import org.jetbrains.idea.svn.browse.DirectoryEntry;
import org.jetbrains.idea.svn.browse.DirectoryEntryConsumer;
-import org.jetbrains.idea.svn.integrate.SvnBranchItem;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.wc.SVNLogClient;
import org.tmatesoft.svn.core.wc.SVNRevision;
@@ -33,27 +35,60 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-public class BranchesLoader {
+/**
+ * @author Konstantin Kolosovsky.
+ */
+public class BranchesLoader implements Runnable {
+ @NotNull private final Project myProject;
+ @NotNull private final NewRootBunch myBunch;
+ @NotNull private final VirtualFile myRoot;
+ @NotNull private final String myUrl;
+ @NotNull private final InfoReliability myInfoReliability;
+ private final boolean myPassive;
+
+ public BranchesLoader(@NotNull Project project,
+ @NotNull NewRootBunch bunch,
+ @NotNull String url,
+ @NotNull InfoReliability infoReliability,
+ @NotNull VirtualFile root,
+ boolean passive) {
+ myProject = project;
+ myBunch = bunch;
+ myUrl = url;
+ myInfoReliability = infoReliability;
+ myRoot = root;
+ myPassive = passive;
+ }
- private BranchesLoader() {
+ public void run() {
+ try {
+ List<SvnBranchItem> branches = loadBranches();
+ myBunch.updateBranches(myRoot, myUrl, new InfoStorage<List<SvnBranchItem>>(branches, myInfoReliability));
+ }
+ catch (VcsException e) {
+ showError(e);
+ }
+ catch (SVNException e) {
+ showError(e);
+ }
}
- public static List<SvnBranchItem> loadBranches(final Project project, final String url, boolean passive) throws SVNException,
- VcsException {
- final SvnConfiguration configuration = SvnConfiguration.getInstance(project);
- final SvnVcs vcs = SvnVcs.getInstance(project);
- SVNURL branchesUrl = SVNURL.parseURIEncoded(url);
+ @NotNull
+ public List<SvnBranchItem> loadBranches() throws SVNException, VcsException {
+ final SvnConfiguration configuration = SvnConfiguration.getInstance(myProject);
+ final SvnVcs vcs = SvnVcs.getInstance(myProject);
+ SVNURL branchesUrl = SVNURL.parseURIEncoded(myUrl);
List<SvnBranchItem> result = new LinkedList<SvnBranchItem>();
SvnTarget target = SvnTarget.fromURL(branchesUrl);
- if (!passive) {
+ if (!myPassive) {
// TODO: Implement ability to specify interactive/non-interactive auth mode for clients
DirectoryEntryConsumer handler = createConsumer(branchesUrl, result);
vcs.getFactory(target).createBrowseClient().list(target, SVNRevision.HEAD, Depth.IMMEDIATES, handler);
}
else {
ISVNDirEntryHandler handler = createHandler(branchesUrl, result);
- SVNLogClient client = vcs.getSvnKitManager().createLogClient(configuration.getPassiveAuthenticationManager(project));
+ SVNLogClient client = vcs.getSvnKitManager().createLogClient(configuration.getPassiveAuthenticationManager(myProject));
client
.doList(target.getURL(), target.getPegRevision(), SVNRevision.HEAD, false, SVNDepth.IMMEDIATES, SVNDirEntry.DIRENT_ALL, handler);
}
@@ -62,6 +97,13 @@ public class BranchesLoader {
return result;
}
+ private void showError(Exception e) {
+ // already logged inside
+ if (InfoReliability.setByUser.equals(myInfoReliability)) {
+ VcsBalloonProblemNotifier.showOverChangesView(myProject, "Branches load error: " + e.getMessage(), MessageType.ERROR);
+ }
+ }
+
@NotNull
private static ISVNDirEntryHandler createHandler(@NotNull final SVNURL branchesUrl, @NotNull final List<SvnBranchItem> result) {
return new ISVNDirEntryHandler() {
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/ConfigureBranchesAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/ConfigureBranchesAction.java
new file mode 100644
index 000000000000..3615c4b4792d
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/ConfigureBranchesAction.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsDataKeys;
+import com.intellij.openapi.vcs.changes.ChangeList;
+import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
+import icons.SvnIcons;
+import org.jetbrains.idea.svn.SvnBundle;
+import org.jetbrains.idea.svn.SvnVcs;
+import org.jetbrains.idea.svn.history.SvnChangeList;
+
+public class ConfigureBranchesAction extends AnAction implements DumbAware {
+ public void update(final AnActionEvent e) {
+ final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
+ final Presentation presentation = e.getPresentation();
+
+ if (project == null) {
+ presentation.setEnabled(false);
+ presentation.setVisible(false);
+ return;
+ }
+
+ presentation.setText(SvnBundle.message("configure.branches.item"));
+ presentation.setDescription(SvnBundle.message("configure.branches.item"));
+ presentation.setIcon(SvnIcons.ConfigureBranches);
+
+ presentation.setVisible(true);
+
+ final ChangeList[] cls = e.getData(VcsDataKeys.CHANGE_LISTS);
+ presentation.setEnabled((cls != null) && (cls.length > 0) &&
+ (SvnVcs.getInstance(project).getName().equals(((CommittedChangeList) cls[0]).getVcs().getName())) &&
+ (((SvnChangeList) cls[0]).getRoot() != null));
+ }
+
+ public void actionPerformed(final AnActionEvent e) {
+ final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
+ final ChangeList[] cls = e.getData(VcsDataKeys.CHANGE_LISTS);
+ if ((cls == null) || (cls.length == 0) ||
+ (! SvnVcs.getInstance(project).getName().equals(((CommittedChangeList) cls[0]).getVcs().getName())) ||
+ (((SvnChangeList) cls[0]).getRoot() == null)) {
+ return;
+ }
+ final SvnChangeList svnList = (SvnChangeList) cls[0];
+ BranchConfigurationDialog.configureBranches(project, svnList.getRoot(), true);
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagAction.java
new file mode 100644
index 000000000000..da1557922a44
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagAction.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2000-2013 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.vcs.AbstractVcs;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.StatusBar;
+import com.intellij.openapi.wm.WindowManager;
+import com.intellij.vcsUtil.VcsUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.idea.svn.SvnBundle;
+import org.jetbrains.idea.svn.SvnStatusUtil;
+import org.jetbrains.idea.svn.SvnVcs;
+import org.jetbrains.idea.svn.actions.BasicAction;
+import org.jetbrains.idea.svn.checkin.CommitEventHandler;
+import org.jetbrains.idea.svn.checkin.IdeaCommitHandler;
+import org.jetbrains.idea.svn.commandLine.SvnBindException;
+import org.jetbrains.idea.svn.update.AutoSvnUpdater;
+import org.jetbrains.idea.svn.update.SingleRootSwitcher;
+import org.tmatesoft.svn.core.SVNErrorCode;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+
+import java.io.File;
+
+public class CreateBranchOrTagAction extends BasicAction {
+ protected String getActionName(AbstractVcs vcs) {
+ return SvnBundle.message("action.Subversion.Copy.text");
+ }
+
+ protected boolean needsAllFiles() {
+ return true;
+ }
+
+ protected boolean isEnabled(Project project, SvnVcs vcs, VirtualFile file) {
+ if (file == null) {
+ return false;
+ }
+ return SvnStatusUtil.isUnderControl(project, file);
+ }
+
+ protected boolean needsFiles() {
+ return true;
+ }
+
+ protected void perform(final Project project, final SvnVcs activeVcs, VirtualFile file, DataContext context)
+ throws VcsException {
+ CreateBranchOrTagDialog dialog = new CreateBranchOrTagDialog(project, true, new File(file.getPath()));
+ dialog.show();
+ if (dialog.isOK()) {
+ final String dstURL = dialog.getToURL();
+ final SVNRevision revision = dialog.getRevision();
+ final String comment = dialog.getComment();
+ final Ref<Exception> exception = new Ref<Exception>();
+ final boolean isSrcFile = dialog.isCopyFromWorkingCopy();
+ final File srcFile = new File(dialog.getCopyFromPath());
+ final SVNURL srcUrl;
+ final SVNURL dstSvnUrl;
+ final SVNURL parentUrl;
+ try {
+ srcUrl = SVNURL.parseURIEncoded(dialog.getCopyFromUrl());
+ dstSvnUrl = SVNURL.parseURIEncoded(dstURL);
+ parentUrl = dstSvnUrl.removePathTail();
+ }
+ catch (SVNException e) {
+ throw new SvnBindException(e);
+ }
+
+ if (!dirExists(activeVcs, parentUrl)) {
+ int rc = Messages.showYesNoDialog(project, "The repository path '" + parentUrl + "' does not exist. Would you like to create it?",
+ "Branch or Tag", Messages.getQuestionIcon());
+ if (rc == Messages.NO) {
+ return;
+ }
+ }
+
+ Runnable copyCommand = new Runnable() {
+ public void run() {
+ try {
+ ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
+ CommitEventHandler handler = null;
+ if (progress != null) {
+ progress.setText(SvnBundle.message("progress.text.copy.to", dstURL));
+ handler = new IdeaCommitHandler(progress);
+ }
+
+ SvnTarget source = isSrcFile ? SvnTarget.fromFile(srcFile, revision) : SvnTarget.fromURL(srcUrl, revision);
+ long newRevision = activeVcs.getFactory(source).createCopyMoveClient()
+ .copy(source, SvnTarget.fromURL(dstSvnUrl), revision, true, false, comment, handler);
+
+ updateStatusBar(newRevision, project);
+ }
+ catch (Exception e) {
+ exception.set(e);
+ }
+ }
+ };
+ ProgressManager.getInstance()
+ .runProcessWithProgressSynchronously(copyCommand, SvnBundle.message("progress.title.copy"), false, project);
+ if (!exception.isNull()) {
+ throw new VcsException(exception.get());
+ }
+
+ if (dialog.isCopyFromWorkingCopy() && dialog.isSwitchOnCreate()) {
+ SingleRootSwitcher switcher =
+ new SingleRootSwitcher(project, VcsUtil.getFilePath(srcFile, srcFile.isDirectory()), dstSvnUrl.toDecodedString());
+
+ AutoSvnUpdater.run(switcher, SvnBundle.message("action.name.switch"));
+ }
+ }
+ }
+
+ private void updateStatusBar(long revision, Project project) {
+ if (revision > 0) {
+ StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);
+
+ if (statusBar != null) {
+ statusBar.setInfo(SvnBundle.message("status.text.comitted.revision", revision));
+ }
+ }
+ }
+
+ private static boolean dirExists(@NotNull final SvnVcs vcs, @NotNull final SVNURL url) throws SvnBindException {
+ final Ref<SvnBindException> excRef = new Ref<SvnBindException>();
+ final Ref<Boolean> resultRef = new Ref<Boolean>(Boolean.TRUE);
+
+ final Runnable taskImpl = new Runnable() {
+ public void run() {
+ try {
+ vcs.getInfo(url, SVNRevision.HEAD);
+ }
+ catch (SvnBindException e) {
+ if (e.contains(SVNErrorCode.RA_ILLEGAL_URL)) {
+ resultRef.set(Boolean.FALSE);
+ }
+ else {
+ excRef.set(e);
+ }
+ }
+ }
+ };
+
+ final Application application = ApplicationManager.getApplication();
+ if (application.isDispatchThread()) {
+ ProgressManager.getInstance().runProcessWithProgressSynchronously(taskImpl, "Checking target folder", true, vcs.getProject());
+ }
+ else {
+ taskImpl.run();
+ }
+ if (!excRef.isNull()) throw excRef.get();
+ return resultRef.get();
+ }
+
+ protected void batchPerform(Project project, SvnVcs activeVcs, VirtualFile[] files, DataContext context)
+ throws VcsException {
+ }
+
+ protected boolean isBatchAction() {
+ return false;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagDialog.form
new file mode 100644
index 000000000000..96eaeaaf2c4c
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagDialog.form
@@ -0,0 +1,226 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.idea.svn.branchConfig.CreateBranchOrTagDialog">
+ <grid id="9287c" binding="myTopPanel" layout-manager="GridBagLayout">
+ <constraints>
+ <xy x="10" y="10" width="561" height="502"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <scrollpane id="57f3f" class="com.intellij.ui.components.JBScrollPane">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="3" vsize-policy="6" hsize-policy="6" anchor="8" fill="3" indent="0" use-parent-layout="false"/>
+ <gridbag top="2" left="2" bottom="2" right="2" weightx="1.0" weighty="1.0"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="cdd55" class="javax.swing.JTextArea" binding="myCommentText">
+ <constraints/>
+ <properties>
+ <columns value="25"/>
+ <lineWrap value="true"/>
+ <rows value="4"/>
+ <wrapStyleWord value="true"/>
+ </properties>
+ </component>
+ </children>
+ </scrollpane>
+ <grid id="9317" layout-manager="GridLayoutManager" row-count="7" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ <gridbag weightx="0.0" weighty="0.0"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="none" title="Copy From"/>
+ <children>
+ <component id="15c1b" class="javax.swing.JRadioButton" binding="myWorkingCopyRadioButton" default-binding="true">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="true"/>
+ <text value="&amp;Working Copy"/>
+ </properties>
+ </component>
+ <component id="643a9" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myWorkingCopyField">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="6" anchor="0" fill="3" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="27881" class="javax.swing.JRadioButton" binding="myRepositoryRadioButton" default-binding="true">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="&amp;Repository Location:"/>
+ </properties>
+ </component>
+ <component id="d7ae0" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myRepositoryField">
+ <constraints>
+ <grid row="5" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="6" anchor="0" fill="3" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="675da" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Re&amp;vision:"/>
+ </properties>
+ </component>
+ <nested-form id="23004" form-file="org/jetbrains/idea/svn/update/SvnRevisionPanel.form" binding="myRevisionPanel">
+ <constraints>
+ <grid row="6" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </nested-form>
+ <hspacer id="b36ab">
+ <constraints>
+ <grid row="6" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </hspacer>
+ <component id="13071" class="javax.swing.JButton" binding="myProjectButton">
+ <constraints>
+ <grid row="5" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <margin top="2" left="2" bottom="2" right="2"/>
+ <text value=""/>
+ <toolTipText value="Use project location"/>
+ </properties>
+ </component>
+ <component id="658a" class="javax.swing.JLabel" binding="myUseThisVariantToLabel">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="dialog.create.branch.or.tag.from.working.copy.warning"/>
+ </properties>
+ </component>
+ <component id="ff73f" class="com.intellij.ui.components.JBCheckBox" binding="mySwitchOnCreate">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <margin top="0" left="-1" bottom="2" right="3"/>
+ <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="create.branch.switch.on.create.title"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <grid id="a6551" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ <gridbag weightx="0.0" weighty="0.0"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="none" title="Copy To"/>
+ <children>
+ <component id="f1672" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myToURLText">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="adefb" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <labelFor value="6108c"/>
+ <text value="Base URL:"/>
+ </properties>
+ </component>
+ <component id="6108c" class="com.intellij.ui.ComboboxWithBrowseButton" binding="myBranchTagBaseComboBox" default-binding="true">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="6d628" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <labelFor value="d3a1"/>
+ <text value="Name:"/>
+ </properties>
+ </component>
+ <component id="d3a1" class="javax.swing.JTextField" binding="myBranchTextField">
+ <constraints>
+ <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="90" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <columns value="25"/>
+ <text value="new_branch"/>
+ </properties>
+ </component>
+ <component id="f3aad" class="javax.swing.JRadioButton" binding="myBranchOrTagRadioButton" default-binding="true">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="true"/>
+ <text value="Branch or Tag"/>
+ </properties>
+ </component>
+ <component id="2c5d3" class="javax.swing.JRadioButton" binding="myAnyLocationRadioButton" default-binding="true">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Any Location"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <component id="a28bf" class="javax.swing.JLabel" binding="myErrorLabel">
+ <constraints>
+ <grid row="4" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ <gridbag weightx="0.0" weighty="0.0"/>
+ </constraints>
+ <properties>
+ <foreground awt-color="red"/>
+ <text value=" "/>
+ </properties>
+ </component>
+ <component id="b34c" class="com.intellij.ui.TitledSeparator">
+ <constraints>
+ <grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ <gridbag weightx="0.0" weighty="0.0"/>
+ </constraints>
+ <properties>
+ <labelFor value="cdd55"/>
+ <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="label.copy.comment"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <buttonGroups>
+ <group name="buttonGroup1">
+ <member id="7c7ec"/>
+ <member id="9b927"/>
+ <member id="19731"/>
+ </group>
+ <group name="buttonGroup2">
+ <member id="15c1b"/>
+ <member id="27881"/>
+ </group>
+ <group name="buttonGroup3">
+ <member id="f3aad"/>
+ <member id="2c5d3"/>
+ </group>
+ </buttonGroups>
+</form>
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagDialog.java
new file mode 100644
index 000000000000..95ae986dd72d
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/CreateBranchOrTagDialog.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.help.HelpManager;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.ComboboxWithBrowseButton;
+import com.intellij.ui.DocumentAdapter;
+import com.intellij.ui.components.JBCheckBox;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.idea.svn.RootUrlInfo;
+import org.jetbrains.idea.svn.SvnBundle;
+import org.jetbrains.idea.svn.SvnVcs;
+import org.jetbrains.idea.svn.dialogs.SelectLocationDialog;
+import org.jetbrains.idea.svn.info.Info;
+import org.jetbrains.idea.svn.update.SvnRevisionPanel;
+import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.DocumentEvent;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: alex
+ * Date: 05.07.2005
+ * Time: 23:35:12
+ */
+public class CreateBranchOrTagDialog extends DialogWrapper {
+ private static final Logger LOG = Logger.getInstance("org.jetbrains.idea.svn.dialogs.CopyDialog");
+
+ private final File mySrcFile;
+ private String mySrcURL;
+ private final Project myProject;
+ private String myURL;
+
+ private TextFieldWithBrowseButton myToURLText;
+
+ private JTextArea myCommentText;
+ private JPanel myTopPanel;
+ private JRadioButton myWorkingCopyRadioButton;
+ private JRadioButton myRepositoryRadioButton;
+ private TextFieldWithBrowseButton myWorkingCopyField;
+ private TextFieldWithBrowseButton myRepositoryField;
+ private SvnRevisionPanel myRevisionPanel;
+ private ComboboxWithBrowseButton myBranchTagBaseComboBox;
+ private JTextField myBranchTextField;
+ private JRadioButton myBranchOrTagRadioButton;
+ private JRadioButton myAnyLocationRadioButton;
+ private JButton myProjectButton;
+ private JLabel myErrorLabel;
+ private JLabel myUseThisVariantToLabel;
+ private JBCheckBox mySwitchOnCreate;
+
+ @NonNls private static final String HELP_ID = "vcs.subversion.branch";
+ private SvnBranchConfigurationNew myBranchConfiguration;
+ private final VirtualFile mySrcVirtualFile;
+ private final String myWcRootUrl;
+
+ public CreateBranchOrTagDialog(final Project project, boolean canBeParent, File file) throws VcsException {
+ super(project, canBeParent);
+ mySrcFile = file;
+ myProject = project;
+ setResizable(true);
+ setTitle(SvnBundle.message("dialog.title.branch"));
+ getHelpAction().setEnabled(true);
+ myUseThisVariantToLabel.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));
+ myProjectButton.setIcon(AllIcons.Nodes.IdeaProject);
+ myBranchTagBaseComboBox.setPreferredSize(new Dimension(myBranchTagBaseComboBox.getPreferredSize().width,
+ myWorkingCopyField.getPreferredSize().height));
+
+ myWorkingCopyField.addBrowseFolderListener("Select Working Copy Location", "Select Location to Copy From:",
+ project, FileChooserDescriptorFactory.createSingleFolderDescriptor());
+ myWorkingCopyField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
+ protected void textChanged(final DocumentEvent e) {
+ updateControls();
+ }
+ });
+ myRepositoryField.addActionListener(new ActionListener() {
+ public void actionPerformed(final ActionEvent e) {
+ String url = SelectLocationDialog.selectLocation(project, mySrcURL);
+ if (url != null) {
+ myRepositoryField.setText(url);
+ }
+ }
+ });
+ myRepositoryField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
+ protected void textChanged(final DocumentEvent e) {
+ updateToURL();
+ }
+ });
+ myToURLText.addActionListener(new ActionListener() {
+ public void actionPerformed(final ActionEvent e) {
+ String url = myToURLText.getText();
+ String dstName = SVNPathUtil.tail(mySrcURL);
+ dstName = SVNEncodingUtil.uriDecode(dstName);
+ url = SelectLocationDialog.selectCopyDestination(myProject, SVNPathUtil.removeTail(url),
+ SvnBundle.message("label.copy.select.location.dialog.copy.as"), dstName, false);
+ if (url != null) {
+ myToURLText.setText(url);
+ }
+ }
+ });
+
+ VirtualFile srcVirtualFile;
+ RootUrlInfo root = SvnVcs.getInstance(myProject).getSvnFileUrlMapping().getWcRootForFilePath(file);
+ if (root == null) {
+ throw new VcsException("Can not find working copy for file: " + file.getPath());
+ }
+ srcVirtualFile = root.getVirtualFile();
+ if (srcVirtualFile == null) {
+ throw new VcsException("Can not find working copy for file: " + file.getPath());
+ }
+ this.mySrcVirtualFile = srcVirtualFile;
+ myWcRootUrl = root.getUrl();
+
+ myRevisionPanel.setRoot(mySrcVirtualFile);
+ myRevisionPanel.setProject(myProject);
+ myRevisionPanel.setUrlProvider(new SvnRevisionPanel.UrlProvider() {
+ public String getUrl() {
+ return mySrcURL;
+ }
+ });
+ updateBranchTagBases();
+
+ myRevisionPanel.addChangeListener(new ChangeListener() {
+ public void stateChanged(final ChangeEvent e) {
+ getOKAction().setEnabled(isOKActionEnabled());
+ }
+ });
+
+ init();
+ ActionListener listener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ updateControls();
+ }
+ };
+ myWorkingCopyRadioButton.addActionListener(listener);
+ myRepositoryRadioButton.addActionListener(listener);
+ myBranchOrTagRadioButton.addActionListener(listener);
+ myAnyLocationRadioButton.addActionListener(listener);
+ updateControls();
+ myBranchTextField.getDocument().addDocumentListener(new DocumentAdapter() {
+ protected void textChanged(final DocumentEvent e) {
+ updateToURL();
+ }
+ });
+ updateToURL();
+ myProjectButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myRepositoryField.setText(myWcRootUrl);
+ }
+ });
+ myBranchTagBaseComboBox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ BranchConfigurationDialog.configureBranches(project, mySrcVirtualFile, true);
+ updateBranchTagBases();
+ updateControls();
+ }
+ });
+ myBranchTagBaseComboBox.getComboBox().addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ updateToURL();
+ updateControls();
+ }
+ });
+ }
+
+ private void updateBranchTagBases() {
+ try {
+ myBranchConfiguration = SvnBranchConfigurationManager.getInstance(myProject).get(mySrcVirtualFile);
+ final String[] strings = ArrayUtil.toStringArray(myBranchConfiguration.getBranchUrls());
+ myBranchTagBaseComboBox.getComboBox().setModel(new DefaultComboBoxModel(strings));
+ }
+ catch (VcsException e) {
+ LOG.info(e);
+ myBranchTagBaseComboBox.setEnabled(false);
+ }
+ }
+
+ private void updateToURL() {
+ if (myBranchConfiguration == null) {
+ return;
+ }
+ String relativeUrl;
+ if (myWorkingCopyRadioButton.isSelected()) {
+ relativeUrl = myBranchConfiguration.getRelativeUrl(mySrcURL);
+ }
+ else {
+ relativeUrl = myBranchConfiguration.getRelativeUrl(myRepositoryField.getText());
+ }
+
+ final Object selectedBranch = myBranchTagBaseComboBox.getComboBox().getSelectedItem();
+ if (relativeUrl != null && selectedBranch != null) {
+ myToURLText.setText(selectedBranch.toString() + "/" + myBranchTextField.getText() + relativeUrl);
+ }
+ }
+
+ private String getToURLTextFromBranch() {
+ final Object selectedBranch = myBranchTagBaseComboBox.getComboBox().getSelectedItem();
+ if (selectedBranch != null) {
+ return selectedBranch + "/" + myBranchTextField.getText();
+ }
+ return null;
+ }
+
+ private void updateControls() {
+ myWorkingCopyField.setEnabled(myWorkingCopyRadioButton.isSelected());
+ mySwitchOnCreate.setEnabled(myWorkingCopyRadioButton.isSelected());
+ myRepositoryField.setEnabled(myRepositoryRadioButton.isSelected());
+ myRevisionPanel.setEnabled(myRepositoryRadioButton.isSelected());
+ myProjectButton.setEnabled(myRepositoryRadioButton.isSelected());
+
+ myBranchTagBaseComboBox.setEnabled(myBranchOrTagRadioButton.isSelected());
+ myBranchTextField.setEnabled(myBranchOrTagRadioButton.isSelected());
+ myToURLText.setEnabled(myAnyLocationRadioButton.isSelected());
+ myUseThisVariantToLabel.setForeground(myWorkingCopyRadioButton.isSelected() ? UIUtil.getActiveTextColor() : UIUtil.getInactiveTextColor());
+
+ getOKAction().setEnabled(isOKActionEnabled());
+ }
+
+ @NotNull
+ protected Action[] createActions() {
+ return new Action[]{getOKAction(), getCancelAction(), getHelpAction()};
+ }
+
+ protected void doHelpAction() {
+ HelpManager.getInstance().invokeHelp(HELP_ID);
+ }
+
+ protected void init() {
+ super.init();
+ SvnVcs vcs = SvnVcs.getInstance(myProject);
+ String revStr = "";
+ Info info = vcs.getInfo(mySrcFile);
+ if (info != null) {
+ mySrcURL = info.getURL() == null ? null : info.getURL().toString();
+ revStr = String.valueOf(info.getRevision());
+ myURL = mySrcURL;
+ }
+ if (myURL == null) {
+ return;
+ }
+ myWorkingCopyField.setText(mySrcFile.toString());
+ myRepositoryField.setText(mySrcURL);
+ myToURLText.setText(myURL);
+ myRevisionPanel.setRevisionText(revStr);
+ updateControls();
+
+ myWorkingCopyRadioButton.setSelected(true);
+ }
+
+ public String getComment() {
+ return myCommentText.getText();
+ }
+
+ public SVNRevision getRevision() {
+ if (myWorkingCopyRadioButton.isSelected()) {
+ return SVNRevision.WORKING;
+ }
+ else {
+ try {
+ return myRevisionPanel.getRevision();
+ }
+ catch (ConfigurationException e) {
+ return SVNRevision.UNDEFINED;
+ }
+ }
+ }
+
+ public String getToURL() {
+ if (myBranchOrTagRadioButton.isSelected()) {
+ return getToURLTextFromBranch();
+ }
+ return myToURLText.getText();
+ }
+
+ protected JComponent createCenterPanel() {
+ return myTopPanel;
+ }
+
+ public JComponent getPreferredFocusedComponent() {
+ return myToURLText;
+ }
+
+ public boolean shouldCloseOnCross() {
+ return true;
+ }
+
+ protected String getDimensionServiceKey() {
+ return "svn.copyDialog";
+ }
+
+ public boolean isOKActionEnabled() {
+ myErrorLabel.setText(" ");
+ if (myURL == null) {
+ return false;
+ }
+ if (myBranchOrTagRadioButton.isSelected() && myBranchTagBaseComboBox.getComboBox().getSelectedItem() == null) {
+ myErrorLabel.setText(SvnBundle.message("create.branch.no.base.location.error"));
+ return false;
+ }
+ String url = getToURL();
+ if (url != null && url.trim().length() > 0) {
+ if (myRepositoryRadioButton.isSelected()) {
+ SVNRevision revision;
+ try {
+ revision = myRevisionPanel.getRevision();
+ }
+ catch (ConfigurationException e) {
+ revision = SVNRevision.UNDEFINED;
+ }
+ if (!revision.isValid() || revision.isLocal()) {
+ myErrorLabel.setText(SvnBundle.message("create.branch.invalid.revision.error", myRevisionPanel.getRevisionText()));
+ return false;
+ }
+ return true;
+ }
+ else if (myWorkingCopyRadioButton.isSelected()) {
+ Info info = SvnVcs.getInstance(myProject).getInfo(mySrcFile);
+ String srcUrl = info != null && info.getURL() != null ? info.getURL().toString() : null;
+ if (srcUrl == null) {
+ myErrorLabel.setText(SvnBundle.message("create.branch.no.working.copy.error", myWorkingCopyField.getText()));
+ return false;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isCopyFromWorkingCopy() {
+ return myWorkingCopyRadioButton.isSelected();
+ }
+
+ public String getCopyFromPath() {
+ return myWorkingCopyField.getText();
+ }
+
+ public String getCopyFromUrl() {
+ return myRepositoryField.getText();
+ }
+
+ public boolean isSwitchOnCreate() {
+ return mySwitchOnCreate.isSelected();
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultBranchConfigInitializer.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultBranchConfigInitializer.java
new file mode 100644
index 000000000000..37b78459448c
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultBranchConfigInitializer.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.svn.SvnUtil;
+import org.jetbrains.idea.svn.SvnVcs;
+import org.jetbrains.idea.svn.api.Depth;
+import org.jetbrains.idea.svn.browse.DirectoryEntry;
+import org.jetbrains.idea.svn.browse.DirectoryEntryConsumer;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author Konstantin Kolosovsky.
+*/
+public class DefaultBranchConfigInitializer implements Runnable {
+
+ private static final Logger LOG = Logger.getInstance(DefaultBranchConfigInitializer.class);
+
+ @NonNls private static final String DEFAULT_TRUNK_NAME = "trunk";
+ @NonNls private static final String DEFAULT_BRANCHES_NAME = "branches";
+ @NonNls private static final String DEFAULT_TAGS_NAME = "tags";
+
+ @NotNull private final Project myProject;
+ @NotNull private final NewRootBunch myBunch;
+ @NotNull private final VirtualFile myRoot;
+
+ public DefaultBranchConfigInitializer(@NotNull Project project, @NotNull NewRootBunch bunch, @NotNull VirtualFile root) {
+ myProject = project;
+ myRoot = root;
+ myBunch = bunch;
+ }
+
+ public void run() {
+ SvnBranchConfigurationNew configuration = getDefaultConfiguration();
+
+ if (configuration != null) {
+ for (String url : configuration.getBranchUrls()) {
+ myBunch.reloadBranchesAsync(myRoot, url, InfoReliability.defaultValues);
+ }
+
+ myBunch.updateForRoot(myRoot, new InfoStorage<SvnBranchConfigurationNew>(configuration, InfoReliability.defaultValues), false);
+ }
+ }
+
+ @Nullable
+ public SvnBranchConfigurationNew getDefaultConfiguration() {
+ SvnBranchConfigurationNew result = null;
+ SvnVcs vcs = SvnVcs.getInstance(myProject);
+ SVNURL rootUrl = SvnUtil.getUrl(vcs, VfsUtilCore.virtualToIoFile(myRoot));
+
+ if (rootUrl != null) {
+ try {
+ result = getDefaultConfiguration(vcs, rootUrl);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ }
+ catch (VcsException e) {
+ LOG.info(e);
+ }
+ }
+ else {
+ LOG.info("Directory is not a working copy: " + myRoot.getPresentableUrl());
+ }
+
+ return result;
+ }
+
+ @NotNull
+ private static SvnBranchConfigurationNew getDefaultConfiguration(@NotNull SvnVcs vcs, @NotNull SVNURL url)
+ throws SVNException, VcsException {
+ SvnBranchConfigurationNew result = new SvnBranchConfigurationNew();
+ result.setTrunkUrl(url.toString());
+
+ SVNURL branchLocationsParent = getBranchLocationsParent(url);
+ if (branchLocationsParent != null) {
+ SvnTarget target = SvnTarget.fromURL(branchLocationsParent);
+
+ vcs.getFactory(target).createBrowseClient().list(target, SVNRevision.HEAD, Depth.IMMEDIATES, createHandler(result, target.getURL()));
+ }
+
+ return result;
+ }
+
+ @Nullable
+ private static SVNURL getBranchLocationsParent(@NotNull SVNURL url) throws SVNException {
+ while (!hasEmptyName(url) && !hasDefaultName(url)) {
+ url = url.removePathTail();
+ }
+
+ return hasDefaultName(url) ? url.removePathTail() : null;
+ }
+
+ private static boolean hasEmptyName(@NotNull SVNURL url) {
+ return StringUtil.isEmpty(SVNPathUtil.tail(url.getPath()));
+ }
+
+ private static boolean hasDefaultName(@NotNull SVNURL url) {
+ String name = SVNPathUtil.tail(url.getPath());
+
+ return name.equalsIgnoreCase(DEFAULT_TRUNK_NAME) ||
+ name.equalsIgnoreCase(DEFAULT_BRANCHES_NAME) ||
+ name.equalsIgnoreCase(DEFAULT_TAGS_NAME);
+ }
+
+ @NotNull
+ private static DirectoryEntryConsumer createHandler(@NotNull final SvnBranchConfigurationNew result, @NotNull final SVNURL rootPath) {
+ return new DirectoryEntryConsumer() {
+
+ @Override
+ public void consume(final DirectoryEntry entry) throws SVNException {
+ if (entry.isDirectory()) {
+ SVNURL childUrl = rootPath.appendPath(entry.getName(), false);
+
+ if (StringUtil.endsWithIgnoreCase(entry.getName(), DEFAULT_TRUNK_NAME)) {
+ result.setTrunkUrl(childUrl.toString());
+ }
+ else {
+ result.addBranches(childUrl.toString(),
+ new InfoStorage<List<SvnBranchItem>>(new ArrayList<SvnBranchItem>(0), InfoReliability.defaultValues));
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java
deleted file mode 100644
index d52798e6bfc3..000000000000
--- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2000-2009 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jetbrains.idea.svn.branchConfig;
-
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vcs.VcsException;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.idea.svn.SvnVcs;
-import org.jetbrains.idea.svn.api.Depth;
-import org.jetbrains.idea.svn.browse.DirectoryEntry;
-import org.jetbrains.idea.svn.browse.DirectoryEntryConsumer;
-import org.jetbrains.idea.svn.info.Info;
-import org.jetbrains.idea.svn.integrate.SvnBranchItem;
-import org.tmatesoft.svn.core.*;
-import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
-import org.tmatesoft.svn.core.wc.SVNRevision;
-import org.tmatesoft.svn.core.wc2.SvnTarget;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultConfigLoader {
-
- private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.branchConfig.DefaultConfigLoader");
-
- @NonNls private static final String DEFAULT_TRUNK_NAME = "trunk";
- @NonNls private static final String DEFAULT_BRANCHES_NAME = "branches";
- @NonNls private static final String DEFAULT_TAGS_NAME = "tags";
-
- private DefaultConfigLoader() {
- }
-
- @Nullable
- public static SvnBranchConfigurationNew loadDefaultConfiguration(final Project project, final VirtualFile vcsRoot) {
- try {
- final SvnVcs vcs = SvnVcs.getInstance(project);
-
- File rootFile = new File(vcsRoot.getPath());
- final Info info = vcs.getInfo(rootFile);
- if (info == null || info.getURL() == null) {
- LOG.info("Directory is not a working copy: " + vcsRoot.getPresentableUrl());
- return null;
- }
- SVNURL baseUrl = info.getURL();
-
- final SvnBranchConfigurationNew result = new SvnBranchConfigurationNew();
- result.setTrunkUrl(baseUrl.toString());
- while(true) {
- final String s = SVNPathUtil.tail(baseUrl.getPath());
- if (s.equalsIgnoreCase(DEFAULT_TRUNK_NAME) || s.equalsIgnoreCase(DEFAULT_BRANCHES_NAME) || s.equalsIgnoreCase(DEFAULT_TAGS_NAME)) {
- SVNURL rootPath = baseUrl.removePathTail();
- SvnTarget target = SvnTarget.fromURL(rootPath);
-
- vcs.getFactory(target).createBrowseClient().list(target, SVNRevision.HEAD, Depth.IMMEDIATES, createHandler(result, rootPath));
- break;
- }
- if (SVNPathUtil.removeTail(baseUrl.getPath()).length() == 0) {
- break;
- }
- baseUrl = baseUrl.removePathTail();
- }
- return result;
- }
- catch (SVNException e) {
- LOG.info(e);
- return null;
- }
- catch (VcsException e) {
- LOG.info(e);
- return null;
- }
- }
-
- @NotNull
- private static DirectoryEntryConsumer createHandler(final SvnBranchConfigurationNew result, final SVNURL rootPath) {
- return new DirectoryEntryConsumer() {
-
- @Override
- public void consume(final DirectoryEntry entry) throws SVNException {
- if (entry.isDirectory()) {
- SVNURL childUrl = rootPath.appendPath(entry.getName(), false);
-
- if (StringUtil.endsWithIgnoreCase(entry.getName(), DEFAULT_TRUNK_NAME)) {
- result.setTrunkUrl(childUrl.toString());
- }
- else {
- result.addBranches(childUrl.toString(),
- new InfoStorage<List<SvnBranchItem>>(new ArrayList<SvnBranchItem>(0), InfoReliability.defaultValues));
- }
- }
- }
- };
- }
-}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java
index 493721e3a8d1..fe10696768c1 100644
--- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java
@@ -15,9 +15,6 @@
*/
package org.jetbrains.idea.svn.branchConfig;
-import com.intellij.util.PairConsumer;
-import org.jetbrains.annotations.Nullable;
-
public class InfoStorage<T> {
public T myT;
public InfoReliability myInfoReliability;
@@ -27,14 +24,15 @@ public class InfoStorage<T> {
myInfoReliability = infoReliability;
}
- public void accept(final InfoStorage<T> infoStorage, @Nullable final PairConsumer<T, T> callbackOnUpdate) {
- if (infoStorage.myInfoReliability.shouldOverride(myInfoReliability)) {
- if (callbackOnUpdate != null) {
- callbackOnUpdate.consume(myT, infoStorage.myT);
- }
+ public boolean accept(final InfoStorage<T> infoStorage) {
+ boolean override = infoStorage.myInfoReliability.shouldOverride(myInfoReliability);
+
+ if (override) {
myT = infoStorage.myT;
myInfoReliability = infoStorage.myInfoReliability;
}
+
+ return override;
}
public T getValue() {
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java
index 2c7444ed85b0..6f7f4185a815 100644
--- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java
@@ -15,31 +15,23 @@
*/
package org.jetbrains.idea.svn.branchConfig;
-import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManagerQueue;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.CalledInBackground;
-import com.intellij.openapi.vcs.VcsException;
-import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.Consumer;
-import com.intellij.util.PairConsumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.jetbrains.idea.svn.integrate.SvnBranchItem;
+import org.jetbrains.idea.svn.SvnVcs;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
// synch is here
-public class NewRootBunch implements SvnBranchConfigManager {
+public class NewRootBunch {
private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.branchConfig.NewRootBunch");
private final Object myLock = new Object();
private final Project myProject;
@@ -52,17 +44,31 @@ public class NewRootBunch implements SvnBranchConfigManager {
myMap = new HashMap<VirtualFile, InfoStorage<SvnBranchConfigurationNew>>();
}
- public void updateForRoot(@NotNull final VirtualFile root, @NotNull final InfoStorage<SvnBranchConfigurationNew> config,
- @Nullable final PairConsumer<SvnBranchConfigurationNew, SvnBranchConfigurationNew> callbackOnUpdate) {
+ public void updateForRoot(@NotNull final VirtualFile root,
+ @NotNull final InfoStorage<SvnBranchConfigurationNew> config,
+ boolean reload) {
synchronized (myLock) {
+ final SvnBranchConfigurationNew previous;
+ boolean override;
final InfoStorage<SvnBranchConfigurationNew> existing = myMap.get(root);
+
if (existing == null) {
+ previous = null;
+ override = true;
myMap.put(root, config);
- if (callbackOnUpdate != null) {
- callbackOnUpdate.consume(null, config.getValue());
- }
- } else {
- existing.accept(config, callbackOnUpdate);
+ }
+ else {
+ previous = existing.getValue();
+ override = existing.accept(config);
+ }
+
+ if (reload && override) {
+ myBranchesLoader.run(new Runnable() {
+ @Override
+ public void run() {
+ reloadBranches(root, previous, config.getValue());
+ }
+ });
}
}
}
@@ -88,17 +94,44 @@ public class NewRootBunch implements SvnBranchConfigManager {
result = new SvnBranchConfigurationNew();
myMap.put(root, new InfoStorage<SvnBranchConfigurationNew>(result, InfoReliability.empty));
myBranchesLoader.run(new DefaultBranchConfigInitializer(myProject, this, root));
- } else {
+ }
+ else {
result = value.getValue();
}
return result;
}
}
- public void reloadBranches(@NotNull final VirtualFile root, @NotNull final String branchParentUrl,
- final Consumer<List<SvnBranchItem>> callback) {
- ApplicationManager.getApplication().executeOnPooledThread(new BranchesLoadRunnable(myProject, this, branchParentUrl,
- InfoReliability.setByUser, root, callback, true));
+ public void reloadBranchesAsync(@NotNull final VirtualFile root,
+ @NotNull final String branchLocation,
+ @NotNull final InfoReliability reliability) {
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ @Override
+ public void run() {
+ reloadBranches(root, branchLocation, reliability, true);
+ }
+ });
+ }
+
+ public void reloadBranches(@NotNull VirtualFile root, @Nullable SvnBranchConfigurationNew prev, @NotNull SvnBranchConfigurationNew next) {
+ final Set<String> oldUrls = (prev == null) ? Collections.<String>emptySet() : new HashSet<String>(prev.getBranchUrls());
+ final SvnVcs vcs = SvnVcs.getInstance(myProject);
+ if (!vcs.isVcsBackgroundOperationsAllowed(root)) return;
+
+ for (String newBranchUrl : next.getBranchUrls()) {
+ // check if cancel had been put
+ if (!vcs.isVcsBackgroundOperationsAllowed(root)) return;
+ if (!oldUrls.contains(newBranchUrl)) {
+ reloadBranches(root, newBranchUrl, InfoReliability.defaultValues, true);
+ }
+ }
+ }
+
+ public void reloadBranches(@NotNull VirtualFile root,
+ @NotNull String branchLocation,
+ @NotNull InfoReliability reliability,
+ boolean passive) {
+ new BranchesLoader(myProject, this, branchLocation, reliability, root, passive).run();
}
@Nullable
@@ -108,28 +141,11 @@ public class NewRootBunch implements SvnBranchConfigManager {
try {
final SvnBranchConfigurationNew configuration = myMap.get(root).getValue();
final String group = configuration.getGroupToLoadToReachUrl(svnurl);
- final Runnable runnable = new Runnable() {
- public void run() {
- final SvnBranchConfigurationNew reloadedConfiguration = myMap.get(root).getValue();
- try {
- result.set(reloadedConfiguration.getWorkingBranch(svnurl));
- }
- catch (SVNException e) {
- //
- }
- }
- };
- if (group == null) {
- runnable.run();
- } else {
- new BranchesLoadRunnable(myProject, this, group, InfoReliability.setByUser, root,
- new Consumer<List<SvnBranchItem>>() {
- public void consume(List<SvnBranchItem> svnBranchItems) {
- runnable.run();
- }
- }, true).run();
+ if (group != null) {
+ reloadBranches(root, group, InfoReliability.setByUser, true);
}
+ result.set(myMap.get(root).getValue().getWorkingBranch(svnurl));
}
catch (SVNException e) {
//
@@ -137,88 +153,6 @@ public class NewRootBunch implements SvnBranchConfigManager {
return result.get();
}
- public static class BranchesLoadRunnable implements Runnable {
- private final Project myProject;
- private final SvnBranchConfigManager myBunch;
- private final VirtualFile myRoot;
- @Nullable
- private final Consumer<List<SvnBranchItem>> myCallback;
- private final String myUrl;
- private final InfoReliability myInfoReliability;
- private boolean myPassive;
-
- public BranchesLoadRunnable(final Project project,
- final SvnBranchConfigManager bunch,
- final String url,
- final InfoReliability infoReliability,
- final VirtualFile root,
- @Nullable final Consumer<List<SvnBranchItem>> callback,
- boolean passive) {
- myProject = project;
- myBunch = bunch;
- myUrl = url;
- myInfoReliability = infoReliability;
- myRoot = root;
- myCallback = callback;
- myPassive = passive;
- }
-
- public void run() {
- boolean callbackCalled = false;
- try {
- final List<SvnBranchItem> items = BranchesLoader.loadBranches(myProject, myUrl, myPassive);
- myBunch.updateBranches(myRoot, myUrl, new InfoStorage<List<SvnBranchItem>>(items, myInfoReliability));
- if (myCallback != null) {
- myCallback.consume(items);
- callbackCalled = true;
- }
- }
- catch (VcsException e) {
- showError(e);
- }
- catch (SVNException e) {
- showError(e);
- }
- finally {
- // callback must be called by contract
- if (myCallback != null && (! callbackCalled)) {
- myCallback.consume(null);
- }
- }
- }
-
- private void showError(Exception e) {
- // already logged inside
- if (InfoReliability.setByUser.equals(myInfoReliability)) {
- VcsBalloonProblemNotifier.showOverChangesView(myProject, "Branches load error: " + e.getMessage(), MessageType.ERROR);
- }
- }
- }
-
- private static class DefaultBranchConfigInitializer implements Runnable {
- private final Project myProject;
- private final SvnBranchConfigManager myBunch;
- private final VirtualFile myRoot;
-
- private DefaultBranchConfigInitializer(final Project project, final SvnBranchConfigManager bunch, final VirtualFile root) {
- myProject = project;
- myRoot = root;
- myBunch = bunch;
- }
-
- public void run() {
- final SvnBranchConfigurationNew result = DefaultConfigLoader.loadDefaultConfiguration(myProject, myRoot);
- if (result != null) {
- final Application application = ApplicationManager.getApplication();
- for (String url : result.getBranchUrls()) {
- application.executeOnPooledThread(new BranchesLoadRunnable(myProject, myBunch, url, InfoReliability.defaultValues, myRoot, null,
- true));
- }
- myBunch.updateForRoot(myRoot, new InfoStorage<SvnBranchConfigurationNew>(result, InfoReliability.defaultValues), null);
- }
- }
- }
-
public Map<VirtualFile, SvnBranchConfigurationNew> getMapCopy() {
synchronized (myLock) {
final Map<VirtualFile, SvnBranchConfigurationNew> result = new HashMap<VirtualFile, SvnBranchConfigurationNew>();
@@ -228,4 +162,4 @@ public class NewRootBunch implements SvnBranchConfigManager {
return result;
}
}
-}
+} \ No newline at end of file
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java
new file mode 100644
index 000000000000..445ce4e9afe3
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.popup.*;
+import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.components.JBList;
+import com.intellij.util.continuation.ModalityIgnorantBackgroundableTask;
+import com.intellij.util.text.DateFormatUtil;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.svn.RootUrlInfo;
+import org.jetbrains.idea.svn.SvnBundle;
+import org.jetbrains.idea.svn.SvnFileUrlMapping;
+import org.jetbrains.idea.svn.SvnVcs;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author yole
+ */
+public class SelectBranchPopup {
+ private final static String CONFIGURE_MESSAGE = SvnBundle.message("configure.branches.item");
+
+ private SelectBranchPopup() {
+ }
+
+ public interface BranchSelectedCallback {
+ void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision);
+ }
+
+ public static void show(Project project, VirtualFile file, BranchSelectedCallback callback, final String title) {
+ show(project, file, callback, title, null);
+ }
+
+ public static void show(Project project, VirtualFile file, BranchSelectedCallback callback, final String title, final Component component) {
+ final SvnFileUrlMapping urlMapping = SvnVcs.getInstance(project).getSvnFileUrlMapping();
+ final SVNURL svnurl = urlMapping.getUrlForFile(new File(file.getPath()));
+ if (svnurl == null) {
+ return;
+ }
+ final RootUrlInfo rootUrlInfo = urlMapping.getWcRootForUrl(svnurl.toString());
+ if (rootUrlInfo == null) {
+ return;
+ }
+
+ // not vcs root but wc root is ok
+ showForBranchRoot(project, rootUrlInfo.getVirtualFile(), callback, title, component);
+ }
+
+ public static void showForBranchRoot(Project project, VirtualFile vcsRoot, BranchSelectedCallback callback, final String title) {
+ showForBranchRoot(project, vcsRoot, callback, title, null);
+ }
+
+ public static void showForBranchRoot(Project project, VirtualFile vcsRoot, BranchSelectedCallback callback, final String title,
+ final Component component) {
+ final SvnBranchConfigurationNew configuration;
+ try {
+ configuration = SvnBranchConfigurationManager.getInstance(project).get(vcsRoot);
+ }
+ catch (VcsException e1) {
+ Messages.showErrorDialog(project, SvnBundle.message("getting.branch.configuration.error", e1.getMessage()), title);
+ return;
+ }
+
+ final List<String> items = new ArrayList<String>();
+ if (! StringUtil.isEmptyOrSpaces(configuration.getTrunkUrl())) {
+ items.add(getTrunkString(configuration));
+ }
+ for (String url : configuration.getBranchUrls()) {
+ items.add(url);
+ }
+ items.add(CONFIGURE_MESSAGE);
+
+ BranchBasesPopupStep step = new BranchBasesPopupStep(project, vcsRoot, configuration, callback, items, title, component);
+ final ListPopup listPopup = JBPopupFactory.getInstance().createListPopup(step);
+ step.showPopupAt(listPopup);
+ }
+
+ private static String getTrunkString(final SvnBranchConfigurationNew configuration) {
+ return configuration.getTrunkUrl() + " (trunk)";
+ }
+
+ private static class BranchBasesPopupStep extends BaseListPopupStep<String> {
+ protected final Project myProject;
+ private final VirtualFile myVcsRoot;
+ private final SvnBranchConfigurationNew myConfiguration;
+ private final boolean myTopLevel;
+ private BranchSelectedCallback myCallback;
+ private final Component myComponent;
+
+ private static final String REFRESH_MESSAGE = SvnBundle.message("refresh.branches.item");
+ private String myTrunkString;
+
+ protected BranchBasesPopupStep(final Project project,
+ final VirtualFile vcsRoot,
+ final SvnBranchConfigurationNew configuration,
+ boolean topLevel,
+ final BranchSelectedCallback callback,
+ Component component) {
+ myProject = project;
+ myVcsRoot = vcsRoot;
+ myConfiguration = configuration;
+ myTrunkString = getTrunkString(configuration);
+ myTopLevel = topLevel;
+ myCallback = callback;
+ myComponent = component;
+ }
+
+ public BranchBasesPopupStep(final Project project, final VirtualFile vcsRoot,
+ final SvnBranchConfigurationNew configuration,
+ final BranchSelectedCallback callback,
+ final List<String> items,
+ final String title,
+ Component component) {
+ this(project, vcsRoot, configuration, true, callback, component);
+ init(title, items, null);
+ }
+
+ @Override
+ public ListSeparator getSeparatorAbove(final String value) {
+ return (CONFIGURE_MESSAGE.equals(value)) ||
+ (REFRESH_MESSAGE.equals(value)) ? new ListSeparator("") : null;
+ }
+
+ @NotNull
+ @Override
+ public String getTextFor(final String value) {
+ int pos = value.lastIndexOf('/');
+ if (pos < 0) {
+ return value;
+ }
+ if (myTopLevel && ((myConfiguration.getTrunkUrl() == null) || (! value.startsWith(myConfiguration.getTrunkUrl())))) {
+ return value.substring(pos+1) + "...";
+ }
+ return value.substring(pos+1);
+ }
+
+ @Override
+ public boolean hasSubstep(final String selectedValue) {
+ return false;
+ }
+
+ @Override
+ public PopupStep onChosen(final String selectedValue, final boolean finalChoice) {
+ if (CONFIGURE_MESSAGE.equals(selectedValue)) {
+ return doFinalStep(new Runnable() {
+ public void run() {
+ BranchConfigurationDialog.configureBranches(myProject, myVcsRoot, true);
+ }
+ });
+ }
+ else if (myTrunkString.equals(selectedValue)) {
+ return doFinalStep(new Runnable() {
+ public void run() {
+ myCallback.branchSelected(myProject, myConfiguration, myConfiguration.getTrunkUrl(), -1);
+ }
+ });
+ }
+ else if (!myTopLevel || selectedValue.equals(myConfiguration.getTrunkUrl())) {
+ return doFinalStep(new Runnable() {
+ public void run() {
+ myCallback.branchSelected(myProject, myConfiguration, selectedValue, -1);
+ }
+ });
+ }
+ else {
+ showBranchPopup(selectedValue);
+ }
+ return FINAL_CHOICE;
+ }
+
+ @Nullable
+ private void loadBranches(final String selectedBranchesHolder, final Runnable runnable) {
+ final ProgressManager pm = ProgressManager.getInstance();
+ pm.run(new ModalityIgnorantBackgroundableTask(myProject, SvnBundle.message("compare.with.branch.progress.loading.branches")) {
+ @Override
+ protected void doInAwtIfFail(Exception e) {
+ runnable.run();
+ }
+
+ @Override
+ protected void doInAwtIfCancel() {
+ runnable.run();
+ }
+
+ @Override
+ protected void doInAwtIfSuccess() {
+ runnable.run();
+ }
+
+ @Override
+ protected void runImpl(@NotNull ProgressIndicator indicator) {
+ final NewRootBunch manager = SvnBranchConfigurationManager.getInstance(myProject).getSvnBranchConfigManager();
+
+ manager.reloadBranches(myVcsRoot, selectedBranchesHolder, InfoReliability.setByUser, false);
+ }
+ });
+ }
+
+ private void showBranchPopup(final String selectedValue) {
+ List<SvnBranchItem> branches = myConfiguration.getBranches(selectedValue);
+ if (branches == null) {
+ return;
+ }
+
+ final Object[] items = new Object[branches.size() + 1];
+ System.arraycopy(branches.toArray(), 0, items, 0, branches.size());
+ items[items.length - 1] = REFRESH_MESSAGE;
+
+ final JList branchList = new JBList(items);
+ branchList.setCellRenderer(new BranchRenderer());
+ final JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(branchList)
+ .setTitle(SVNPathUtil.tail(selectedValue))
+ .setResizable(true)
+ //.setDimensionServiceKey("Svn.CompareWithBranchPopup")
+ .setItemChoosenCallback(new Runnable() {
+ public void run() {
+ if (REFRESH_MESSAGE.equals(branchList.getSelectedValue())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ loadBranches(selectedValue, new Runnable() {
+ @Override
+ public void run() {
+ showBranchPopup(selectedValue);
+ }
+ });
+ }
+ });
+ return;
+ }
+ SvnBranchItem item = (SvnBranchItem)branchList.getSelectedValue();
+ if (item != null) {
+ myCallback.branchSelected(myProject, myConfiguration, item.getUrl(), item.getRevision());
+ }
+ }
+ })
+ .createPopup();
+ showPopupAt(popup);
+ }
+
+ public void showPopupAt(final JBPopup listPopup) {
+ if (myComponent == null) {
+ listPopup.showCenteredInCurrentWindow(myProject);
+ } else {
+ listPopup.showInCenterOf(myComponent);
+ }
+ }
+ }
+
+ private static class BranchRenderer extends JPanel implements ListCellRenderer {
+ private final JLabel myUrlLabel = new JLabel();
+ private final JLabel myDateLabel = new JLabel();
+
+ public BranchRenderer() {
+ super(new BorderLayout());
+ add(myUrlLabel, BorderLayout.WEST);
+ add(myDateLabel, BorderLayout.EAST);
+ myUrlLabel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
+ myDateLabel.setHorizontalAlignment(JLabel.RIGHT);
+ myDateLabel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
+ myDateLabel.setForeground(UIUtil.getInactiveTextColor());
+ }
+
+ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ if (isSelected || cellHasFocus) {
+ setBackground(UIUtil.getListSelectionBackground());
+ final Color selectedForegroundColor = UIUtil.getListSelectionForeground();
+ myUrlLabel.setForeground(selectedForegroundColor);
+ myDateLabel.setForeground(selectedForegroundColor);
+ setForeground(selectedForegroundColor);
+ }
+ else {
+ setBackground(UIUtil.getListBackground());
+ final Color foregroundColor = UIUtil.getListForeground();
+ myUrlLabel.setForeground(foregroundColor);
+ myDateLabel.setForeground(UIUtil.getInactiveTextColor());
+ setForeground(foregroundColor);
+ }
+ if (value instanceof String) {
+ myUrlLabel.setText((String) value);
+ myDateLabel.setText("");
+ } else {
+ SvnBranchItem item = (SvnBranchItem) value;
+ myUrlLabel.setText(SVNPathUtil.tail(item.getUrl()));
+ final long creationMillis = item.getCreationDateMillis();
+ myDateLabel.setText((creationMillis > 0) ? DateFormatUtil.formatDate(creationMillis) : "");
+ }
+ return this;
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigManager.java
deleted file mode 100644
index 0a47a7771171..000000000000
--- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigManager.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2000-2009 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jetbrains.idea.svn.branchConfig;
-
-import com.intellij.openapi.vcs.CalledInBackground;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.Consumer;
-import com.intellij.util.PairConsumer;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.idea.svn.integrate.SvnBranchItem;
-import org.tmatesoft.svn.core.SVNURL;
-
-import java.util.List;
-import java.util.Map;
-
-public interface SvnBranchConfigManager {
- void updateForRoot(@NotNull VirtualFile root, @NotNull InfoStorage<SvnBranchConfigurationNew> config,
- @Nullable final PairConsumer<SvnBranchConfigurationNew, SvnBranchConfigurationNew> callbackOnUpdate);
-
- void updateBranches(@NotNull VirtualFile root, @NotNull String branchesParent,
- @NotNull InfoStorage<List<SvnBranchItem>> items);
-
- @NotNull
- SvnBranchConfigurationNew getConfig(@NotNull VirtualFile root);
-
- void reloadBranches(@NotNull VirtualFile root, @NotNull String branchParentUrl,
- Consumer<List<SvnBranchItem>> callback);
- @Nullable
- @CalledInBackground
- SVNURL getWorkingBranchWithReload(final SVNURL svnurl, final VirtualFile root);
-
- Map<VirtualFile, SvnBranchConfigurationNew> getMapCopy();
-}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfiguration.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfiguration.java
new file mode 100644
index 000000000000..0c6138fd08c2
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfiguration.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.idea.svn.branchConfig;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Default constructor and setters are necessary for serialization purposes.
+ *
+ * @author yole
+ */
+@SuppressWarnings("UnusedDeclaration")
+public class SvnBranchConfiguration {
+ private String myTrunkUrl;
+ @NotNull private List<String> myBranchUrls;
+ private boolean myUserinfoInUrl;
+
+ public SvnBranchConfiguration() {
+ myBranchUrls = new ArrayList<String>();
+ }
+
+ public SvnBranchConfiguration(String trunkUrl, @NotNull List<String> branchUrls, boolean userinfoInUrl) {
+ myTrunkUrl = trunkUrl;
+ myBranchUrls = branchUrls;
+ Collections.sort(myBranchUrls);
+ myUserinfoInUrl = userinfoInUrl;
+ }
+
+ public boolean isUserinfoInUrl() {
+ return myUserinfoInUrl;
+ }
+
+ public void setUserinfoInUrl(final boolean userinfoInUrl) {
+ myUserinfoInUrl = userinfoInUrl;
+ }
+
+ public void setBranchUrls(@NotNull List<String> branchUrls) {
+ myBranchUrls = branchUrls;
+ Collections.sort(myBranchUrls);
+ }
+
+ public void setTrunkUrl(final String trunkUrl) {
+ myTrunkUrl = trunkUrl;
+ }
+
+ public String getTrunkUrl() {
+ return myTrunkUrl;
+ }
+
+ @NotNull
+ public List<String> getBranchUrls() {
+ return myBranchUrls;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationManager.java
new file mode 100644
index 000000000000..772e5edd2755
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationManager.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.lifecycle.PeriodicalTasksCloser;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressManagerQueue;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.changes.committed.VcsConfigurationChangeListener;
+import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
+import com.intellij.openapi.vcs.impl.VcsInitObject;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.messages.MessageBus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.idea.svn.SvnVcs;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author yole
+ */
+@State(
+ name = "SvnBranchConfigurationManager",
+ storages = {
+ @Storage(
+ file = StoragePathMacros.PROJECT_FILE
+ )}
+)
+public class SvnBranchConfigurationManager implements PersistentStateComponent<SvnBranchConfigurationManager.ConfigurationBean> {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationManager");
+ private final Project myProject;
+ private final ProjectLevelVcsManager myVcsManager;
+ private final SvnLoadedBranchesStorage myStorage;
+ private final ProgressManagerQueue myBranchesLoader;
+ private boolean myIsInitialized;
+
+ public SvnBranchConfigurationManager(final Project project,
+ final ProjectLevelVcsManager vcsManager,
+ final SvnLoadedBranchesStorage storage) {
+ myProject = project;
+ myVcsManager = vcsManager;
+ myStorage = storage;
+ myBranchesLoader = new ProgressManagerQueue(myProject, "Subversion Branches Preloader");
+ // TODO: Seems that ProgressManagerQueue is not suitable here at least for some branches loading tasks. For instance,
+ // TODO: for DefaultConfigLoader it would be better to run modal cancellable task - so branches structure could be detected and
+ // TODO: shown in dialog. Currently when "Configure Branches" is invoked for the first time - no branches are shown.
+ // TODO: If "Cancel" is pressed and "Configure Branches" invoked once again - already detected (in background) branches are shown.
+ ((ProjectLevelVcsManagerImpl) vcsManager).addInitializationRequest(VcsInitObject.BRANCHES, new Runnable() {
+ @Override
+ public void run() {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ @Override
+ public void run() {
+ if (myProject.isDisposed()) return;
+ myBranchesLoader.start();
+ }
+ });
+ }
+ });
+ myBunch = new NewRootBunch(project, myBranchesLoader);
+ }
+
+ public static SvnBranchConfigurationManager getInstance(final Project project) {
+ SvnBranchConfigurationManager result = PeriodicalTasksCloser.getInstance().safeGetService(project, SvnBranchConfigurationManager.class);
+
+ if (result != null) {
+ result.initialize();
+ }
+
+ return result;
+ }
+
+ public static class ConfigurationBean {
+ public Map<String, SvnBranchConfiguration> myConfigurationMap = new TreeMap<String, SvnBranchConfiguration>();
+ /**
+ * version of "support SVN in IDEA". for features tracking. should grow
+ */
+ public Long myVersion;
+ public boolean mySupportsUserInfoFilter;
+ }
+
+ public Long getSupportValue() {
+ return myConfigurationBean.myVersion;
+ }
+
+ private ConfigurationBean myConfigurationBean = new ConfigurationBean();
+ private final NewRootBunch myBunch;
+
+ public SvnBranchConfigurationNew get(@NotNull final VirtualFile vcsRoot) throws VcsException {
+ return myBunch.getConfig(vcsRoot);
+ }
+
+ public NewRootBunch getSvnBranchConfigManager() {
+ return myBunch;
+ }
+
+ public void setConfiguration(final VirtualFile vcsRoot, final SvnBranchConfigurationNew configuration) {
+ myBunch.updateForRoot(vcsRoot, new InfoStorage<SvnBranchConfigurationNew>(configuration, InfoReliability.setByUser), true);
+
+ SvnBranchMapperManager.getInstance().notifyBranchesChanged(myProject, vcsRoot, configuration);
+
+ final MessageBus messageBus = myProject.getMessageBus();
+ messageBus.syncPublisher(VcsConfigurationChangeListener.BRANCHES_CHANGED).execute(myProject, vcsRoot);
+ }
+
+ public ConfigurationBean getState() {
+ final ConfigurationBean result = new ConfigurationBean();
+ result.myVersion = myConfigurationBean.myVersion;
+ final UrlSerializationHelper helper = new UrlSerializationHelper(SvnVcs.getInstance(myProject));
+
+ for (VirtualFile root : myBunch.getMapCopy().keySet()) {
+ final String key = root.getPath();
+ final SvnBranchConfigurationNew configOrig = myBunch.getConfig(root);
+ final SvnBranchConfiguration configuration =
+ new SvnBranchConfiguration(configOrig.getTrunkUrl(), configOrig.getBranchUrls(), configOrig.isUserinfoInUrl());
+
+ result.myConfigurationMap.put(key, helper.prepareForSerialization(configuration));
+ }
+ result.mySupportsUserInfoFilter = true;
+ return result;
+ }
+
+ public void loadState(ConfigurationBean object) {
+ myConfigurationBean = object;
+ }
+
+ private synchronized void initialize() {
+ if (!myIsInitialized) {
+ myIsInitialized = true;
+
+ preloadBranches(resolveAllBranchPoints());
+ }
+ }
+
+ @NotNull
+ private Set<Pair<VirtualFile, SvnBranchConfigurationNew>> resolveAllBranchPoints() {
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final UrlSerializationHelper helper = new UrlSerializationHelper(SvnVcs.getInstance(myProject));
+ final Set<Pair<VirtualFile, SvnBranchConfigurationNew>> branchPointsToLoad = ContainerUtil.newHashSet();
+ for (Map.Entry<String, SvnBranchConfiguration> entry : myConfigurationBean.myConfigurationMap.entrySet()) {
+ final SvnBranchConfiguration configuration = entry.getValue();
+ final VirtualFile root = lfs.refreshAndFindFileByIoFile(new File(entry.getKey()));
+ if (root == null) {
+ LOG.info("root not found: " + entry.getKey());
+ continue;
+ }
+
+ final SvnBranchConfiguration configToConvert;
+ if ((! myConfigurationBean.mySupportsUserInfoFilter) || configuration.isUserinfoInUrl()) {
+ configToConvert = helper.afterDeserialization(entry.getKey(), configuration);
+ } else {
+ configToConvert = configuration;
+ }
+ final SvnBranchConfigurationNew newConfig = new SvnBranchConfigurationNew();
+ newConfig.setTrunkUrl(configToConvert.getTrunkUrl());
+ newConfig.setUserinfoInUrl(configToConvert.isUserinfoInUrl());
+ for (String branchUrl : configToConvert.getBranchUrls()) {
+ List<SvnBranchItem> stored = getStored(branchUrl);
+ if (stored != null && ! stored.isEmpty()) {
+ newConfig.addBranches(branchUrl, new InfoStorage<List<SvnBranchItem>>(stored, InfoReliability.setByUser));
+ } else {
+ branchPointsToLoad.add(Pair.create(root, newConfig));
+ newConfig.addBranches(branchUrl, new InfoStorage<List<SvnBranchItem>>(new ArrayList<SvnBranchItem>(), InfoReliability.empty));
+ }
+ }
+
+ myBunch.updateForRoot(root, new InfoStorage<SvnBranchConfigurationNew>(newConfig, InfoReliability.setByUser), false);
+ }
+ return branchPointsToLoad;
+ }
+
+ private void preloadBranches(@NotNull final Collection<Pair<VirtualFile, SvnBranchConfigurationNew>> branchPoints) {
+ ((ProjectLevelVcsManagerImpl) myVcsManager).addInitializationRequest(VcsInitObject.BRANCHES, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ public void run() {
+ try {
+ for (Pair<VirtualFile, SvnBranchConfigurationNew> pair : branchPoints) {
+ myBunch.reloadBranches(pair.getFirst(), null, pair.getSecond());
+ }
+ }
+ catch (ProcessCanceledException e) {
+ //
+ }
+ }
+ });
+ }
+ });
+ }
+
+ private List<SvnBranchItem> getStored(String branchUrl) {
+ Collection<SvnBranchItem> collection = myStorage.get(branchUrl);
+ if (collection == null) return null;
+ final List<SvnBranchItem> items = new ArrayList<SvnBranchItem>(collection);
+ Collections.sort(items);
+ return items;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java
index 4102210a657a..3ce3c986c8ae 100644
--- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java
@@ -24,7 +24,6 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnUtil;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.info.Info;
-import org.jetbrains.idea.svn.integrate.SvnBranchItem;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
@@ -98,7 +97,7 @@ public class SvnBranchConfigurationNew {
LOG.info("Branches list not updated for : '" + branchParentName + "; since config has changed.");
return;
}
- current.accept(items, null);
+ current.accept(items);
}
public Map<String, InfoStorage<List<SvnBranchItem>>> getBranchMap() {
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchItem.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchItem.java
new file mode 100644
index 000000000000..a0bd0868b6d2
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchItem.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.idea.svn.branchConfig;
+
+import java.util.Date;
+
+/**
+ * @author yole
+ */
+public class SvnBranchItem implements Comparable<SvnBranchItem> {
+ private String myUrl;
+ private long myCreationDateMillis;
+ private long myRevision;
+
+ // to be serializable
+ public SvnBranchItem() {
+ }
+
+ public SvnBranchItem(final String url, final Date creationDate, final long revision) {
+ myUrl = url;
+ // descendant can be passed (and is passed) (java.util.Date is not final)
+ myCreationDateMillis = creationDate.getTime();
+ myRevision = revision;
+ }
+
+ public void setUrl(final String url) {
+ myUrl = url;
+ }
+
+ public void setCreationDateMillis(final long creationDate) {
+ myCreationDateMillis = creationDate;
+ }
+
+ public void setRevision(final long revision) {
+ myRevision = revision;
+ }
+
+ public String getUrl() {
+ return myUrl;
+ }
+
+ public long getCreationDateMillis() {
+ return myCreationDateMillis;
+ }
+
+ public long getRevision() {
+ return myRevision;
+ }
+
+ public int compareTo(SvnBranchItem o) {
+ // === -compare()
+ return myCreationDateMillis < o.myCreationDateMillis ? 1 : ((myCreationDateMillis == o.myCreationDateMillis) ? 0 : -1);
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchMapperManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchMapperManager.java
new file mode 100644
index 000000000000..f9f99bea1e17
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchMapperManager.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Holds what working copies we have for URLs
+ */
+@State(
+ name = "SvnBranchMapperManager",
+ storages = {
+ @Storage(
+ file = StoragePathMacros.APP_CONFIG + "/other.xml"
+ )}
+)
+public class SvnBranchMapperManager implements PersistentStateComponent<SvnBranchMapperManager.SvnBranchMapperHolder> {
+ private SvnBranchMapperHolder myStateHolder;
+
+ public static SvnBranchMapperManager getInstance() {
+ return ServiceManager.getService(SvnBranchMapperManager.class);
+ }
+
+ public SvnBranchMapperManager() {
+ myStateHolder = new SvnBranchMapperHolder();
+ }
+
+ public SvnBranchMapperHolder getState() {
+ return myStateHolder;
+ }
+
+ public void loadState(final SvnBranchMapperHolder state) {
+ myStateHolder = state;
+ }
+
+ public void put(final String url, final String value) {
+ myStateHolder.put(url, value);
+ }
+
+ public void remove(final String url, final File value) {
+ final Set<String> set = myStateHolder.get(url);
+ if (set != null) {
+ set.remove(value.getAbsolutePath());
+ }
+ }
+
+ public void notifyBranchesChanged(final Project project, final VirtualFile vcsRoot, final SvnBranchConfigurationNew configuration) {
+ final Map<String, String> map = configuration.getUrl2FileMappings(project, vcsRoot);
+ if (map != null) {
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ public Set<String> get(final String key) {
+ return myStateHolder.get(key);
+ }
+
+ public static class SvnBranchMapperHolder {
+ public Map<String, Set<String>> myMapping;
+
+ public SvnBranchMapperHolder() {
+ myMapping = new HashMap<String, Set<String>>();
+ }
+
+ public void put(final String key, final String value) {
+ Set<String> files = myMapping.get(key);
+ if (files == null) {
+ files = new HashSet<String>();
+ myMapping.put(key, files);
+ }
+ files.add(value);
+ }
+
+ public Set<String> get(final String key) {
+ return myMapping.get(key);
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnLoadedBranchesStorage.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnLoadedBranchesStorage.java
new file mode 100644
index 000000000000..041e33cc5ac7
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnLoadedBranchesStorage.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.persistent.SmallMapSerializer;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.EnumeratorStringDescriptor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 8/24/11
+ * Time: 1:21 PM
+ */
+public class SvnLoadedBranchesStorage {
+ private final Object myLock;
+ private SmallMapSerializer<String, Map<String, Collection<SvnBranchItem>>> myState;
+ private final File myFile;
+ private final Project myProject;
+
+ public SvnLoadedBranchesStorage(final Project project) {
+ myProject = project;
+ final File vcsFile = new File(PathManager.getSystemPath(), "vcs");
+ File file = new File(vcsFile, "svn_branches");
+ file.mkdirs();
+ myFile = new File(file, project.getLocationHash());
+ myLock = new Object();
+ }
+
+ @Nullable
+ public Collection<SvnBranchItem> get(final String url) {
+ synchronized (myLock) {
+ if (myState == null) return null;
+ Map<String, Collection<SvnBranchItem>> map = myState.get("");
+ return map == null ? null : map.get(SvnBranchConfigurationNew.ensureEndSlash(url));
+ }
+ }
+
+ public void activate() {
+ synchronized (myLock) {
+ myState = new SmallMapSerializer<String, Map<String, Collection<SvnBranchItem>>>(myFile, new EnumeratorStringDescriptor(), createExternalizer());
+ }
+ }
+
+
+ public void deactivate() {
+ Map<String, Collection<SvnBranchItem>> branchLocationToBranchItemsMap = ContainerUtil.newHashMap();
+ SvnBranchConfigurationManager manager = SvnBranchConfigurationManager.getInstance(myProject);
+ Map<VirtualFile,SvnBranchConfigurationNew> mapCopy = manager.getSvnBranchConfigManager().getMapCopy();
+ for (Map.Entry<VirtualFile, SvnBranchConfigurationNew> entry : mapCopy.entrySet()) {
+ Map<String,InfoStorage<List<SvnBranchItem>>> branchMap = entry.getValue().getBranchMap();
+ for (Map.Entry<String, InfoStorage<List<SvnBranchItem>>> storageEntry : branchMap.entrySet()) {
+ branchLocationToBranchItemsMap.put(storageEntry.getKey(), storageEntry.getValue().getValue());
+ }
+ }
+ synchronized (myLock) {
+ // TODO: Possibly implement optimization - do not perform save if there are no changes in branch locations and branch items
+ // ensure myState.put() is called - so myState will treat itself as dirty and myState.force() will invoke real persisting
+ myState.put("", branchLocationToBranchItemsMap);
+ myState.force();
+ myState = null;
+ }
+ }
+
+ private DataExternalizer<Map<String, Collection<SvnBranchItem>>> createExternalizer() {
+ return new DataExternalizer<Map<String, Collection<SvnBranchItem>>>() {
+ @Override
+ public void save(@NotNull DataOutput out, Map<String, Collection<SvnBranchItem>> value) throws IOException {
+ out.writeInt(value.size());
+ ArrayList<String> keys = new ArrayList<String>(value.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ out.writeUTF(key);
+ List<SvnBranchItem> list = new ArrayList<SvnBranchItem>(value.get(key));
+ Collections.sort(list, SerializationComparator.getInstance());
+ out.writeInt(list.size());
+ for (SvnBranchItem item : list) {
+ out.writeUTF(item.getUrl());
+ out.writeLong(item.getCreationDateMillis());
+ out.writeLong(item.getRevision());
+ }
+ }
+ }
+
+ @Override
+ public Map<String, Collection<SvnBranchItem>> read(@NotNull DataInput in) throws IOException {
+ final HashMap<String, Collection<SvnBranchItem>> map = new HashMap<String, Collection<SvnBranchItem>>();
+ int mapSize = in.readInt();
+ for (int i = 0; i < mapSize; i++) {
+ final String key = in.readUTF();
+ final int size = in.readInt();
+ final ArrayList<SvnBranchItem> list = new ArrayList<SvnBranchItem>(size);
+ for (int j = 0; j < size; j++) {
+ String url = in.readUTF();
+ long creation = in.readLong();
+ long revision = in.readLong();
+ list.add(new SvnBranchItem(url, new Date(creation), revision));
+ }
+ map.put(key, list);
+ }
+ return map;
+ }
+ };
+ }
+
+ private static class SerializationComparator implements Comparator<SvnBranchItem> {
+ private final static SerializationComparator ourInstance = new SerializationComparator();
+
+ public static SerializationComparator getInstance() {
+ return ourInstance;
+ }
+
+ @Override
+ public int compare(SvnBranchItem o1, SvnBranchItem o2) {
+ return o1.getUrl().compareToIgnoreCase(o2.getUrl());
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/UrlSerializationHelper.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/UrlSerializationHelper.java
new file mode 100644
index 000000000000..36c16f33a0fc
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/UrlSerializationHelper.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.idea.svn.branchConfig;
+
+import com.intellij.openapi.util.Ref;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.svn.SvnUtil;
+import org.jetbrains.idea.svn.SvnVcs;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author Konstantin Kolosovsky.
+*/
+public class UrlSerializationHelper {
+ private final SvnVcs myVcs;
+
+ public UrlSerializationHelper(final SvnVcs vcs) {
+ myVcs = vcs;
+ }
+
+ public SvnBranchConfiguration prepareForSerialization(final SvnBranchConfiguration configuration) {
+ final Ref<Boolean> withUserInfo = new Ref<Boolean>();
+ final String trunkUrl = serializeUrl(configuration.getTrunkUrl(), withUserInfo);
+
+ if (Boolean.FALSE.equals(withUserInfo.get())) {
+ return configuration;
+ }
+
+ final List<String> branches = configuration.getBranchUrls();
+ final List<String> newBranchesList = new ArrayList<String>(branches.size());
+ for (String s : branches) {
+ newBranchesList.add(serializeUrl(s, withUserInfo));
+ }
+
+ return new SvnBranchConfiguration(trunkUrl, newBranchesList, withUserInfo.isNull() ? false : withUserInfo.get());
+ }
+
+ public SvnBranchConfiguration afterDeserialization(final String path, final SvnBranchConfiguration configuration) {
+ if (! configuration.isUserinfoInUrl()) {
+ return configuration;
+ }
+ final String userInfo = getUserInfo(path);
+ if (userInfo == null) {
+ return configuration;
+ }
+
+ final String newTrunkUrl = deserializeUrl(configuration.getTrunkUrl(), userInfo);
+ final List<String> branches = configuration.getBranchUrls();
+ final List<String> newBranchesList = new ArrayList<String>(branches.size());
+ for (String s : branches) {
+ newBranchesList.add(deserializeUrl(s, userInfo));
+ }
+
+ return new SvnBranchConfiguration(newTrunkUrl, newBranchesList, userInfo.length() > 0);
+ }
+
+ private static String serializeUrl(final String url, final Ref<Boolean> withUserInfo) {
+ if (Boolean.FALSE.equals(withUserInfo.get())) {
+ return url;
+ }
+ try {
+ final SVNURL svnurl = SVNURL.parseURIEncoded(url);
+ if (withUserInfo.isNull()) {
+ final String userInfo = svnurl.getUserInfo();
+ withUserInfo.set((userInfo != null) && (userInfo.length() > 0));
+ }
+ if (withUserInfo.get()) {
+ return SVNURL.create(svnurl.getProtocol(), null, svnurl.getHost(), SvnUtil.resolvePort(svnurl), svnurl.getURIEncodedPath(), true)
+ .toString();
+ }
+ }
+ catch (SVNException e) {
+ //
+ }
+ return url;
+ }
+
+ @Nullable
+ private String getUserInfo(final String path) {
+ final SVNURL svnurl = myVcs.getSvnFileUrlMapping().getUrlForFile(new File(path));
+ return svnurl != null ? svnurl.getUserInfo() : null;
+ }
+
+ private static String deserializeUrl(final String url, final String userInfo) {
+ try {
+ final SVNURL svnurl = SVNURL.parseURIEncoded(url);
+ return SVNURL.create(svnurl.getProtocol(), userInfo, svnurl.getHost(), SvnUtil.resolvePort(svnurl), svnurl.getURIEncodedPath(),
+ true).toString();
+ } catch (SVNException e) {
+ return url;
+ }
+ }
+}