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)
This commit is contained in:
Nadya.Zabrodina
2013-05-21 20:20:49 +04:00
parent 5e8d29064f
commit 43bed3f1e7
19 changed files with 1459 additions and 19 deletions
@@ -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
+1
View File
@@ -55,6 +55,7 @@
<action id="hg4idea.merge.files" class="org.zmlx.hg4idea.action.HgMerge" icon="AllIcons.Vcs.MergeSourcesTree"/>
<separator/>
<action id="hg4idea.branches" class="org.zmlx.hg4idea.action.HgBranchesAction" text="_Branches..."/>
<action id="hg4idea.tag" class="org.zmlx.hg4idea.action.HgCreateTagAction"/>
<action id="hg4idea.updateTo" class="org.zmlx.hg4idea.action.HgUpdateToAction"/>
<action id="hg4idea.resolve.mark" class="org.zmlx.hg4idea.action.HgMarkResolved"/>
@@ -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;
}
@@ -62,7 +62,7 @@ abstract class HgAbstractGlobalAction extends AnAction {
protected abstract void execute(Project project, Collection<VirtualFile> 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());
}
@@ -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.*;
/**
* <p>
* The popup which allows to quickly switch and control Hg branches.
* </p>
* <p>
* Use {@link #asListPopup()} to achieve the {@link com.intellij.openapi.ui.popup.ListPopup} itself.
* </p>
*
* @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<AnAction> preselectActionCondition = new Condition<AnAction>() {
@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<AnAction> 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);
}
};
}
}
}
@@ -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<String> branches = new ArrayList<String>(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<HgRepository> myRepositories;
NewBranchAction(@NotNull Project project, @NotNull List<HgRepository> 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);
}
}
}
}
@@ -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<VirtualFile> 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();
}
}
@@ -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);
}
}
@@ -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<String> arguments = new LinkedList<String>();
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");
@@ -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);
}
}
@@ -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;
/**
* <p>
* 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.
* </p>
* <p>
* The HgRepository is updated "externally" by the {@link org.zmlx.hg4idea.repo.HgRepositoryUpdater}, when correspondent {@code .hg/} service files
* change.
* </p>
* Other components may subscribe to HgRepository changes via the STATUS_TOPIC {@link com.intellij.util.messages.Topic}
* </p>
*
* @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<String> 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();
}
@@ -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<String> 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);
}
}
@@ -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<Object> myNotifier;
@NotNull private volatile State myState = State.NORMAL;
@Nullable private volatile String myCurrentRevision = null;
@NotNull private volatile String myCurrentBranch = DEFAULT_BRANCH;
@NotNull private volatile List<String> myBranches = Collections.emptyList();
/*
* Get the HgRepository instance from the {@link HgRepositoryManager}.
* If you need to have an instance of HgRepository for a repository outside the project, use
* {@link #getLightInstance(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.project.Project, PlatformFacade, com.intellij.openapi.Disposable)}.
*/
protected HgRepositoryImpl(@NotNull VirtualFile rootDir, @NotNull Project project,
@NotNull Disposable parentDisposable) {
myRootDir = rootDir;
myProject = project;
Disposer.register(parentDisposable, this);
myHgDir = myRootDir.findChild(HgUtil.DOT_HG);
assert myHgDir != null : ".hg directory wasn't found under " + rootDir.getPresentableUrl();
myReader = new HgRepositoryReader(VfsUtilCore.virtualToIoFile(myHgDir));
MessageBus messageBus = project.getMessageBus();
myNotifier = new QueueProcessor<Object>(new NotificationConsumer(myProject, messageBus, this), myProject.getDisposed());
update();
}
public static HgRepository getFullInstance(@NotNull VirtualFile root, @NotNull Project project,
@NotNull Disposable parentDisposable) {
HgRepositoryImpl repository = new HgRepositoryImpl(root, project, parentDisposable);
repository.setupUpdater();
return repository;
}
private void setupUpdater() {
HgRepositoryUpdater updater = new HgRepositoryUpdater(this);
Disposer.register(this, updater);
}
@Override
public void dispose() {
}
@Override
@NotNull
public VirtualFile getRoot() {
return myRootDir;
}
@NotNull
@Override
public VirtualFile getHgDir() {
return myHgDir;
}
@Override
@NotNull
public String getPresentableUrl() {
return getRoot().getPresentableUrl();
}
@Override
@NotNull
public Project getProject() {
return myProject;
}
@Override
@NotNull
public State getState() {
return myState;
}
@Override
@Nullable
public String getCurrentRevision() {
return myCurrentRevision;
}
@Override
@NotNull
public String getCurrentBranch() {
return myCurrentBranch;
}
@Override
@NotNull
public List<String> getBranches() {
return myBranches;
}
@Override
public boolean isFresh() {
return !myReader.headExist();
}
@Override
public void update() {
readRepository();
notifyListeners();
}
private void readRepository() {
if (!isFresh()) {
myState = myReader.readState();
myCurrentRevision = myReader.readCurrentRevision();
myCurrentBranch = myReader.readCurrentBranch();
myBranches = myReader.readBranches();
}
}
protected void notifyListeners() {
myNotifier.add(STUB_OBJECT); // we don't have parameters for listeners
}
private static class NotificationConsumer implements Consumer<Object> {
private final Project myProject;
private final MessageBus myMessageBus;
private final HgRepository myRepository;
NotificationConsumer(Project project, MessageBus messageBus, HgRepository repository) {
myProject = project;
myMessageBus = messageBus;
myRepository = repository;
}
@Override
public void consume(Object o) {
if (!Disposer.isDisposed(myProject)) {
myMessageBus.syncPublisher(HgVcs.STATUS_TOPIC).update(myProject, myRepository.getRoot());
}
}
}
@Override
public String toString() {
return getPresentableUrl();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HgRepositoryImpl that = (HgRepositoryImpl)o;
if (!myProject.equals(that.myProject)) return false;
if (!myRootDir.equals(that.myRootDir)) return false;
return true;
}
@Override
public int hashCode() {
int result = myProject.hashCode();
result = 31 * result + (myRootDir.hashCode());
return result;
}
}
@@ -0,0 +1,165 @@
/*
* 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.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Reads information about the Hg repository from Hg service files located in the {@code .hg} folder.
* NB: works with {@link java.io.File}, i.e. reads from disk. Consider using caching.
* Throws a {@link HgRepoStateException} in the case of incorrect Hg file format.
*
* @author Nadya Zabrodina
*/
class HgRepositoryReader {
private static final Logger LOG = Logger.getInstance(HgRepositoryReader.class);
private static Pattern BRANCH_PATTERN = Pattern.compile("\\s*(.+)\\s+(.+)");
private static final int IO_RETRIES = 3; // number of retries before fail if an IOException happens during file read.
private final File myHgDir; // .hg/
private final File myBranchHeadsFile; // .hg/cache/branchheads - this file does not exist before first commit
private final File myCurrentBranch; // .hg/branch
HgRepositoryReader(@NotNull File hgDir) {
myHgDir = hgDir;
assertFileExists(myHgDir, ".hg directory not found in " + hgDir);
myBranchHeadsFile = new File(new File(myHgDir, "cache"), "branchheads");
myCurrentBranch = new File(myHgDir, "branch");
}
/**
* Finds current revision value.
*
* @return The current revision hash, or <b>{@code null}</b> if current revision is unknown - it is the initial repository state.
*/
@Nullable
String readCurrentRevision() {
String[] branchesWithHeads = tryLoadFile(myBranchHeadsFile).trim().split("\n");
String head = branchesWithHeads[0];
Matcher matcher = BRANCH_PATTERN.matcher(head);
if (matcher.matches()) {
return (matcher.group(1));
}
return null;
}
/**
* Return current branch
*/
@NotNull
String readCurrentBranch() {
if (branchExist()) {
String rev = tryLoadFile(myCurrentBranch);
return rev.trim();
}
return HgRepository.DEFAULT_BRANCH;
}
List<String> readBranches() {
List<String> branches = new ArrayList<String>();
String[] branchesWithHeads = tryLoadFile(myBranchHeadsFile).trim().split("\n");
// first one - is a head revision: head hash + head number;
for (int i = 1; i < branchesWithHeads.length; ++i) {
Matcher matcher = BRANCH_PATTERN.matcher(branchesWithHeads[i]);
if (matcher.matches()) {
branches.add(matcher.group(2));
}
}
return branches;
}
private static void assertFileExists(File file, String message) {
if (!file.exists()) {
throw new HgRepoStateException(message);
}
}
/**
* Loads the file content.
* Tries 3 times, then a {@link HgRepoStateException} is thrown.
*
* @param file File to read.
* @return file content.
*/
@NotNull
private static String tryLoadFile(final File file) {
return tryOrThrow(new Callable<String>() {
@Override
public String call() throws Exception {
return FileUtil.loadFile(file);
}
}, file);
}
/**
* Tries to execute the given action.
* If an IOException happens, tries again up to 3 times, and then throws a {@link HgRepoStateException}.
* If an other exception happens, rethrows it as a {@link HgRepoStateException}.
* In the case of success returns the result of the task execution.
*/
private static String tryOrThrow(Callable<String> actionToTry, File fileToLoad) {
IOException cause = null;
for (int i = 0; i < IO_RETRIES; i++) {
try {
return actionToTry.call();
}
catch (IOException e) {
LOG.info("IOException while loading " + fileToLoad, e);
cause = e;
}
catch (Exception e) { // this shouldn't happen since only IOExceptions are thrown in clients.
throw new HgRepoStateException("Couldn't load file " + fileToLoad, e);
}
}
throw new HgRepoStateException("Couldn't load file " + fileToLoad, cause);
}
public HgRepository.State readState() {
if (isMergeInProgress()) {
return HgRepository.State.MERGING;
}
return HgRepository.State.NORMAL;
}
private boolean isMergeInProgress() {
File mergeFile = new File(myHgDir, "merge");
return mergeFile.exists();
}
public boolean headExist() {
return myBranchHeadsFile.exists();
}
public boolean branchExist() {
return myCurrentBranch.exists();
}
}
@@ -0,0 +1,146 @@
/*
* 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.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.util.Consumer;
import com.intellij.util.Processor;
import com.intellij.util.concurrency.QueueProcessor;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Listens to .hg service files changes and updates {@link HgRepository} when needed.
*
* @author Nadya Zabrodina
*/
final class HgRepositoryUpdater implements Disposable, BulkFileListener {
private final HgRepositoryFiles myRepositoryFiles;
private final MessageBusConnection myMessageBusConnection;
private final QueueProcessor<Object> myUpdateQueue;
private final Object DUMMY_UPDATE_OBJECT = new Object();
private final VirtualFile myBranchHeadsDir;
private final LocalFileSystem.WatchRequest myWatchRequest;
HgRepositoryUpdater(HgRepository repository) {
VirtualFile hgDir = repository.getHgDir();
myWatchRequest = LocalFileSystem.getInstance().addRootToWatch(hgDir.getPath(), true);
myRepositoryFiles = HgRepositoryFiles.getInstance(hgDir);
visitHgDirVfs(hgDir);
myBranchHeadsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getBranchHeadsPath());
Project project = repository.getProject();
myUpdateQueue = new QueueProcessor<Object>(new Updater(repository), project.getDisposed());
if (!project.isDisposed()) {
myMessageBusConnection = project.getMessageBus().connect();
myMessageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, this);
}
else {
myMessageBusConnection = null;
}
}
private static void visitHgDirVfs(@NotNull VirtualFile hgDir) {
hgDir.getChildren();
for (String subdir : HgRepositoryFiles.getSubDirRelativePaths()) {
VirtualFile dir = hgDir.findFileByRelativePath(subdir);
// process recursively, because we need to visit all branches under cache/branchheads
visitAllChildrenRecursively(dir);
}
}
private static void visitAllChildrenRecursively(@Nullable VirtualFile dir) {
if (dir == null) {
return;
}
VfsUtil.processFilesRecursively(dir, new Processor<VirtualFile>() {
@Override
public boolean process(VirtualFile virtualFile) {
return true;
}
});
}
@Override
public void dispose() {
if (myWatchRequest != null) {
LocalFileSystem.getInstance().removeWatchedRoot(myWatchRequest);
}
if (myMessageBusConnection != null) {
myMessageBusConnection.disconnect();
}
}
@Override
public void before(@NotNull List<? extends VFileEvent> events) {
// everything is handled in #after()
}
@Override
public void after(@NotNull List<? extends VFileEvent> events) {
// which files in .hg were changed
boolean branchHeadsChanged = false;
boolean branchFileChanged = false;
boolean mergeFileChanged = false;
for (VFileEvent event : events) {
String filePath = event.getPath();
if (filePath == null) {
continue;
}
if (myRepositoryFiles.isbranchHeadsFile(filePath)) {
branchHeadsChanged = true;
}
else if (myRepositoryFiles.isBranchFile(filePath)) {
branchFileChanged = true;
visitAllChildrenRecursively(myBranchHeadsDir);
}
else if (myRepositoryFiles.isMergeFile(filePath)) {
mergeFileChanged = true;
}
}
if (branchHeadsChanged || branchFileChanged || mergeFileChanged) {
myUpdateQueue.add(DUMMY_UPDATE_OBJECT);
}
}
private static class Updater implements Consumer<Object> {
private final HgRepository myRepository;
public Updater(HgRepository repository) {
myRepository = repository;
}
@Override
public void consume(Object dummy) {
myRepository.update();
}
}
}
@@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.StatusBarWidget;
@@ -31,22 +32,25 @@ import org.jetbrains.annotations.Nullable;
import org.zmlx.hg4idea.HgProjectSettings;
import org.zmlx.hg4idea.HgUpdater;
import org.zmlx.hg4idea.HgVcs;
import org.zmlx.hg4idea.action.HgBranchPopup;
import org.zmlx.hg4idea.repo.HgRepositoryImpl;
import org.zmlx.hg4idea.status.HgCurrentBranchStatus;
import org.zmlx.hg4idea.util.HgUtil;
import java.awt.*;
import java.awt.event.MouseEvent;
/**
* Widget to display basic hg status in the IJ status bar.
*/
public class HgStatusWidget extends EditorBasedWidget implements StatusBarWidget.TextPresentation, StatusBarWidget.Multiframe, HgUpdater {
public class HgStatusWidget extends EditorBasedWidget
implements StatusBarWidget.MultipleTextValuesPresentation,
StatusBarWidget.Multiframe, HgUpdater {
private static final String MAX_STRING = "hg: default branch (128)";
@NotNull private final HgVcs myVcs;
@NotNull private final HgProjectSettings myProjectSettings;
@NotNull private final HgCurrentBranchStatus myCurrentBranchStatus;
private MessageBusConnection myBusConnection;
private volatile String myText = "";
private volatile String myTooltip = "";
@@ -91,13 +95,20 @@ public class HgStatusWidget extends EditorBasedWidget implements StatusBarWidget
}
@Override
public String getTooltipText() {
return myTooltip;
public ListPopup getPopupStep() {
Project project = getProject();
if (project == null) {
return null;
}
VirtualFile root = HgUtil.getRootForSelectedFile(project);
if (root != null) {
return HgBranchPopup.getInstance(project, HgRepositoryImpl.getFullInstance(root, project, project)).asListPopup();
}
return null;
}
@NotNull
@Override
public String getText() {
public String getSelectedValue() {
final String text = myText;
return StringUtil.isEmpty(text) ? "" : "hg: " + text;
}
@@ -105,15 +116,17 @@ public class HgStatusWidget extends EditorBasedWidget implements StatusBarWidget
@NotNull
@Override
@Deprecated
public String getMaxPossibleText() {
public String getMaxValue() {
return MAX_STRING;
}
@Override
public float getAlignment() {
return Component.LEFT_ALIGNMENT;
public String getTooltipText() {
return myTooltip;
}
@Override
// Updates branch information on click
public Consumer<MouseEvent> getClickConsumer() {
@@ -161,8 +174,8 @@ public class HgStatusWidget extends EditorBasedWidget implements StatusBarWidget
return;
}
myBusConnection = project.getMessageBus().connect();
myBusConnection.subscribe(HgVcs.STATUS_TOPIC, this);
MessageBusConnection busConnection = project.getMessageBus().connect();
busConnection.subscribe(HgVcs.STATUS_TOPIC, this);
DvcsUtil.installStatusBarWidget(myProject, this);
}
@@ -21,7 +21,7 @@
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<grid id="3b42" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="3b42" layout-manager="GridLayoutManager" row-count="4" 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="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
@@ -55,12 +55,12 @@
</component>
<hspacer id="27a66">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<component id="5fcc4" class="javax.swing.JCheckBox" binding="forceCheckBox" default-binding="true">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="org/zmlx/hg4idea/HgVcsMessages" key="hg4idea.push.force"/>
@@ -80,6 +80,20 @@
</constraints>
<properties/>
</component>
<component id="e76b" class="javax.swing.JCheckBox" binding="newBranchCheckBox">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<enabled value="false"/>
<text resource-bundle="org/zmlx/hg4idea/HgVcsMessages" key="hg4idea.push.asNewBranch"/>
</properties>
</component>
<hspacer id="af930">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
</children>
</grid>
<nested-form id="9e17d" form-file="org/zmlx/hg4idea/ui/HgRepositorySelectorComponent.form" binding="hgRepositorySelectorComponent">
@@ -51,6 +51,7 @@ public class HgPushDialog extends DialogWrapper {
private JCheckBox branchCheckBox;
private JComboBox branchComboBox;
private EditorComboBox myRepositoryURL;
private JCheckBox newBranchCheckBox;
private String myCurrentRepositoryUrl;
public HgPushDialog(Project project, Collection<VirtualFile> repos, List<HgTagBranch> branches, @Nullable VirtualFile selectedRepo) {
@@ -114,6 +115,10 @@ public class HgPushDialog extends DialogWrapper {
return forceCheckBox.isSelected();
}
public boolean isNewBranch() {
return newBranchCheckBox.isSelected();
}
protected JComponent createCenterPanel() {
return contentPanel;
}
@@ -159,6 +164,7 @@ public class HgPushDialog extends DialogWrapper {
setOKActionEnabled(validateOptions());
revisionTxt.setEnabled(revisionCbx.isSelected());
branchComboBox.setEnabled(branchCheckBox.isSelected());
newBranchCheckBox.setEnabled(branchCheckBox.isSelected());
}
private boolean validateOptions() {
@@ -14,7 +14,14 @@ package org.zmlx.hg4idea.util;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.ShutDownTracker;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.*;
@@ -24,6 +31,9 @@ import com.intellij.openapi.vcs.history.VcsFileRevisionEx;
import com.intellij.openapi.vcs.vfs.VcsVirtualFile;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.status.StatusBarUtil;
import com.intellij.ui.GuiUtils;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
@@ -33,6 +43,7 @@ import org.zmlx.hg4idea.command.HgRemoveCommand;
import org.zmlx.hg4idea.command.HgStatusCommand;
import org.zmlx.hg4idea.command.HgWorkingCopyRevisionsCommand;
import org.zmlx.hg4idea.provider.HgChangeProvider;
import org.zmlx.hg4idea.repo.HgRepository;
import java.awt.*;
import java.io.*;
@@ -50,6 +61,7 @@ public abstract class HgUtil {
public static final Pattern URL_WITH_PASSWORD = Pattern.compile("(?:.+)://(?:.+)(:.+)@(?:.+)"); //http(s)://username:password@url
public static final int MANY_FILES = 100;
private static final Logger LOG = Logger.getInstance(HgUtil.class);
public static final String DOT_HG = ".hg";
public static File copyResourceToTempFile(String basename, String extension) throws IOException {
final InputStream in = HgUtil.class.getClassLoader().getResourceAsStream("python/" + basename + extension);
@@ -203,7 +215,7 @@ public abstract class HgUtil {
* Checks if the given directory is an hg root.
*/
public static boolean isHgRoot(VirtualFile dir) {
return dir.findChild(".hg") != null;
return dir.findChild(DOT_HG) != null;
}
/**
@@ -249,6 +261,52 @@ public abstract class HgUtil {
return getHgRootOrThrow(project, VcsUtil.getFilePath(file.getPath()));
}
/**
* Returns the currently selected file, based on which HgBranch components will identify the current repository root.
*/
@Nullable
public static VirtualFile getSelectedFile(@NotNull Project project) {
StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);
final FileEditor fileEditor = StatusBarUtil.getCurrentFileEditor(project, statusBar);
VirtualFile result = null;
if (fileEditor != null) {
if (fileEditor instanceof TextEditor) {
Document document = ((TextEditor)fileEditor).getEditor().getDocument();
result = FileDocumentManager.getInstance().getFile(document);
}
}
if (result == null) {
final FileEditorManager manager = FileEditorManager.getInstance(project);
if (manager != null) {
Editor editor = manager.getSelectedTextEditor();
if (editor != null) {
result = FileDocumentManager.getInstance().getFile(editor.getDocument());
}
}
}
return result;
}
@Nullable
public static VirtualFile getRootForSelectedFile(@NotNull Project project) {
VirtualFile selectedFile = getSelectedFile(project);
if (selectedFile != null) {
return getHgRootOrNull(project, selectedFile);
}
return null;
}
/**
* Shows a message dialog to enter the name of new branch.
* @return name of new branch or {@code null} if user has cancelled the dialog.
*/
@Nullable
public static String getNewBranchNameFromUser(@NotNull Project project,
@NotNull String dialogTitle) {
return Messages.showInputDialog(project, "Enter the name of new branch:", dialogTitle, Messages.getQuestionIcon());
}
/**
* Checks is a merge operation is in progress on the given repository.
* Actually gets the number of parents of the current revision. If there are 2 parents, then a merge is going on. Otherwise there is
@@ -481,4 +539,13 @@ public abstract class HgUtil {
}
return path;
}
public static String getDisplayableBranchText(HgRepository repository) {
HgRepository.State state = repository.getState();
String branchText = "";
if (state == HgRepository.State.MERGING) {
branchText += state.toString() + " ";
}
return branchText + repository.getCurrentBranch();
}
}