git: let plugins customize some parts of the push process

VCS-969
This commit is contained in:
Kirill Likhodedov
2018-03-11 18:51:10 +03:00
parent dad4576381
commit c9672a471d
9 changed files with 198 additions and 27 deletions
+2
View File
@@ -235,5 +235,7 @@
<extensionPoints>
<extensionPoint qualifiedName="Git4Idea.GitHttpAuthDataProvider" interface="git4idea.remote.GitHttpAuthDataProvider"/>
<extensionPoint qualifiedName="Git4Idea.gitRepositoryHostingService" interface="git4idea.remote.GitRepositoryHostingService"/>
<extensionPoint qualifiedName="com.intellij.vcs.git.pushCustomizationFactory"
interface="git4idea.push.GitPushProcessCustomizationFactory" />
</extensionPoints>
</idea-plugin>
@@ -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
@@ -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
@@ -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
@@ -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<GitPushProcessCustomizationFactory.GitPushProcessCustomization> 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<GitRepository> getRootsToUpdate(@NotNull PushUpdateSettings updateSettings,
@NotNull Set<GitRepository> 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);
@@ -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();
}
@@ -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;
}
}
@@ -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}.
* <br/><br/>
* Note that only one customization can be installed: if several ones are found among installed plugins, none is being executed.
*/
public interface GitPushProcessCustomizationFactory {
ExtensionPointName<GitPushProcessCustomizationFactory> 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<GitRepository, PushSpec<GitPushSource, GitPushTarget>> 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<GitRepository, GitPushRepoResult> executeAfterPushIteration(@NotNull Map<GitRepository, GitPushRepoResult> results);
/**
* Overrides the call to `git push`.
*/
@NotNull
GitCommandResult runPushCommand(@NotNull GitRepository repository,
@NotNull PushSpec<GitPushSource, GitPushTarget> pushSpec,
@NotNull GitPushParams pushParams,
@NotNull GitLineHandlerListener progressListener);
/**
* Will be executed after the whole push procedure is finished, successfully or not.
*/
void executeAfterPush(@NotNull Map<GitRepository, GitPushRepoResult> results);
}
}
@@ -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,