IDEA-134617 hg close branch from branch widget implemented

* ability to close branch for selected repo added;
 * if repositories tracked synchronously , then add an ability to close all;
 * only related changes (for appropriate repositories) will be preselected to perform close commit
This commit is contained in:
Nadya Zabrodina
2014-12-22 20:03:29 +03:00
parent 3dc10f4b9d
commit ae92a76c28
8 changed files with 226 additions and 21 deletions
@@ -142,9 +142,27 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj
private final MyUpdateButtonsRunnable myUpdateButtonsRunnable = new MyUpdateButtonsRunnable(this);
public static boolean commitChanges(final Project project, final List<Change> changes, final LocalChangeList initialSelection,
final List<CommitExecutor> executors, final boolean showVcsCommit, final String comment,
@Nullable CommitResultHandler customResultHandler, boolean cancelIfNoChanges) {
public static boolean commitChanges(final Project project,
final List<Change> changes,
final LocalChangeList initialSelection,
final List<CommitExecutor> executors,
final boolean showVcsCommit,
final String comment,
@Nullable CommitResultHandler customResultHandler,
boolean cancelIfNoChanges) {
return commitChanges(project, changes, initialSelection, executors, showVcsCommit, null, comment, customResultHandler,
cancelIfNoChanges);
}
public static boolean commitChanges(final Project project,
final List<Change> changes,
final LocalChangeList initialSelection,
final List<CommitExecutor> executors,
final boolean showVcsCommit,
@Nullable final AbstractVcs singleVcs,
final String comment,
@Nullable CommitResultHandler customResultHandler,
boolean cancelIfNoChanges) {
if (cancelIfNoChanges && changes.isEmpty() && !ApplicationManager.getApplication().isUnitTestMode()) {
Messages.showInfoMessage(project, VcsBundle.message("commit.dialog.no.changes.detected.text"),
VcsBundle.message("commit.dialog.no.changes.detected.title"));
@@ -159,8 +177,10 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj
}
final ChangeListManager manager = ChangeListManager.getInstance(project);
CommitChangeListDialog dialog = new CommitChangeListDialog(project, changes, initialSelection, executors, showVcsCommit, manager.getDefaultChangeList(), manager.getChangeListsCopy(), null,
false, comment, customResultHandler);
CommitChangeListDialog dialog =
new CommitChangeListDialog(project, changes, initialSelection, executors, showVcsCommit, manager.getDefaultChangeList(),
manager.getChangeListsCopy(), singleVcs,
false, comment, customResultHandler);
if (!ApplicationManager.getApplication().isUnitTestMode()) {
dialog.show();
}
@@ -990,7 +1010,7 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj
if (myIsAlien) {
helper.doAlienCommit(myVcs);
} else {
helper.doCommit();
helper.doCommit(myVcs);
}
}
@@ -101,7 +101,11 @@ public class CommitHelper {
}
public boolean doCommit() {
return doCommit(new CommitProcessor());
return doCommit((AbstractVcs)null);
}
public boolean doCommit(@Nullable AbstractVcs vcs) {
return doCommit(new CommitProcessor(vcs));
}
public boolean doAlienCommit(final AbstractVcs vcs) {
@@ -374,8 +378,10 @@ public class CommitHelper {
private LocalHistoryAction myAction;
private ChangeListsModificationAfterCommit myAfterVcsRefreshModification;
private boolean myCommitSuccess;
@Nullable private final AbstractVcs myVcs;
private CommitProcessor() {
private CommitProcessor(@Nullable AbstractVcs vcs) {
myVcs = vcs;
myAfterVcsRefreshModification = ChangeListsModificationAfterCommit.NOTHING;
if (myChangeList instanceof LocalChangeList) {
final LocalChangeList localList = (LocalChangeList) myChangeList;
@@ -391,6 +397,9 @@ public class CommitHelper {
}
public void callSelf() {
if (myVcs != null && myIncludedChanges.isEmpty()) {
process(myVcs, myIncludedChanges);
}
ChangesUtil.processChangesByVcs(myProject, myIncludedChanges, this);
}
@@ -53,6 +53,7 @@ import org.jetbrains.annotations.Nullable;
import org.zmlx.hg4idea.provider.*;
import org.zmlx.hg4idea.provider.annotate.HgAnnotationProvider;
import org.zmlx.hg4idea.provider.commit.HgCheckinEnvironment;
import org.zmlx.hg4idea.provider.commit.HgCloseBranchExecutor;
import org.zmlx.hg4idea.provider.commit.HgCommitAndPushExecutor;
import org.zmlx.hg4idea.provider.update.HgUpdateEnvironment;
import org.zmlx.hg4idea.roots.HgIntegrationEnabler;
@@ -104,7 +105,8 @@ public class HgVcs extends AbstractVcs<CommittedChangeList> {
private HgExecutableValidator myExecutableValidator;
private final Object myExecutableValidatorLock = new Object();
private File myPromptHooksExtensionFile;
private CommitExecutor myCommitAndPushExecutor;
private final CommitExecutor myCommitAndPushExecutor;
private final HgCloseBranchExecutor myCloseBranchExecutor;
private HgRemoteStatusUpdater myHgRemoteStatusUpdater;
private HgStatusWidget myStatusWidget;
@@ -130,6 +132,7 @@ public class HgVcs extends AbstractVcs<CommittedChangeList> {
committedChangesProvider = new HgCachingCommittedChangesProvider(project, this);
myMergeProvider = new HgMergeProvider(myProject);
myCommitAndPushExecutor = new HgCommitAndPushExecutor(checkinEnvironment);
myCloseBranchExecutor = new HgCloseBranchExecutor(checkinEnvironment);
}
public String getDisplayName() {
@@ -396,6 +399,11 @@ public class HgVcs extends AbstractVcs<CommittedChangeList> {
return Collections.singletonList(myCommitAndPushExecutor);
}
@NotNull
public HgCloseBranchExecutor getCloseBranchExecutor() {
return myCloseBranchExecutor;
}
public static VcsKey getKey() {
return ourKey;
}
@@ -81,6 +81,7 @@ public class HgBranchPopup extends DvcsBranchPopup<HgRepository> {
List<HgRepository> allRepositories = repositoryManager.getRepositories();
popupGroup.add(new HgBranchPopupActions.HgNewBranchAction(myProject, allRepositories, myCurrentRepository));
popupGroup.addAction(new HgBranchPopupActions.HgNewBookmarkAction(allRepositories, myCurrentRepository));
popupGroup.addAction(new HgBranchPopupActions.HgCloseBranchAction(allRepositories, myCurrentRepository));
popupGroup.addAction(new HgBranchPopupActions.HgShowUnnamedHeadsForCurrentBranchAction(myCurrentRepository));
popupGroup.addAll(createRepositoriesActions());
@@ -23,9 +23,15 @@ 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.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.PlatformIcons;
@@ -35,15 +41,19 @@ import com.intellij.vcs.log.impl.HashImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.zmlx.hg4idea.HgNameWithHashInfo;
import org.zmlx.hg4idea.HgVcs;
import org.zmlx.hg4idea.action.HgCommandResultNotifier;
import org.zmlx.hg4idea.command.HgBookmarkCommand;
import org.zmlx.hg4idea.command.HgBranchCreateCommand;
import org.zmlx.hg4idea.execution.HgCommandException;
import org.zmlx.hg4idea.execution.HgCommandResult;
import org.zmlx.hg4idea.execution.HgCommandResultHandler;
import org.zmlx.hg4idea.provider.commit.HgCloseBranchExecutor;
import org.zmlx.hg4idea.repo.HgRepository;
import org.zmlx.hg4idea.repo.HgRepositoryManager;
import org.zmlx.hg4idea.ui.HgBookmarkDialog;
import org.zmlx.hg4idea.util.HgErrorUtil;
import org.zmlx.hg4idea.util.HgUtil;
import java.util.*;
@@ -64,6 +74,7 @@ public class HgBranchPopupActions {
DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
popupGroup.addAction(new HgNewBranchAction(myProject, Collections.singletonList(myRepository), myRepository));
popupGroup.addAction(new HgNewBookmarkAction(Collections.singletonList(myRepository), myRepository));
popupGroup.addAction(new HgBranchPopupActions.HgCloseBranchAction(Collections.singletonList(myRepository), myRepository));
popupGroup.addAction(new HgShowUnnamedHeadsForCurrentBranchAction(myRepository));
if (toInsert != null) {
popupGroup.addAll(toInsert);
@@ -129,6 +140,60 @@ public class HgBranchPopupActions {
}
}
public static class HgCloseBranchAction extends DumbAwareAction {
@NotNull private final List<HgRepository> myRepositories;
@NotNull final HgRepository myPreselectedRepo;
HgCloseBranchAction(@NotNull List<HgRepository> repositories, @NotNull HgRepository preselectedRepo) {
super("Close " + StringUtil.pluralize("branch", repositories.size()),
"Close current " + StringUtil.pluralize("branch", repositories.size()), AllIcons.Actions.Delete);
myRepositories = repositories;
myPreselectedRepo = preselectedRepo;
}
@Override
public void actionPerformed(AnActionEvent e) {
final Project project = myPreselectedRepo.getProject();
ApplicationManager.getApplication().saveAll();
ChangeListManager.getInstance(project).invokeAfterUpdate(new Runnable() {
@Override
public void run() {
commitAndCloseBranch(project);
}
}, InvokeAfterUpdateMode.SYNCHRONOUS_CANCELLABLE, VcsBundle
.message("waiting.changelists.update.for.show.commit.dialog.message"),
ModalityState.current());
}
private void commitAndCloseBranch(@NotNull final Project project) {
final LocalChangeList activeChangeList = ChangeListManager.getInstance(project).getDefaultChangeList();
HgVcs vcs = HgVcs.getInstance(project);
assert vcs != null;
final HgRepositoryManager repositoryManager = HgUtil.getRepositoryManager(project);
List<Change> changesForRepositories = ContainerUtil.filter(activeChangeList.getChanges(), new Condition<Change>() {
@Override
public boolean value(Change change) {
return myRepositories.contains(repositoryManager.getRepositoryForFile(ChangesUtil.getFilePath(change)));
}
});
HgCloseBranchExecutor closeBranchExecutor = vcs.getCloseBranchExecutor();
closeBranchExecutor.setRepositories(myRepositories);
CommitChangeListDialog.commitChanges(project, changesForRepositories, activeChangeList,
Collections.<CommitExecutor>singletonList(closeBranchExecutor),
false, vcs, "Close Branch", null, false);
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabledAndVisible(ContainerUtil.and(myRepositories, new Condition<HgRepository>() {
@Override
public boolean value(HgRepository repository) {
return repository.getOpenedBranches().contains(repository.getCurrentBranch());
}
}));
}
}
public static class HgNewBookmarkAction extends DumbAwareAction {
@NotNull protected final List<HgRepository> myRepositories;
@NotNull final HgRepository myPreselectedRepo;
@@ -12,6 +12,7 @@
// limitations under the License.
package org.zmlx.hg4idea.command;
import com.intellij.dvcs.repo.Repository;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
@@ -28,6 +29,7 @@ import org.zmlx.hg4idea.HgVcs;
import org.zmlx.hg4idea.HgVcsMessages;
import org.zmlx.hg4idea.execution.HgCommandException;
import org.zmlx.hg4idea.execution.HgCommandExecutor;
import org.zmlx.hg4idea.repo.HgRepository;
import org.zmlx.hg4idea.repo.HgRepositoryManager;
import org.zmlx.hg4idea.util.HgEncodingUtil;
import org.zmlx.hg4idea.util.HgUtil;
@@ -51,16 +53,22 @@ public class HgCommitCommand {
private final String myMessage;
@NotNull private final Charset myCharset;
private final boolean myAmend;
private final boolean myCloseBranch;
private Set<HgFile> myFiles = Collections.emptySet();
@NotNull private List<String> mySubrepos = Collections.emptyList();
public HgCommitCommand(Project project, @NotNull VirtualFile root, String message, boolean amend) {
public HgCommitCommand(@NotNull Project project, @NotNull VirtualFile root, String message, boolean amend, boolean closeBranch) {
myProject = project;
myRoot = root;
myMessage = message;
myCharset = HgEncodingUtil.getDefaultCharset(myProject);
myAmend = amend;
myCloseBranch = closeBranch;
}
public HgCommitCommand(@NotNull Project project, @NotNull VirtualFile root, String message, boolean amend) {
this(project, root, message, amend, false);
}
public HgCommitCommand(Project project, @NotNull VirtualFile root, String message) {
@@ -80,9 +88,9 @@ public class HgCommitCommand {
throw new HgCommandException(HgVcsMessages.message("hg4idea.commit.error.messageEmpty"));
}
//if it's merge commit, so myFiles is Empty. Need to commit all files in changeList.
// see HgCheckinEnviroment->commit() method
// see HgCheckinEnvironment->commit() method
if (myFiles.isEmpty()) {
commitChunkFiles(Collections.<String>emptyList(), myAmend);
commitChunkFiles(Collections.<String>emptyList(), myAmend, myCloseBranch);
}
else {
List<String> relativePaths = ContainerUtil.map2List(myFiles, new Function<HgFile, String>() {
@@ -94,12 +102,12 @@ public class HgCommitCommand {
List<List<String>> chunkedCommits = VcsFileUtil.chunkRelativePaths(relativePaths);
int size = chunkedCommits.size();
// commit with subrepo should be first, because it's not possible to amend with --subrepos argument;
commitChunkFiles(chunkedCommits.get(0), myAmend, !mySubrepos.isEmpty());
commitChunkFiles(chunkedCommits.get(0), myAmend, !mySubrepos.isEmpty(), myCloseBranch && size == 1);
HgVcs vcs = HgVcs.getInstance(myProject);
boolean amendCommit = vcs != null && vcs.getVersion().isAmendSupported();
for (int i = 1; i < size; i++) {
List<String> chunk = chunkedCommits.get(i);
commitChunkFiles(chunk, amendCommit);
commitChunkFiles(chunk, amendCommit, false, myCloseBranch && i == size - 1);
}
}
if (!myProject.isDisposed()) {
@@ -111,11 +119,14 @@ public class HgCommitCommand {
messageBus.syncPublisher(HgVcs.BRANCH_TOPIC).update(myProject, null);
}
private void commitChunkFiles(@NotNull List<String> chunk, boolean amendCommit) throws VcsException {
commitChunkFiles(chunk, amendCommit, false);
private void commitChunkFiles(@NotNull List<String> chunk, boolean amendCommit, boolean closeBranch) throws VcsException {
commitChunkFiles(chunk, amendCommit, false, closeBranch);
}
private void commitChunkFiles(@NotNull List<String> chunk, boolean amendCommit, boolean withSubrepos) throws VcsException {
private void commitChunkFiles(@NotNull List<String> chunk, boolean amendCommit, boolean withSubrepos, boolean closeBranch)
throws VcsException {
HgRepository repository = HgUtil.getRepositoryForFile(myProject, myRoot);
assert repository != null;
List<String> parameters = new LinkedList<String>();
parameters.add("--logfile");
parameters.add(saveCommitMessage().getAbsolutePath());
@@ -127,6 +138,14 @@ public class HgCommitCommand {
else if (amendCommit) {
parameters.add("--amend");
}
if (closeBranch) {
if (chunk.isEmpty() && repository.getState() != Repository.State.MERGING) {
//if there are changed files but nothing selected -> need to exclude all; if merge commit then nothing excluded
parameters.add("-X");
parameters.add("\"**\"");
}
parameters.add("--close-branch");
}
parameters.addAll(chunk);
HgCommandExecutor executor = new HgCommandExecutor(myProject);
executor.setCharset(myCharset);
@@ -60,6 +60,8 @@ public class HgCheckinEnvironment implements CheckinEnvironment {
private boolean myNextCommitIsPushed;
private boolean myNextCommitAmend; // If true, the next commit is amended
private boolean myShouldCommitSubrepos;
private boolean myCloseBranch;
@Nullable private Collection<HgRepository> myRepos;
public HgCheckinEnvironment(Project project) {
myProject = project;
@@ -67,8 +69,15 @@ public class HgCheckinEnvironment implements CheckinEnvironment {
public RefreshableOnComponent createAdditionalOptionsPanel(CheckinProjectPanel panel,
PairConsumer<Object, Object> additionalDataConsumer) {
reset();
return new HgCommitAdditionalComponent(myProject, panel);
}
public void reset() {
myNextCommitIsPushed = false;
return new HgCommitAdditionalComponent(myProject,panel);
myShouldCommitSubrepos = false;
myCloseBranch = false;
myRepos = null;
}
public String getDefaultMessageFor(FilePath[] filesToCheckin) {
@@ -83,19 +92,19 @@ public class HgCheckinEnvironment implements CheckinEnvironment {
return HgVcsMessages.message("hg4idea.commit");
}
@SuppressWarnings({"ThrowableInstanceNeverThrown"})
public List<VcsException> commit(List<Change> changes,
String preparedComment,
@NotNull NullableFunction<Object, Object> parametersHolder,
Set<String> feedback) {
List<VcsException> exceptions = new LinkedList<VcsException>();
Map<HgRepository, Set<HgFile>> repositoriesMap = getFilesByRepository(changes);
addRepositoriesWithoutChanges(repositoriesMap);
for (Map.Entry<HgRepository, Set<HgFile>> entry : repositoriesMap.entrySet()) {
HgRepository repo = entry.getKey();
Set<HgFile> selectedFiles = entry.getValue();
HgCommitCommand command =
new HgCommitCommand(myProject, repo.getRoot(), preparedComment, myNextCommitAmend);
new HgCommitCommand(myProject, repo.getRoot(), preparedComment, myNextCommitAmend, myCloseBranch);
if (isMergeCommit(repo.getRoot())) {
//partial commits are not allowed during merges
@@ -255,7 +264,7 @@ public class HgCheckinEnvironment implements CheckinEnvironment {
private void addFile(Map<HgRepository, Set<HgFile>> result, ContentRevision contentRevision) {
FilePath filePath = contentRevision.getFile();
// try to find repository from hgFile from change
// try to find repository from hgFile from change: to be able commit sub repositories as expected
HgRepository repo = HgUtil.getRepositoryForFile(myProject, contentRevision instanceof HgCurrentBinaryContentRevision
? ((HgCurrentBinaryContentRevision)contentRevision).getRepositoryRoot()
: ChangesUtil.findValidParentAccurately(filePath));
@@ -276,6 +285,23 @@ public class HgCheckinEnvironment implements CheckinEnvironment {
myNextCommitIsPushed = true;
}
public void setCloseBranch(boolean closeBranch) {
myCloseBranch = closeBranch;
}
public void setRepos(@NotNull Collection<HgRepository> repos) {
myRepos = repos;
}
private void addRepositoriesWithoutChanges(@NotNull Map<HgRepository, Set<HgFile>> repositoryMap) {
if (myRepos == null) return;
for (HgRepository repository : myRepos) {
if (!repositoryMap.keySet().contains(repository)) {
repositoryMap.put(repository, Collections.<HgFile>emptySet());
}
}
}
/**
* Commit options for hg
*/
@@ -0,0 +1,57 @@
/*
* 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.zmlx.hg4idea.provider.commit;
import com.intellij.openapi.vcs.changes.CommitExecutorBase;
import com.intellij.openapi.vcs.changes.CommitSession;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.zmlx.hg4idea.repo.HgRepository;
import java.util.Collection;
public class HgCloseBranchExecutor extends CommitExecutorBase {
@NotNull private final HgCheckinEnvironment myCheckinEnvironment;
@NotNull private static final String CLOSE_BRANCH_TITLE = "Commit And &Close" ;
public HgCloseBranchExecutor(@NotNull HgCheckinEnvironment environment) {
myCheckinEnvironment = environment;
}
@Override
public boolean areChangesRequired() {
return false;
}
@Nls
@Override
public String getActionText() {
return CLOSE_BRANCH_TITLE;
}
@NotNull
@Override
public CommitSession createCommitSession() {
myCheckinEnvironment.setCloseBranch(true);
return CommitSession.VCS_COMMIT;
}
public void setRepositories(@NotNull Collection<HgRepository> repositories) {
myCheckinEnvironment.setRepos(repositories);
}
}