diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java index 779260b26f0d..73334983a268 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java @@ -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 changes, final LocalChangeList initialSelection, - final List executors, final boolean showVcsCommit, final String comment, - @Nullable CommitResultHandler customResultHandler, boolean cancelIfNoChanges) { + public static boolean commitChanges(final Project project, + final List changes, + final LocalChangeList initialSelection, + final List 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 changes, + final LocalChangeList initialSelection, + final List 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); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitHelper.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitHelper.java index 3f0747e80b30..b64d5ac1997c 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitHelper.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitHelper.java @@ -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); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgVcs.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgVcs.java index 02655cffa3ae..bce14e8215a0 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgVcs.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgVcs.java @@ -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 { 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 { 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 { return Collections.singletonList(myCommitAndPushExecutor); } + @NotNull + public HgCloseBranchExecutor getCloseBranchExecutor() { + return myCloseBranchExecutor; + } + public static VcsKey getKey() { return ourKey; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java index 3dcaf6dee161..dc73c36ee503 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java @@ -81,6 +81,7 @@ public class HgBranchPopup extends DvcsBranchPopup { List 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()); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java index 633fe1cf87d9..9de49d560b65 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java @@ -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 myRepositories; + @NotNull final HgRepository myPreselectedRepo; + + HgCloseBranchAction(@NotNull List 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 changesForRepositories = ContainerUtil.filter(activeChangeList.getChanges(), new Condition() { + @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.singletonList(closeBranchExecutor), + false, vcs, "Close Branch", null, false); + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabledAndVisible(ContainerUtil.and(myRepositories, new Condition() { + @Override + public boolean value(HgRepository repository) { + return repository.getOpenedBranches().contains(repository.getCurrentBranch()); + } + })); + } + } + public static class HgNewBookmarkAction extends DumbAwareAction { @NotNull protected final List myRepositories; @NotNull final HgRepository myPreselectedRepo; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java index 794c7096d207..4ba12ee4add0 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java @@ -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 myFiles = Collections.emptySet(); @NotNull private List 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.emptyList(), myAmend); + commitChunkFiles(Collections.emptyList(), myAmend, myCloseBranch); } else { List relativePaths = ContainerUtil.map2List(myFiles, new Function() { @@ -94,12 +102,12 @@ public class HgCommitCommand { List> 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 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 chunk, boolean amendCommit) throws VcsException { - commitChunkFiles(chunk, amendCommit, false); + private void commitChunkFiles(@NotNull List chunk, boolean amendCommit, boolean closeBranch) throws VcsException { + commitChunkFiles(chunk, amendCommit, false, closeBranch); } - private void commitChunkFiles(@NotNull List chunk, boolean amendCommit, boolean withSubrepos) throws VcsException { + private void commitChunkFiles(@NotNull List chunk, boolean amendCommit, boolean withSubrepos, boolean closeBranch) + throws VcsException { + HgRepository repository = HgUtil.getRepositoryForFile(myProject, myRoot); + assert repository != null; List parameters = new LinkedList(); 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); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java index cd46452f96f1..fe13294ef8ab 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java @@ -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 myRepos; public HgCheckinEnvironment(Project project) { myProject = project; @@ -67,8 +69,15 @@ public class HgCheckinEnvironment implements CheckinEnvironment { public RefreshableOnComponent createAdditionalOptionsPanel(CheckinProjectPanel panel, PairConsumer 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 commit(List changes, String preparedComment, @NotNull NullableFunction parametersHolder, Set feedback) { List exceptions = new LinkedList(); Map> repositoriesMap = getFilesByRepository(changes); + addRepositoriesWithoutChanges(repositoriesMap); for (Map.Entry> entry : repositoriesMap.entrySet()) { HgRepository repo = entry.getKey(); Set 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> 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 repos) { + myRepos = repos; + } + + private void addRepositoriesWithoutChanges(@NotNull Map> repositoryMap) { + if (myRepos == null) return; + for (HgRepository repository : myRepos) { + if (!repositoryMap.keySet().contains(repository)) { + repositoryMap.put(repository, Collections.emptySet()); + } + } + } + /** * Commit options for hg */ diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCloseBranchExecutor.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCloseBranchExecutor.java new file mode 100644 index 000000000000..3aac41d2b394 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCloseBranchExecutor.java @@ -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 repositories) { + myCheckinEnvironment.setRepos(repositories); + } +}