From 43bed3f1e75e283ae1976d6776307ec94633eb41 Mon Sep 17 00:00:00 2001
From: "Nadya.Zabrodina"
Date: Tue, 21 May 2013 20:18:24 +0400
Subject: [PATCH] HgBranches feature first take (git-style )
* New "Branches" action for HG invokes HgBranchPopup. So does the click on HgBranchWidget on status bar.
* HgBranchPopup shows common actions ("New branch") and the list of repositories and not active branches.
* Merge and UpdateTo branch actions are supported from PopUp view.
* HgRepository stores information about branches
* HgRepositoryReader collects information from files
* HgRepositoryUpdater call repositpry reader if .hg files were changed externally
* Checkbox for new branch creation push added
* Check if .hg internal files exist added
* Additional checks for new repository(without any commit) added (cache,branch and other files maybe not exist)
---
.../org/zmlx/hg4idea/HgVcsMessages.properties | 1 +
plugins/hg4idea/src/META-INF/plugin.xml | 1 +
.../src/org/zmlx/hg4idea/HgPusher.java | 1 +
.../action/HgAbstractGlobalAction.java | 2 +-
.../zmlx/hg4idea/action/HgBranchPopup.java | 244 ++++++++++++++++++
.../hg4idea/action/HgBranchPopupActions.java | 226 ++++++++++++++++
.../zmlx/hg4idea/action/HgBranchesAction.java | 50 ++++
.../command/HgBranchCreateCommand.java | 50 ++++
.../zmlx/hg4idea/command/HgPushCommand.java | 14 +-
.../hg4idea/repo/HgRepoStateException.java | 30 +++
.../org/zmlx/hg4idea/repo/HgRepository.java | 119 +++++++++
.../zmlx/hg4idea/repo/HgRepositoryFiles.java | 82 ++++++
.../zmlx/hg4idea/repo/HgRepositoryImpl.java | 215 +++++++++++++++
.../zmlx/hg4idea/repo/HgRepositoryReader.java | 165 ++++++++++++
.../hg4idea/repo/HgRepositoryUpdater.java | 146 +++++++++++
.../hg4idea/status/ui/HgStatusWidget.java | 37 ++-
.../src/org/zmlx/hg4idea/ui/HgPushDialog.form | 20 +-
.../src/org/zmlx/hg4idea/ui/HgPushDialog.java | 6 +
.../src/org/zmlx/hg4idea/util/HgUtil.java | 69 ++++-
19 files changed, 1459 insertions(+), 19 deletions(-)
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchPopup.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchPopupActions.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchesAction.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/command/HgBranchCreateCommand.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepoStateException.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepository.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryFiles.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryImpl.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryReader.java
create mode 100644 plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryUpdater.java
diff --git a/plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties b/plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties
index 804bc45776d9..7ba4790d25f3 100644
--- a/plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties
+++ b/plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties
@@ -127,3 +127,4 @@ hg4idea.history.copy.revision.number=Copy Revision Number
hg4idea.changelist.column.branch=Branch
hg4idea.annotation.tool.tip=commit {0}\nAuthor: {1}\nDate: {2}\n\n{3}
+hg4idea.push.asNewBranch=push as &new remote branch
diff --git a/plugins/hg4idea/src/META-INF/plugin.xml b/plugins/hg4idea/src/META-INF/plugin.xml
index ef53adfcd3d5..e875f749273d 100644
--- a/plugins/hg4idea/src/META-INF/plugin.xml
+++ b/plugins/hg4idea/src/META-INF/plugin.xml
@@ -55,6 +55,7 @@
+
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java
index 6b91e826aff0..1ddff9b62423 100644
--- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java
@@ -137,6 +137,7 @@ public class HgPusher {
command.setRevision(dialog.getRevision());
command.setForce(dialog.isForce());
command.setBranch(dialog.getBranch());
+ command.setIsNewBranch(dialog.isNewBranch());
return command;
}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java
index 84a9e8028b0e..930cae34318b 100644
--- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java
@@ -62,7 +62,7 @@ abstract class HgAbstractGlobalAction extends AnAction {
protected abstract void execute(Project project, Collection repositories, @Nullable VirtualFile selectedRepo);
- protected static void handleException(Project project, Exception e) {
+ public static void handleException(Project project, Exception e) {
LOG.info(e);
new HgCommandResultNotifier(project).notifyError(null, "Error", e.getMessage());
}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchPopup.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchPopup.java
new file mode 100644
index 000000000000..7975847b8435
--- /dev/null
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchPopup.java
@@ -0,0 +1,244 @@
+/*
+ * 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.zmlx.hg4idea.action;
+
+import com.intellij.openapi.actionSystem.ActionGroup;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.ListPopup;
+import com.intellij.openapi.ui.popup.PopupStep;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.ErrorLabel;
+import com.intellij.ui.JBColor;
+import com.intellij.ui.components.panels.OpaquePanel;
+import com.intellij.ui.popup.PopupFactoryImpl;
+import com.intellij.ui.popup.WizardPopup;
+import com.intellij.ui.popup.list.ListPopupImpl;
+import com.intellij.ui.popup.list.PopupListElementRenderer;
+import com.intellij.util.PlatformIcons;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.zmlx.hg4idea.repo.HgRepository;
+import org.zmlx.hg4idea.repo.HgRepositoryImpl;
+import org.zmlx.hg4idea.util.HgUtil;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ *
+ * The popup which allows to quickly switch and control Hg branches.
+ *
+ *
+ * Use {@link #asListPopup()} to achieve the {@link com.intellij.openapi.ui.popup.ListPopup} itself.
+ *
+ *
+ * @author NadyaZabrodina
+ */
+public class HgBranchPopup {
+
+ private final Project myProject;
+
+ private final HgRepository myCurrentRepository;
+ private final ListPopupImpl myPopup;
+
+ public ListPopup asListPopup() {
+ return myPopup;
+ }
+
+ /**
+ * @param currentRepository Current repository, which means the repository of the currently open or selected file.
+ */
+ public static HgBranchPopup getInstance(@NotNull Project project, @NotNull HgRepository currentRepository) {
+ return new HgBranchPopup(project, currentRepository);
+ }
+
+ private HgBranchPopup(@NotNull Project project, @NotNull HgRepository currentRepository) {
+ myProject = project;
+ myCurrentRepository = currentRepository;
+ String title = createPopupTitle(currentRepository);
+
+ Condition preselectActionCondition = new Condition() {
+ @Override
+ public boolean value(AnAction action) {
+ return false;
+ }
+ };
+
+ myPopup = new HgBranchActionGroupPopup(title, project, preselectActionCondition);
+
+ setCurrentBranchInfo();
+ }
+
+
+ @NotNull
+ private static String createPopupTitle(@NotNull HgRepository currentRepository) {
+ String title = "Hg Branches";
+
+ title += " in " + currentRepository.getRoot().getName();
+
+ return title;
+ }
+
+ private void setCurrentBranchInfo() {
+ String branchText = "Current branch : ";
+ myPopup.setAdText(branchText + HgUtil.getDisplayableBranchText(myCurrentRepository), SwingConstants.CENTER);
+ }
+
+
+ private ActionGroup createActions() {
+ DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
+
+ fillPopupWithCurrentRepositoryActions(popupGroup, createRepositoriesActions());
+
+ popupGroup.addSeparator();
+ return popupGroup;
+ }
+
+
+ private DefaultActionGroup createRepositoriesActions() {
+ DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
+ popupGroup.addSeparator("Repositories");
+ for (VirtualFile repository : HgUtil.getHgRepositories(myProject)) {
+ HgRepository repo = HgRepositoryImpl.getFullInstance(repository, myProject, myProject);
+ popupGroup.add(new RootAction(repo, null));
+ }
+ return popupGroup;
+ }
+
+
+ private void fillPopupWithCurrentRepositoryActions(@NotNull DefaultActionGroup popupGroup, @Nullable DefaultActionGroup actions) {
+ popupGroup.addAll(new HgBranchPopupActions(myProject, myCurrentRepository).createActions(actions));
+ }
+
+ private static class RootAction extends ActionGroup {
+
+ private final HgRepository myRepository;
+
+ /**
+ * @param currentRepository Pass null in the case of common repositories - none repository will be highlighted then.
+ */
+ RootAction(@NotNull HgRepository repository, @Nullable HgRepository currentRepository) {
+ super(repository.getRoot().getName(), true);
+ myRepository = repository;
+ if (repository.equals(currentRepository)) {
+ getTemplatePresentation().setIcon(PlatformIcons.CHECK_ICON);
+ }
+ }
+
+ @NotNull
+ @Override
+ public AnAction[] getChildren(@Nullable AnActionEvent e) {
+ ActionGroup group = new HgBranchPopupActions(myRepository.getProject(), myRepository).createActions(null);
+ return group.getChildren(e);
+ }
+
+ @NotNull
+ public String getCaption() {
+ String captionText = "Current branch in " + myRepository.getRoot().getName() + ": ";
+ return captionText + HgUtil.getDisplayableBranchText(myRepository);
+ }
+
+ @NotNull
+ public String getBranch() {
+ return myRepository.getCurrentBranch();
+ }
+ }
+
+ private class HgBranchActionGroupPopup extends PopupFactoryImpl.ActionGroupPopup {
+ public HgBranchActionGroupPopup(@NotNull String title, @NotNull Project project,
+ @NotNull Condition preselectActionCondition) {
+ super(title, HgBranchPopup.this.createActions(), SimpleDataContext.getProjectContext(project), false, false, false, true, null, -1,
+ preselectActionCondition, null);
+ }
+
+ @Override
+ protected WizardPopup createPopup(WizardPopup parent, PopupStep step, Object parentValue) {
+ WizardPopup popup = super.createPopup(parent, step, parentValue);
+ RootAction rootAction = getRootAction(parentValue);
+ if (rootAction != null) {
+ popup.setAdText((rootAction).getCaption());
+ }
+ return popup;
+ }
+
+ @Nullable
+ private RootAction getRootAction(Object value) {
+ if (value instanceof PopupFactoryImpl.ActionItem) {
+ AnAction action = ((PopupFactoryImpl.ActionItem)value).getAction();
+ if (action instanceof RootAction) {
+ return (RootAction)action;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected ListCellRenderer getListElementRenderer() {
+ return new PopupListElementRenderer(this) {
+
+ private ErrorLabel myBranchLabel;
+
+ @Override
+ protected void customizeComponent(JList list, Object value, boolean isSelected) {
+ super.customizeComponent(list, value, isSelected);
+
+ RootAction rootAction = getRootAction(value);
+ if (rootAction != null) {
+ myBranchLabel.setVisible(true);
+ myBranchLabel.setText(String.format("[%s]", rootAction.getBranch()));
+
+ if (isSelected) {
+ setSelected(myBranchLabel);
+ }
+ else {
+ myBranchLabel.setBackground(getBackground());
+ myBranchLabel.setForeground(JBColor.GRAY); // different foreground than for other elements
+ }
+
+ adjustOpacity(myBranchLabel, isSelected);
+ }
+ else {
+ myBranchLabel.setVisible(false);
+ }
+ }
+
+ @Override
+ protected JComponent createItemComponent() {
+ myTextLabel = new ErrorLabel();
+ myTextLabel.setOpaque(true);
+ myTextLabel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
+
+ myBranchLabel = new ErrorLabel();
+ myBranchLabel.setOpaque(true);
+ myBranchLabel.setBorder(BorderFactory.createEmptyBorder(1, UIUtil.DEFAULT_HGAP, 1, 1));
+
+ JPanel compoundPanel = new OpaquePanel(new BorderLayout(), Color.white);
+ compoundPanel.add(myTextLabel, BorderLayout.CENTER);
+ compoundPanel.add(myBranchLabel, BorderLayout.EAST);
+
+ return layoutComponent(compoundPanel);
+ }
+ };
+ }
+ }
+}
+
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchPopupActions.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchPopupActions.java
new file mode 100644
index 000000000000..81eb20400cc8
--- /dev/null
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchPopupActions.java
@@ -0,0 +1,226 @@
+/*
+ * 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.zmlx.hg4idea.action;
+
+import com.intellij.openapi.actionSystem.ActionGroup;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.update.UpdatedFiles;
+import com.intellij.util.IconUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.zmlx.hg4idea.HgRevisionNumber;
+import org.zmlx.hg4idea.HgVcs;
+import org.zmlx.hg4idea.command.HgBranchCreateCommand;
+import org.zmlx.hg4idea.command.HgMergeCommand;
+import org.zmlx.hg4idea.command.HgUpdateCommand;
+import org.zmlx.hg4idea.execution.HgCommandException;
+import org.zmlx.hg4idea.execution.HgCommandResult;
+import org.zmlx.hg4idea.execution.HgCommandResultHandler;
+import org.zmlx.hg4idea.provider.update.HgConflictResolver;
+import org.zmlx.hg4idea.provider.update.HgHeadMerger;
+import org.zmlx.hg4idea.repo.HgRepository;
+import org.zmlx.hg4idea.util.HgErrorUtil;
+import org.zmlx.hg4idea.util.HgUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Nadya Zabrodina
+ */
+public class HgBranchPopupActions {
+
+ private final Project myProject;
+ private final HgRepository myRepository;
+
+ HgBranchPopupActions(Project project, HgRepository repository) {
+ myProject = project;
+ myRepository = repository;
+ }
+
+ ActionGroup createActions(@Nullable DefaultActionGroup toInsert) {
+ DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
+ popupGroup.addAction(new NewBranchAction(myProject, Collections.singletonList(myRepository)));
+
+ if (toInsert != null) {
+ popupGroup.addAll(toInsert);
+ }
+
+ popupGroup.addSeparator("Branches");
+ List branches = new ArrayList(myRepository.getBranches());
+ Collections.sort(branches);
+ for (String branch : branches) {
+ if (!branch.equals(myRepository.getCurrentBranch())) { // don't show current branch in the list
+ popupGroup.add(new BranchActions(myProject, branch, myRepository));
+ }
+ }
+ return popupGroup;
+ }
+
+ static class NewBranchAction extends DumbAwareAction {
+ private final Project myProject;
+ private final List myRepositories;
+
+ NewBranchAction(@NotNull Project project, @NotNull List repositories) {
+ super("New Branch", "Create and checkout new branch", IconUtil.getAddIcon());
+ myProject = project;
+ myRepositories = repositories;
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final String name = HgUtil.getNewBranchNameFromUser(myProject, "Create New Branch");
+
+ try {
+ new HgBranchCreateCommand(myProject, HgUtil.getRootForSelectedFile(myProject), name).execute(new HgCommandResultHandler() {
+ @Override
+ public void process(@Nullable HgCommandResult result) {
+ myProject.getMessageBus().syncPublisher(HgVcs.BRANCH_TOPIC).update(myProject, null);
+ if (HgErrorUtil.hasErrorsInCommandExecution(result)) {
+ new HgCommandResultNotifier(myProject)
+ .notifyError(result, "Creation failed", "Branch creation [" + name + "] failed");
+ }
+ }
+ });
+ }
+ catch (HgCommandException exception) {
+ HgAbstractGlobalAction.handleException(myProject, exception);
+ }
+ }
+
+ @Override
+ public void update(AnActionEvent e) {
+ if (anyRepositoryIsFresh()) {
+ e.getPresentation().setEnabled(false);
+ e.getPresentation().setDescription("Checkout of a new branch is not possible before the first commit.");
+ }
+ }
+
+ private boolean anyRepositoryIsFresh() {
+ for (HgRepository repository : myRepositories) {
+ if (repository.isFresh()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+
+ /**
+ * Actions available for branches.
+ */
+ static class BranchActions extends ActionGroup {
+
+ private final Project myProject;
+ private String myBranchName;
+ @NotNull private final HgRepository mySelectedRepository;
+
+ BranchActions(@NotNull Project project, @NotNull String branchName,
+ @NotNull HgRepository selectedRepository) {
+ super("", true);
+ myProject = project;
+ myBranchName = branchName;
+ mySelectedRepository = selectedRepository;
+ getTemplatePresentation().setText(calcBranchText(), false); // no mnemonics
+ }
+
+ @NotNull
+ private String calcBranchText() {
+ return myBranchName;
+ }
+
+ @NotNull
+ @Override
+ public AnAction[] getChildren(@Nullable AnActionEvent e) {
+ return new AnAction[]{
+ new UpdateToAction(myProject, mySelectedRepository, myBranchName),
+ new MergeAction(myProject, mySelectedRepository, myBranchName)
+ };
+ }
+
+ private static class MergeAction extends DumbAwareAction {
+
+ private final Project myProject;
+ private final HgRepository mySelectedRepository;
+ private final String myBranchName;
+
+ public MergeAction(@NotNull Project project,
+ @NotNull HgRepository selectedRepository,
+ @NotNull String branchName) {
+ super("Merge");
+ myProject = project;
+ mySelectedRepository = selectedRepository;
+ myBranchName = branchName;
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ UpdatedFiles updatedFiles = UpdatedFiles.create();
+ HgMergeCommand hgMergeCommand = new HgMergeCommand(myProject, mySelectedRepository.getRoot());
+ hgMergeCommand.setBranch(myBranchName);
+ //hgMergeCommand.setRevision(myBranchName.getHead().getChangeset());
+ HgCommandResultNotifier notifier = new HgCommandResultNotifier(myProject);
+ try {
+ new HgHeadMerger(myProject, hgMergeCommand)
+ .merge(mySelectedRepository.getRoot(), updatedFiles, HgRevisionNumber.NULL_REVISION_NUMBER);
+ new HgConflictResolver(myProject, updatedFiles).resolve(mySelectedRepository.getRoot());
+ }
+ catch (VcsException exception) {
+ if (exception.isWarning()) {
+ notifier.notifyWarning("Warning during merge", exception.getMessage());
+ }
+ else {
+ notifier.notifyError(null, "Exception during merge", exception.getMessage());
+ }
+ }
+ }
+ }
+
+ private static class UpdateToAction extends DumbAwareAction {
+
+ private final Project myProject;
+ private final HgRepository mySelectedRepository;
+ private final String myBranch;
+
+ public UpdateToAction(@NotNull Project project,
+ @NotNull HgRepository selectedRepository,
+ @NotNull String branch) {
+ super("Update To");
+ myProject = project;
+ mySelectedRepository = selectedRepository;
+ myBranch = branch;
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ HgUpdateCommand hgUpdateCommand = new HgUpdateCommand(myProject, mySelectedRepository.getRoot());
+ hgUpdateCommand.setBranch(myBranch);
+ HgCommandResult result = hgUpdateCommand.execute();
+ if (HgErrorUtil.hasErrorsInCommandExecution(result)) {
+ new HgCommandResultNotifier(myProject).notifyError(result, "", "Update failed");
+ }
+ myProject.getMessageBus().syncPublisher(HgVcs.BRANCH_TOPIC).update(myProject, null);
+ }
+ }
+ }
+}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchesAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchesAction.java
new file mode 100644
index 000000000000..0debdaba692d
--- /dev/null
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchesAction.java
@@ -0,0 +1,50 @@
+/*
+ * 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.zmlx.hg4idea.action;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.Nullable;
+import org.zmlx.hg4idea.repo.HgRepository;
+import org.zmlx.hg4idea.repo.HgRepositoryImpl;
+import org.zmlx.hg4idea.util.HgUtil;
+
+import java.util.Collection;
+
+/**
+ * @author Nadya Zabrodina
+ */
+
+public class HgBranchesAction extends HgAbstractGlobalAction {
+
+ @Override
+ protected void execute(Project project, Collection repositories, @Nullable VirtualFile selectedRepo) {
+ HgRepository repository;
+ if (selectedRepo != null) {
+ repository = HgRepositoryImpl.getFullInstance(selectedRepo, project, project);
+ }
+ else {
+ VirtualFile selectedRoot = HgUtil.getRootForSelectedFile(project);
+ if (selectedRoot != null) {
+ repository = HgRepositoryImpl.getFullInstance(selectedRoot, project, project);
+ }
+ else {
+ return;
+ }
+ }
+ HgBranchPopup.getInstance(project, repository).asListPopup().showInFocusCenter();
+ }
+}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgBranchCreateCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgBranchCreateCommand.java
new file mode 100644
index 000000000000..759c0613ac29
--- /dev/null
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgBranchCreateCommand.java
@@ -0,0 +1,50 @@
+/*
+ * 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.zmlx.hg4idea.command;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.zmlx.hg4idea.execution.HgCommandException;
+import org.zmlx.hg4idea.execution.HgCommandExecutor;
+import org.zmlx.hg4idea.execution.HgCommandResultHandler;
+
+import java.util.Arrays;
+
+/**
+ * @author Nadya Zabrodina
+ */
+public class HgBranchCreateCommand {
+
+ private final Project project;
+ private final VirtualFile repo;
+ private final String branchName;
+
+ public HgBranchCreateCommand(@NotNull Project project, @NotNull VirtualFile repo, @Nullable String branchName) {
+ this.project = project;
+ this.repo = repo;
+ this.branchName = branchName;
+ }
+
+ public void execute(@Nullable HgCommandResultHandler resultHandler) throws HgCommandException {
+ if (StringUtil.isEmptyOrSpaces(branchName)) {
+ throw new HgCommandException("branch name is empty");
+ }
+ new HgCommandExecutor(project).execute(repo, "branch", Arrays.asList(branchName), resultHandler);
+ }
+}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java
index 37f37917f6b3..357fa891201a 100644
--- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java
@@ -34,6 +34,7 @@ public class HgPushCommand {
private String myRevision;
private boolean myForce;
private HgTagBranch myBranch;
+ private boolean myIsNewBranch;
public HgPushCommand(Project project, @NotNull VirtualFile repo, String destination) {
myProject = project;
@@ -53,6 +54,10 @@ public class HgPushCommand {
myBranch = branch;
}
+ public void setIsNewBranch(boolean isNewBranch) {
+ myIsNewBranch = isNewBranch;
+ }
+
public void execute(final HgCommandResultHandler resultHandler) {
final List arguments = new LinkedList();
if (!StringUtil.isEmptyOrSpaces(myRevision)) {
@@ -60,8 +65,13 @@ public class HgPushCommand {
arguments.add(myRevision);
}
if (myBranch != null) {
- arguments.add("-b");
- arguments.add(myBranch.getName());
+ if (myIsNewBranch) {
+ arguments.add("--new-branch");
+ }
+ else {
+ arguments.add("-b");
+ arguments.add(myBranch.getName());
+ }
}
if (myForce) {
arguments.add("-f");
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepoStateException.java b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepoStateException.java
new file mode 100644
index 000000000000..6bcc816053bb
--- /dev/null
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepoStateException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.zmlx.hg4idea.repo;
+
+/**
+ * @author Nadya Zabrodina
+ */
+public class HgRepoStateException extends RuntimeException {
+
+ public HgRepoStateException(String message) {
+ super(message);
+ }
+
+ public HgRepoStateException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepository.java b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepository.java
new file mode 100644
index 000000000000..b7feda1d6246
--- /dev/null
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepository.java
@@ -0,0 +1,119 @@
+/*
+ * 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.zmlx.hg4idea.repo;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+
+/**
+ *
+ * HgRepository is a representation of a Hg repository stored under the specified directory.
+ * It stores the information about the repository, which is frequently requested by other plugin components.
+ * All get-methods (like {@link #getCurrentRevision()}) are just getters of the correspondent fields and thus are very fast.
+ *
+ *
+ * The HgRepository is updated "externally" by the {@link org.zmlx.hg4idea.repo.HgRepositoryUpdater}, when correspondent {@code .hg/} service files
+ * change.
+ *
+ * Other components may subscribe to HgRepository changes via the STATUS_TOPIC {@link com.intellij.util.messages.Topic}
+ *
+ *
+ * @author Nadya Zabrodina
+ */
+
+public interface HgRepository {
+
+ String DEFAULT_BRANCH = "default";
+
+ /**
+ * Current state of the repository.
+ */
+
+ enum State {
+ /**
+ * HEAD is on branch, no merge process is in progress.
+ */
+
+ NORMAL,
+ /**
+ * During merge (for instance, merge failed with conflicts that weren't immediately resolved).
+ */
+
+ MERGING {
+ @Override
+ public String toString() {
+ return "Merging";
+ }
+ },
+
+ }
+
+ @NotNull
+ VirtualFile getRoot();
+
+ @NotNull
+ VirtualFile getHgDir();
+
+ @NotNull
+ String getPresentableUrl();
+
+ @NotNull
+ Project getProject();
+
+
+ @NotNull
+ State getState();
+
+
+ /**
+ * Returns the hash of the revision, which HEAD currently points to.
+ * Returns null only in the case of a fresh repository, when no commit have been made.
+ */
+
+ @Nullable
+ String getCurrentRevision();
+
+ /**
+ * Returns the current branch of this Hg repository.
+ * If the repository is being rebased, then the current branch is the branch being rebased (which was current before the rebase
+ * operation has started).
+ * Returns null, if the repository is not on a branch and not in the REBASING state.
+ */
+
+ @NotNull
+ String getCurrentBranch();
+
+ @NotNull
+ Collection getBranches();
+
+ /**
+ * @return true if current repository is "fresh", i.e. if no commits have been made yet.
+ */
+
+ boolean isFresh();
+
+ /**
+ * Updates the HgRepository by reading information from .hg dir...
+ */
+
+ void update();
+}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryFiles.java b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryFiles.java
new file mode 100644
index 000000000000..2ccdbd016d42
--- /dev/null
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryFiles.java
@@ -0,0 +1,82 @@
+/*
+ * 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.zmlx.hg4idea.repo;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+
+/**
+ * Stores paths to Hg service files (from .hg/ directory) that are used by IDEA, and provides methods to check if a file
+ * matches once of them.
+ *
+ * @author Nadya Zabrodina
+ */
+public class HgRepositoryFiles {
+
+ public static final String BRANCHHEADS = "cache/branchheads";
+ public static final String MERGE = "merge";
+ public static final String BRANCH = "branch";
+
+
+ private final String myBranchHeadsPath;
+ private final String myMergePath;
+ private final String myBranchPath;
+
+
+ public static HgRepositoryFiles getInstance(@NotNull VirtualFile hgDir) {
+ return new HgRepositoryFiles(hgDir);
+ }
+
+ private HgRepositoryFiles(@NotNull VirtualFile hgDir) {
+ myBranchHeadsPath = hgDir.getPath() + slash(BRANCHHEADS);
+ myBranchPath = hgDir.getPath() + slash(BRANCH);
+ myMergePath = hgDir.getPath() + slash(MERGE);
+ }
+
+ @NotNull
+ private static String slash(@NotNull String s) {
+ return "/" + s;
+ }
+
+ /**
+ * Returns subdirectories of .hg which we are interested in - they should be watched by VFS.
+ */
+ @NotNull
+ static Collection getSubDirRelativePaths() {
+ return Arrays.asList(slash(BRANCHHEADS), slash(MERGE));
+ }
+
+ public String getBranchHeadsPath() {
+ return myBranchHeadsPath;
+ }
+
+
+ public boolean isbranchHeadsFile(String filePath) {
+ return filePath.equals(myBranchHeadsPath);
+ }
+
+ public boolean isBranchFile(String filePath) {
+ return filePath.equals(myBranchPath);
+ }
+
+ public boolean isMergeFile(String filePath) {
+ return filePath.startsWith(myMergePath);
+ }
+}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryImpl.java b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryImpl.java
new file mode 100644
index 000000000000..75b8c3fe4345
--- /dev/null
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryImpl.java
@@ -0,0 +1,215 @@
+/*
+ * 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.zmlx.hg4idea.repo;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Consumer;
+import com.intellij.util.concurrency.QueueProcessor;
+import com.intellij.util.messages.MessageBus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.zmlx.hg4idea.HgVcs;
+import org.zmlx.hg4idea.util.HgUtil;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * @author Nadya Zabrodina
+ */
+
+public class HgRepositoryImpl implements HgRepository, Disposable {
+
+ private static final Object STUB_OBJECT = new Object();
+
+ private final Project myProject;
+ private final VirtualFile myRootDir;
+ private final HgRepositoryReader myReader;
+ private final VirtualFile myHgDir;
+ private final QueueProcessor