diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index fc95f022af66..df65fd215cf1 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -235,5 +235,7 @@ + diff --git a/plugins/git4idea/src/git4idea/branch/GitDeleteRemoteBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitDeleteRemoteBranchOperation.java index d2d1ab5d3729..6fb2c7df5b85 100644 --- a/plugins/git4idea/src/git4idea/branch/GitDeleteRemoteBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitDeleteRemoteBranchOperation.java @@ -24,6 +24,7 @@ import com.intellij.openapi.vcs.VcsNotifier; import git4idea.commands.Git; import git4idea.commands.GitCommandResult; import git4idea.commands.GitCompoundResult; +import git4idea.push.GitPushParamsImpl; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.ui.branch.GitMultiRootBranchConfig; @@ -162,7 +163,7 @@ class GitDeleteRemoteBranchOperation extends GitBranchOperation { @NotNull private GitCommandResult pushDeletion(@NotNull GitRepository repository, @NotNull GitRemote remote, @NotNull String branchName) { - return myGit.push(repository, remote, ":" + branchName, false, false, false, null); + return myGit.push(repository, new GitPushParamsImpl(remote, ":" + branchName, false, false, false, null)); } @Nullable diff --git a/plugins/git4idea/src/git4idea/commands/Git.java b/plugins/git4idea/src/git4idea/commands/Git.java index cdd988c3fe79..3fdf65c8fcfd 100644 --- a/plugins/git4idea/src/git4idea/commands/Git.java +++ b/plugins/git4idea/src/git4idea/commands/Git.java @@ -21,6 +21,7 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import git4idea.branch.GitRebaseParams; +import git4idea.push.GitPushParams; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.reset.GitResetMode; @@ -144,12 +145,7 @@ public interface Git { @NotNull GitCommandResult push(@NotNull GitRepository repository, - @NotNull GitRemote remote, - @NotNull String spec, - boolean force, - boolean updateTracking, - boolean skipHook, - @Nullable String tagMode, + @NotNull GitPushParams pushParams, GitLineHandlerListener... listeners); @NotNull diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index 7f106ffed30c..5a69903e3b89 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -27,6 +27,7 @@ import com.intellij.vcsUtil.VcsFileUtil; import git4idea.GitVcs; import git4idea.branch.GitRebaseParams; import git4idea.config.GitVersionSpecialty; +import git4idea.push.GitPushParams; import git4idea.rebase.GitInteractiveRebaseEditorHandler; import git4idea.rebase.GitRebaseEditorHandler; import git4idea.rebase.GitRebaseEditorService; @@ -38,8 +39,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.io.OutputStreamWriter; -import java.nio.file.Paths; import java.util.*; import static git4idea.GitUtil.COMMENT_CHAR; @@ -399,14 +398,10 @@ public class GitImpl extends GitImplBase { @Override @NotNull public GitCommandResult push(@NotNull GitRepository repository, - @NotNull GitRemote remote, - @NotNull String spec, - boolean force, - boolean updateTracking, - boolean skipHook, - @Nullable String tagMode, + @NotNull GitPushParams pushParams, GitLineHandlerListener... listeners) { - return doPush(repository, remote.getName(), remote.getPushUrls(), spec, force, updateTracking, skipHook, tagMode, listeners); + return doPush(repository, pushParams.getRemote().getName(), pushParams.getRemote().getPushUrls(), pushParams.getSpec(), + pushParams.isForce(), pushParams.shouldSetupTracking(), pushParams.shouldSkipHooks(), pushParams.getTagMode(), listeners); } @NotNull diff --git a/plugins/git4idea/src/git4idea/push/GitPushOperation.java b/plugins/git4idea/src/git4idea/push/GitPushOperation.java index 441429e3819b..9655119a16ae 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushOperation.java +++ b/plugins/git4idea/src/git4idea/push/GitPushOperation.java @@ -65,6 +65,7 @@ import static com.intellij.util.containers.ContainerUtil.filter; import static git4idea.commands.GitAuthenticationListener.GIT_AUTHENTICATION_SUCCESS; import static git4idea.push.GitPushNativeResult.Type.FORCED_UPDATE; import static git4idea.push.GitPushNativeResult.Type.NEW_REF; +import static git4idea.push.GitPushProcessCustomizationFactory.GIT_PUSH_CUSTOMIZATION_FACTORY_EP; import static git4idea.push.GitPushRepoResult.Type.NOT_PUSHED; import static git4idea.push.GitPushRepoResult.Type.REJECTED_NO_FF; @@ -92,6 +93,7 @@ public class GitPushOperation { private final ProgressIndicator myProgressIndicator; private final GitVcsSettings mySettings; private final GitRepositoryManager myRepositoryManager; + @Nullable private final GitPushProcessCustomizationFactory.GitPushProcessCustomization myPushProcessCustomization; public GitPushOperation(@NotNull Project project, @NotNull GitPushSupport pushSupport, @@ -121,6 +123,8 @@ public class GitPushOperation { currentHeads.put(repository, new GitRevisionNumber(head)); } } + + myPushProcessCustomization = findPushCustomization(); } @NotNull @@ -188,6 +192,7 @@ public class GitPushOperation { } } } + if (myPushProcessCustomization != null) myPushProcessCustomization.executeAfterPush(results); } finally { if (beforePushLabel != null) { @@ -200,6 +205,24 @@ public class GitPushOperation { return prepareCombinedResult(results, updatedRoots, preUpdatePositions, beforePushLabel, afterPushLabel); } + @Nullable + private GitPushProcessCustomizationFactory.GitPushProcessCustomization findPushCustomization() { + List customizations = StreamEx + .of(GIT_PUSH_CUSTOMIZATION_FACTORY_EP.getExtensions()) + .map(factory -> factory.createCustomization(myProject, myPushSpecs, myForce)).toList(); + + if (customizations.isEmpty()) { + return null; + } + else if (customizations.size() > 1) { + LOG.error("Only one GitPushProcessCustomization is allowed, but more are installed: " + customizations); + return null; + } + else { + return customizations.get(0); + } + } + @NotNull private Collection getRootsToUpdate(@NotNull PushUpdateSettings updateSettings, @NotNull Set rejectedRepositories) { @@ -303,6 +326,10 @@ public class GitPushOperation { results.put(repository, repoResult); } + if (myPushProcessCustomization != null) { + return myPushProcessCustomization.executeAfterPushIteration(results); + } + // fill other not-processed repositories as not-pushed for (GitRepository repository : repositories) { if (!results.containsKey(repository)) { @@ -358,8 +385,17 @@ public class GitPushOperation { String spec = sourceBranch.getFullName() + ":" + targetBranch.getNameForRemoteOperations(); GitRemote remote = targetBranch.getRemote(); - GitCommandResult res = myGit.push(repository, remote, spec, myForce, setUpstream, mySkipHook, tagMode, progressListener); - if(res.success()){ + GitPushParamsImpl params = new GitPushParamsImpl(remote, spec, myForce, setUpstream, mySkipHook, tagMode); + + GitCommandResult res; + if (myPushProcessCustomization != null) { + res = myPushProcessCustomization.runPushCommand(repository, pushSpec, params, progressListener); + } + else { + res = myGit.push(repository, params, progressListener); + } + + if (res.success()) { BackgroundTaskUtil.syncPublisher(myProject, GIT_AUTHENTICATION_SUCCESS).authenticationSucceeded(repository, remote); } return new ResultWithOutput(res); diff --git a/plugins/git4idea/src/git4idea/push/GitPushParams.java b/plugins/git4idea/src/git4idea/push/GitPushParams.java new file mode 100644 index 000000000000..0b823c26d749 --- /dev/null +++ b/plugins/git4idea/src/git4idea/push/GitPushParams.java @@ -0,0 +1,24 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package git4idea.push; + +import git4idea.repo.GitRemote; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface GitPushParams { + @NotNull + GitRemote getRemote(); + + @NotNull + String getSpec(); + + boolean isForce(); + + boolean shouldSetupTracking(); + + boolean shouldSkipHooks(); + + @Nullable + String getTagMode(); +} + diff --git a/plugins/git4idea/src/git4idea/push/GitPushParamsImpl.java b/plugins/git4idea/src/git4idea/push/GitPushParamsImpl.java new file mode 100644 index 000000000000..5d297aab185b --- /dev/null +++ b/plugins/git4idea/src/git4idea/push/GitPushParamsImpl.java @@ -0,0 +1,63 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package git4idea.push; + +import git4idea.repo.GitRemote; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class GitPushParamsImpl implements GitPushParams { + @NotNull private final GitRemote myRemote; + @NotNull private final String mySpec; + private final boolean myForce; + private final boolean mySetupTracking; + private final boolean mySkipHooks; + @Nullable private final String myTagMode; + + public GitPushParamsImpl(@NotNull GitRemote remote, + @NotNull String spec, + boolean force, + boolean setupTracking, + boolean skipHooks, + @Nullable String tagMode) { + + myRemote = remote; + mySpec = spec; + myForce = force; + mySetupTracking = setupTracking; + mySkipHooks = skipHooks; + myTagMode = tagMode; + } + + @NotNull + @Override + public GitRemote getRemote() { + return myRemote; + } + + @NotNull + @Override + public String getSpec() { + return mySpec; + } + + @Override + public boolean isForce() { + return myForce; + } + + @Override + public boolean shouldSetupTracking() { + return mySetupTracking; + } + + @Override + public boolean shouldSkipHooks() { + return mySkipHooks; + } + + @Nullable + @Override + public String getTagMode() { + return myTagMode; + } +} diff --git a/plugins/git4idea/src/git4idea/push/GitPushProcessCustomizationFactory.java b/plugins/git4idea/src/git4idea/push/GitPushProcessCustomizationFactory.java new file mode 100644 index 000000000000..1dad85a69142 --- /dev/null +++ b/plugins/git4idea/src/git4idea/push/GitPushProcessCustomizationFactory.java @@ -0,0 +1,60 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package git4idea.push; + +import com.intellij.dvcs.push.PushSpec; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import git4idea.commands.GitCommandResult; +import git4idea.commands.GitLineHandlerListener; +import git4idea.repo.GitRepository; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Implement to customize some parts of the {@link GitPushOperation git push process}. + *

+ * Note that only one customization can be installed: if several ones are found among installed plugins, none is being executed. + */ +public interface GitPushProcessCustomizationFactory { + + ExtensionPointName GIT_PUSH_CUSTOMIZATION_FACTORY_EP = + ExtensionPointName.create("com.intellij.vcs.git.pushCustomizationFactory"); + + /** + * Creates a push customization instance for a push session, if such customization is allowed in the given conditions. + */ + @Nullable + GitPushProcessCustomization createCustomization(@NotNull Project project, + @NotNull Map> pushSpecs, + boolean forcePush); + + interface GitPushProcessCustomization { + + /** + * Will be executed after one push iteration, + * i.e. after all `git push` commands are called against all selected repositories, + * but before the rebase/merge is proposed or a notification is shown. + * + * @param results Results of `git push` commands. + * @return Adjusted results of the push process - these will be processed by the push process instead of the original ones. + */ + @NotNull + Map executeAfterPushIteration(@NotNull Map results); + + /** + * Overrides the call to `git push`. + */ + @NotNull + GitCommandResult runPushCommand(@NotNull GitRepository repository, + @NotNull PushSpec pushSpec, + @NotNull GitPushParams pushParams, + @NotNull GitLineHandlerListener progressListener); + + /** + * Will be executed after the whole push procedure is finished, successfully or not. + */ + void executeAfterPush(@NotNull Map results); + } +} diff --git a/plugins/git4idea/tests/git4idea/test/TestGit.kt b/plugins/git4idea/tests/git4idea/test/TestGit.kt index da1cf3d45190..e6f4d344ef48 100644 --- a/plugins/git4idea/tests/git4idea/test/TestGit.kt +++ b/plugins/git4idea/tests/git4idea/test/TestGit.kt @@ -24,9 +24,9 @@ import git4idea.commands.GitCommandResult import git4idea.commands.GitImpl import git4idea.commands.GitLineHandler import git4idea.commands.GitLineHandlerListener +import git4idea.push.GitPushParams import git4idea.rebase.GitInteractiveRebaseEditorHandler import git4idea.rebase.GitRebaseEditorService -import git4idea.repo.GitRemote import git4idea.repo.GitRepository import java.io.File @@ -51,16 +51,10 @@ class TestGitImpl : GitImpl() { val plainTextEditor: ((String) -> String)?) override fun push(repository: GitRepository, - remote: GitRemote, - spec: String, - force: Boolean, - updateTracking: Boolean, - skipHook: Boolean, - tagMode: String?, + pushParams: GitPushParams, vararg listeners: GitLineHandlerListener): GitCommandResult { pushListener?.invoke(repository) - return myPushHandler(repository) ?: - super.push(repository, remote, spec, force, updateTracking, skipHook, tagMode, *listeners) + return myPushHandler(repository) ?: super.push(repository, pushParams, *listeners) } override fun branchDelete(repository: GitRepository,