diff options
Diffstat (limited to 'plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig')
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="&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="&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&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; + } + } +} |