(myAllRepositories.size());
- if (myRootNode.getChildCount() == 0) { // the method is requested before tree construction began => returning all repos.
- return myAllRepositories;
- }
-
- for (int i = 0; i < myRootNode.getChildCount(); i++) {
- TreeNode child = myRootNode.getChildAt(i);
- if (child instanceof CheckedTreeNode) {
- CheckedTreeNode node = (CheckedTreeNode)child;
- if (node.isChecked()) {
- if (node.getUserObject() instanceof GitRepository) {
- selectedRepositories.add((GitRepository)node.getUserObject());
- }
- }
- }
- }
- return selectedRepositories;
- }
- }
- finally {
- TREE_CONSTRUCTION_LOCK.readLock().unlock();
- }
- }
-
- @NotNull
- private static String getDateString(@NotNull GitCommit commit) {
- return DvcsUtil.getDateString(commit);
- }
-
- @NotNull
- private static String getHashString(@NotNull GitCommit commit) {
- return DvcsUtil.getShortHash(commit.getId().toString());
- }
-
- private static class MyTreeCellRenderer extends CheckboxTree.CheckboxTreeCellRenderer {
-
- @Override
- public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
- Object userObject;
- if (value instanceof CheckedTreeNode) {
- userObject = ((CheckedTreeNode)value).getUserObject();
- } else if (value instanceof DefaultMutableTreeNode) {
- userObject = ((DefaultMutableTreeNode)value).getUserObject();
- } else {
- return;
- }
-
- ColoredTreeCellRenderer renderer = getTextRenderer();
- if (userObject instanceof GitCommit) {
- GitCommit commit = (GitCommit)userObject;
- renderer.append(commit.getSubject(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, getTextRenderer().getForeground()));
- renderer.setToolTipText(getHashString(commit) + " " + getDateString(commit));
- }
- else if (userObject instanceof GitRepository) {
- String repositoryPath = DvcsUtil.getShortRepositoryName((GitRepository)userObject);
- renderer.append(repositoryPath, SimpleTextAttributes.GRAY_ATTRIBUTES);
- }
- else if (userObject instanceof GitPushBranchInfo) {
- GitPushBranchInfo branchInfo = (GitPushBranchInfo) userObject;
- GitBranch fromBranch = branchInfo.getSourceBranch();
- GitBranch dest = branchInfo.getDestBranch();
-
- GitPushBranchInfo.Type type = branchInfo.getType();
- final String showingRecentCommits = ", showing " + GitPusher.RECENT_COMMITS_NUMBER + " recent commits";
- String text = fromBranch.getName();
- SimpleTextAttributes attrs = SimpleTextAttributes.REGULAR_ATTRIBUTES;
- String additionalText = "";
- switch (type) {
- case STANDARD:
- text += " -> " + dest.getName();
- if (branchInfo.getCommits().isEmpty()) {
- additionalText = " nothing to push";
- }
- break;
- case NEW_BRANCH:
- text += " -> +" + dest.getName();
- attrs = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES;
- additionalText = " new branch will be created" + showingRecentCommits;
- break;
- case NO_TRACKED_OR_TARGET:
- attrs = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES;
- additionalText = " no tracked branch. Use checkbox below to push branch to manually specified" + showingRecentCommits;
- break;
- }
- renderer.append(text, attrs);
- renderer.append(additionalText, new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, UIUtil.getInactiveTextColor()));
- }
- else if (userObject instanceof FakeCommit) {
- int spaces = 6 + 15 + 3 + 30;
- String s = String.format("%" + spaces + "s", " ");
- renderer.append(s, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, renderer.getBackground()));
- }
- else if (userObject instanceof MoreCommitsToShow) {
- renderer.append("...");
- }
- else {
- renderer.append(userObject == null ? "" : userObject.toString());
- }
- }
- }
-
- private static class FakeCommit {
- }
-
- private static class MoreCommitsToShow {
- }
-}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushNativeResult.java b/plugins/git4idea/src/git4idea/push/GitPushNativeResult.java
new file mode 100644
index 000000000000..206d5311961b
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/push/GitPushNativeResult.java
@@ -0,0 +1,77 @@
+/*
+ * 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 git4idea.push;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Push result as reported by {@code git push} command.
+ *
+ * @see GitPushNativeResultParser
+ * @see GitPushRepoResult
+ */
+class GitPushNativeResult {
+
+ enum Type {
+ SUCCESS,
+ FORCED_UPDATE,
+ NEW_REF,
+ REJECTED,
+ DELETED,
+ UP_TO_DATE,
+ ERROR
+ }
+
+ @NotNull private final Type myType;
+ @Nullable private final String myRange;
+ @Nullable private final String myErrorOutput;
+
+ private GitPushNativeResult(@NotNull Type type, @Nullable String range, @Nullable String errorOutput) {
+ myType = type;
+ myRange = range;
+ myErrorOutput = errorOutput;
+ }
+
+ public GitPushNativeResult(@NotNull Type type, @Nullable String range) {
+ this(type, range, null);
+ }
+
+ @NotNull
+ public static GitPushNativeResult error(@NotNull String errorOutput) {
+ return new GitPushNativeResult(Type.ERROR, null, errorOutput);
+ }
+
+ @NotNull
+ public Type getType() {
+ return myType;
+ }
+
+ @Nullable
+ public String getRange() {
+ return myRange;
+ }
+
+ @Nullable
+ public String getErrorOutput() {
+ return myErrorOutput;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s: '%s', stderr:[%s]", myType, myRange, myErrorOutput);
+ }
+}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushNativeResultParser.java b/plugins/git4idea/src/git4idea/push/GitPushNativeResultParser.java
new file mode 100644
index 000000000000..91eff9a72014
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/push/GitPushNativeResultParser.java
@@ -0,0 +1,132 @@
+/*
+ * 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 git4idea.push;
+
+import com.intellij.openapi.diagnostic.Logger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses the output received from git push and returns a result.
+ * NB: It is assumed that only one ref is pushed => there is only one result in the output.
+ *
+ * Output format described by git-push man:
+ *
+ * The status of the push is output in tabular form, with each line representing the status of a single ref.
+ * If --porcelain is used, then each line of the output is of the form:
+ *
+ * <flag> \t <from>:<to> \t <summary> (<reason>)
+ *
+ * The status of up-to-date refs is shown only if --porcelain or --verbose option is used.
+ *
+ * flag
+ * A single character indicating the status of the ref:
+ * (space)
+ * for a successfully pushed fast-forward;
+ * +
+ * for a successful forced update;
+ * -
+ * for a successfully deleted ref;
+ * *
+ * for a successfully pushed new ref;
+ * !
+ * for a ref that was rejected or failed to push; and
+ * =
+ * for a ref that was up to date and did not need pushing.
+ *
+ * summary
+ * For a successfully pushed ref, the summary shows the old and new values of the ref in a form
+ * suitable for using as an argument to git log (this is .. in most cases, and
+ * ... for forced non-fast-forward updates).
+ *
+ * For a failed update, more details are given:
+ * rejected
+ * Git did not try to send the ref at all, typically because it is not a fast-forward and you
+ * did not force the update.
+ *
+ * remote rejected
+ * The remote end refused the update. Usually caused by a hook on the remote side, or because
+ * the remote repository has one of the following safety options in effect:
+ * receive.denyCurrentBranch (for pushes to the checked out branch), receive.denyNonFastForwards
+ * (for forced non-fast-forward updates), receive.denyDeletes or receive.denyDeleteCurrent. See
+ * git-config(1).
+ *
+ * remote failure
+ * The remote end did not report the successful update of the ref, perhaps because of a
+ * temporary error on the remote side, a break in the network connection, or other transient
+ * error.
+ *
+ * from
+ * The name of the local ref being pushed, minus its refs// prefix. In the case of deletion,
+ * the name of the local ref is omitted.
+ *
+ * to
+ * The name of the remote ref being updated, minus its refs// prefix.
+ *
+ * reason
+ * A human-readable explanation. In the case of successfully pushed refs, no explanation is needed.
+ * For a failed ref, the reason for failure is described.
+ *
+ */
+public class GitPushNativeResultParser {
+
+ private static final Logger LOG = Logger.getInstance(GitPushNativeResultParser.class);
+ private static final Pattern PATTERN = Pattern.compile("^.*([ +\\-\\*!=])\\s(\\S+):(\\S+)\\s(\\S+).*$");
+ private static final Pattern RANGE = Pattern.compile("[0-9a-f]+[\\.]{2,3}[0-9a-f]+");
+
+ @Nullable
+ public static GitPushNativeResult parse(@NotNull List output) {
+ for (String line : output) {
+ Matcher matcher = PATTERN.matcher(line);
+ if (matcher.matches()) {
+ return parseRefResult(matcher, line);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static GitPushNativeResult parseRefResult(Matcher matcher, String line) {
+ String flag = matcher.group(1);
+ String from = matcher.group(2);
+ String to = matcher.group(3);
+ String summary = matcher.group(4);
+
+ GitPushNativeResult.Type type = parseType(flag);
+ if (type == null) {
+ LOG.error("Couldn't parse push result type from flag [" + flag + "] in [" + line + "]");
+ return null;
+ }
+ String range = RANGE.matcher(summary).matches() ? summary : null;
+ return new GitPushNativeResult(type, range);
+ }
+
+ private static GitPushNativeResult.Type parseType(String flag) {
+ switch(flag.charAt(0)) {
+ case ' ' : return GitPushNativeResult.Type.SUCCESS;
+ case '+' : return GitPushNativeResult.Type.FORCED_UPDATE;
+ case '-' : return GitPushNativeResult.Type.DELETED;
+ case '*' : return GitPushNativeResult.Type.NEW_REF;
+ case '!' : return GitPushNativeResult.Type.REJECTED;
+ case '=' : return GitPushNativeResult.Type.UP_TO_DATE;
+ }
+ return null;
+ }
+}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushOperation.java b/plugins/git4idea/src/git4idea/push/GitPushOperation.java
new file mode 100644
index 000000000000..18edf5ce9eb6
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/push/GitPushOperation.java
@@ -0,0 +1,370 @@
+/*
+ * 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 git4idea.push;
+
+import com.intellij.dvcs.DvcsUtil;
+import com.intellij.dvcs.push.PushSpec;
+import com.intellij.history.Label;
+import com.intellij.history.LocalHistory;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.EmptyProgressIndicator;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.update.UpdatedFiles;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Function;
+import com.intellij.util.ObjectUtils;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.UIUtil;
+import git4idea.*;
+import git4idea.branch.GitBranchUtil;
+import git4idea.commands.Git;
+import git4idea.commands.GitCommandResult;
+import git4idea.commands.GitLineHandlerListener;
+import git4idea.commands.GitStandardProgressAnalyzer;
+import git4idea.config.GitVcsSettings;
+import git4idea.config.UpdateMethod;
+import git4idea.history.GitHistoryUtils;
+import git4idea.merge.MergeChangeCollector;
+import git4idea.repo.GitBranchTrackInfo;
+import git4idea.repo.GitRepository;
+import git4idea.settings.GitPushSettings;
+import git4idea.update.GitUpdateProcess;
+import git4idea.update.GitUpdateResult;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * Executes git push operation:
+ *
+ * - Calls push for the given repositories with given parameters;
+ * - Collects results;
+ * - If push is rejected, proposes to update via merge or rebase;
+ * - Shows a notification about push result
+ *
+ */
+public class GitPushOperation {
+
+ private static final Logger LOG = Logger.getInstance(GitPushOperation.class);
+ private static final int MAX_PUSH_ATTEMPTS = 10;
+
+ private final Project myProject;
+ private final Map> myPushSpecs;
+ private final boolean myForce;
+ private final Git myGit;
+ private final ProgressIndicator myProgressIndicator;
+ private final GitVcsSettings mySettings;
+ private final GitPushSettings myPushSettings;
+ private final GitPlatformFacade myPlatformFacade;
+
+ public GitPushOperation(Project project, Map> pushSpecs, boolean force) {
+ myProject = project;
+ myPushSpecs = pushSpecs;
+ myForce = force;
+ myGit = ServiceManager.getService(Git.class);
+ myProgressIndicator = ObjectUtils.notNull(ProgressManager.getInstance().getProgressIndicator(), new EmptyProgressIndicator());
+ mySettings = GitVcsSettings.getInstance(myProject);
+ myPushSettings = GitPushSettings.getInstance(myProject);
+ myPlatformFacade = ServiceManager.getService(project, GitPlatformFacade.class);
+
+ Map currentHeads = ContainerUtil.newHashMap();
+ for (GitRepository repository : pushSpecs.keySet()) {
+ repository.update();
+ String head = repository.getCurrentRevision();
+ if (head == null) {
+ LOG.error("This repository has no commits");
+ }
+ else {
+ currentHeads.put(repository, new GitRevisionNumber(head));
+ }
+ }
+ }
+
+ @NotNull
+ public GitPushResult execute() {
+ PushUpdateSettings updateSettings = readPushUpdateSettings();
+ Label beforePushLabel = null;
+ Label afterPushLabel = null;
+ Map preUpdatePositions = updateRootInfoAndRememberPositions();
+
+ final Map results = ContainerUtil.newHashMap();
+ Map updatedRoots = ContainerUtil.newHashMap();
+
+ try {
+ Collection remainingRoots = myPushSpecs.keySet();
+ for (int pushAttempt = 0;
+ pushAttempt < MAX_PUSH_ATTEMPTS && !remainingRoots.isEmpty();
+ pushAttempt++, remainingRoots = getRejectedAndNotPushed(results)) {
+ Map resultMap = push(remainingRoots);
+ results.putAll(resultMap);
+
+ GroupedPushResult result = GroupedPushResult.group(resultMap);
+
+ // stop on first error
+ if (!result.errors.isEmpty()) {
+ break;
+ }
+
+ // propose to update if rejected
+ if (!result.rejected.isEmpty()) {
+ boolean shouldUpdate = true;
+ if (pushingToNotTrackedBranch(result.rejected)) {
+ shouldUpdate = false;
+ }
+ else if (pushAttempt == 0 && !mySettings.autoUpdateIfPushRejected()) {
+ updateSettings = showDialogAndGetExitCode(result.rejected.keySet(), updateSettings);
+ if (updateSettings != null) {
+ savePushUpdateSettings(updateSettings);
+ }
+ else {
+ shouldUpdate = false;
+ }
+ }
+
+ if (!shouldUpdate) {
+ break;
+ }
+
+ if (beforePushLabel == null) { // put the label only before the very first update
+ beforePushLabel = LocalHistory.getInstance().putSystemLabel(myProject, "Before push");
+ }
+ Collection rootsToUpdate = updateSettings.shouldUpdateAllRoots() ?
+ myPushSpecs.keySet() :
+ result.rejected.keySet();
+ GitUpdateResult updateResult = update(rootsToUpdate, updateSettings.getUpdateMethod());
+ for (GitRepository repository : rootsToUpdate) {
+ updatedRoots.put(repository, updateResult); // TODO update result in GitUpdateProcess is a single for several roots
+ }
+ if (!updateResult.isSuccess() ||
+ updateResult == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS || updateResult == GitUpdateResult.INCOMPLETE) {
+ break;
+ }
+ }
+ }
+ }
+ finally {
+ if (beforePushLabel != null) {
+ afterPushLabel = LocalHistory.getInstance().putSystemLabel(myProject, "After push");
+ }
+ }
+ return prepareCombinedResult(results, updatedRoots, preUpdatePositions, beforePushLabel, afterPushLabel);
+ }
+
+ private static boolean pushingToNotTrackedBranch(@NotNull Map rejected) {
+ return ContainerUtil.exists(rejected.entrySet(), new Condition>() {
+ @Override
+ public boolean value(Map.Entry entry) {
+ GitRepository repository = entry.getKey();
+ GitLocalBranch currentBranch = repository.getCurrentBranch();
+ assert currentBranch != null;
+ GitBranchTrackInfo trackInfo = GitBranchUtil.getTrackInfoForBranch(repository, currentBranch);
+ return trackInfo == null || !trackInfo.getRemoteBranch().getFullName().equals(entry.getValue().getTargetBranch());
+ }
+ });
+ }
+
+ @NotNull
+ private static List getRejectedAndNotPushed(@NotNull final Map results) {
+ return ContainerUtil.filter(results.keySet(), new Condition() {
+ @Override
+ public boolean value(GitRepository repository) {
+ return results.get(repository).getType() == GitPushRepoResult.Type.REJECTED ||
+ results.get(repository).getType() == GitPushRepoResult.Type.NOT_PUSHED;
+ }
+ });
+ }
+
+ @NotNull
+ private Map updateRootInfoAndRememberPositions() {
+ return ContainerUtil.map2Map(myPushSpecs.keySet(),
+ new Function>() {
+ @Override
+ public Pair fun(GitRepository repository) {
+ repository.update();
+ return Pair.create(repository, repository.getCurrentRevision());
+ }
+ });
+ }
+
+ private GitPushResult prepareCombinedResult(final Map allRoots,
+ final Map updatedRoots,
+ final Map preUpdatePositions,
+ Label beforeUpdateLabel,
+ Label afterUpdateLabel) {
+ Map results = ContainerUtil.newHashMap();
+ UpdatedFiles updatedFiles = UpdatedFiles.create();
+ for (Map.Entry entry : allRoots.entrySet()) {
+ GitRepository repository = entry.getKey();
+ GitPushRepoResult simpleResult = entry.getValue();
+ GitUpdateResult updateResult = updatedRoots.get(repository);
+ if (updateResult == null) {
+ results.put(repository, simpleResult);
+ }
+ else {
+ collectUpdatedFiles(updatedFiles, repository, preUpdatePositions.get(repository));
+ results.put(repository, GitPushRepoResult.addUpdateResult(simpleResult, updateResult));
+ }
+ }
+ return new GitPushResult(results, updatedFiles, beforeUpdateLabel, afterUpdateLabel);
+ }
+
+ @NotNull
+ private Map push(@NotNull Collection repositories) {
+ Map results = ContainerUtil.newLinkedHashMap();
+ for (GitRepository repository : repositories) {
+ PushSpec spec = myPushSpecs.get(repository);
+ GitPushNativeResult result = doPush(repository, spec);
+ LOG.debug("Pushed to " + DvcsUtil.getShortRepositoryName(repository) + ": " + result);
+
+ int commits = collectNumberOfPushedCommits(repository.getRoot(), result);
+
+ GitPushTarget target = spec.getTarget();
+ assert target != null;
+ GitPushRepoResult repoResult = GitPushRepoResult.convertFromNative(result, commits, spec.getSource().getBranch(), target.getBranch());
+ LOG.debug("Converted result: " + repoResult);
+ results.put(repository, repoResult);
+ }
+
+ // fill other not-processed repositories as not-pushed
+ for (GitRepository repository : repositories) {
+ if (!results.containsKey(repository)) {
+ PushSpec spec = myPushSpecs.get(repository);
+ results.put(repository, GitPushRepoResult.notPushed(spec.getSource().getBranch(), spec.getTarget().getBranch()));
+ }
+ }
+ return results;
+ }
+
+ private int collectNumberOfPushedCommits(@NotNull VirtualFile root, @NotNull GitPushNativeResult result) {
+ if (result.getType() != GitPushNativeResult.Type.SUCCESS) {
+ return -1;
+ }
+ String range = result.getRange();
+ if (range == null) {
+ LOG.error("Range of pushed commits not reported in " + result);
+ return -1;
+ }
+ try {
+ return GitHistoryUtils.history(myProject, root, range).size();
+ }
+ catch (VcsException e) {
+ LOG.error("Couldn't collect commits from range " + range);
+ return -1;
+ }
+ }
+
+ private void collectUpdatedFiles(@NotNull UpdatedFiles updatedFiles, @NotNull GitRepository repository,
+ @NotNull String preUpdatePosition) {
+ MergeChangeCollector collector = new MergeChangeCollector(myProject, repository.getRoot(), new GitRevisionNumber(preUpdatePosition));
+ ArrayList exceptions = new ArrayList();
+ collector.collect(updatedFiles, exceptions);
+ for (VcsException exception : exceptions) {
+ LOG.info(exception);
+ }
+ }
+
+ @NotNull
+ private GitPushNativeResult doPush(@NotNull GitRepository repository, @NotNull PushSpec pushSpec) {
+ GitPushTarget target = pushSpec.getTarget();
+ assert target != null;
+ GitLocalBranch sourceBranch = pushSpec.getSource().getBranch();
+ GitRemoteBranch targetBranch = target.getBranch();
+
+ GitLineHandlerListener progressListener = GitStandardProgressAnalyzer.createListener(myProgressIndicator);
+ boolean setUpstream = pushSpec.getTarget().isNewBranchCreated() && !branchTrackingInfoIsSet(repository, sourceBranch);
+ GitCommandResult res = myGit.push(repository, sourceBranch, targetBranch, myForce, setUpstream, progressListener);
+
+ GitPushNativeResult result = GitPushNativeResultParser.parse(res.getOutput());
+ if (result == null) {
+ result = GitPushNativeResult.error(res.getErrorOutputAsJoinedString());
+ }
+ return result;
+ }
+
+ private static boolean branchTrackingInfoIsSet(@NotNull GitRepository repository, @NotNull final GitLocalBranch source) {
+ return ContainerUtil.exists(repository.getBranchTrackInfos(), new Condition() {
+ @Override
+ public boolean value(GitBranchTrackInfo info) {
+ return info.getLocalBranch().equals(source);
+ }
+ });
+ }
+
+ private void savePushUpdateSettings(@NotNull PushUpdateSettings PushUpdateSettings) {
+ UpdateMethod updateMethod = PushUpdateSettings.getUpdateMethod();
+ myPushSettings.setUpdateAllRoots(PushUpdateSettings.shouldUpdateAllRoots());
+ myPushSettings.setUpdateMethod(updateMethod);
+ }
+
+ @NotNull
+ private PushUpdateSettings readPushUpdateSettings() {
+ boolean updateAllRoots = myPushSettings.shouldUpdateAllRoots();
+ UpdateMethod updateMethod = myPushSettings.getUpdateMethod();
+ return new PushUpdateSettings(updateAllRoots, updateMethod);
+ }
+
+ @Nullable
+ private PushUpdateSettings showDialogAndGetExitCode(@NotNull final Set repositories,
+ @NotNull final PushUpdateSettings initialSettings) {
+ final Ref updateSettings = Ref.create();
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ GitRejectedPushUpdateDialog dialog = new GitRejectedPushUpdateDialog(myProject, repositories, initialSettings);
+ DialogManager.show(dialog);
+ int exitCode = dialog.getExitCode();
+ if (exitCode != DialogWrapper.CANCEL_EXIT_CODE) {
+ mySettings.setAutoUpdateIfPushRejected(dialog.shouldAutoUpdateInFuture());
+ updateSettings.set(new PushUpdateSettings(dialog.shouldUpdateAll(), convertUpdateMethodFromDialogExitCode(exitCode)));
+ }
+ }
+ });
+ return updateSettings.get();
+ }
+
+ @NotNull
+ private static UpdateMethod convertUpdateMethodFromDialogExitCode(int exitCode) {
+ switch (exitCode) {
+ case GitRejectedPushUpdateDialog.MERGE_EXIT_CODE: return UpdateMethod.MERGE;
+ case GitRejectedPushUpdateDialog.REBASE_EXIT_CODE: return UpdateMethod.REBASE;
+ default: throw new IllegalStateException("Unexpected exit code: " + exitCode);
+ }
+ }
+
+ @NotNull
+ protected GitUpdateResult update(@NotNull Collection rootsToUpdate, @NotNull UpdateMethod updateMethod) {
+ GitUpdateProcess.UpdateMethod um = updateMethod == UpdateMethod.MERGE ?
+ GitUpdateProcess.UpdateMethod.MERGE :
+ GitUpdateProcess.UpdateMethod.REBASE;
+ GitUpdateResult updateResult = new GitUpdateProcess(myProject, myPlatformFacade, myProgressIndicator,
+ new HashSet(rootsToUpdate), UpdatedFiles.create()).update(um);
+ for (GitRepository repository : rootsToUpdate) {
+ repository.getRoot().refresh(true, true);
+ repository.update();
+ }
+ return updateResult;
+ }
+
+}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushRejectedDetector.java b/plugins/git4idea/src/git4idea/push/GitPushRejectedDetector.java
deleted file mode 100644
index ca96475cfeec..000000000000
--- a/plugins/git4idea/src/git4idea/push/GitPushRejectedDetector.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2000-2011 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 git4idea.push;
-
-import com.intellij.openapi.util.Key;
-import git4idea.commands.GitLineHandlerListener;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @author Kirill Likhodedov
- */
-class GitPushRejectedDetector implements GitLineHandlerListener {
-
- private static final Pattern REJECTED_PATTERN = Pattern.compile("\\s+! \\[rejected\\]\\s+(\\S+) -> (\\S+) .*");
-
- private final Collection myRejectedRefs = new ArrayList();
-
- @Override
- public void onLineAvailable(String line, Key outputType) {
- Matcher matcher = REJECTED_PATTERN.matcher(line);
- if (matcher.matches()) {
- String src = matcher.group(1);
- String dst = matcher.group(2);
- myRejectedRefs.add(new RejectedRef(src, dst));
- }
- }
-
- @Override
- public void processTerminated(int exitCode) {
- }
-
- @Override
- public void startFailed(Throwable exception) {
- }
-
- public boolean rejected() {
- return !myRejectedRefs.isEmpty();
- }
-
- public Collection getRejectedBranches() {
- Collection branches = new ArrayList(myRejectedRefs.size());
- for (RejectedRef rejectedRef : myRejectedRefs) {
- branches.add(rejectedRef.mySource);
- }
- return branches;
- }
-
- static class RejectedRef {
- private final String mySource;
- private final String myDestination;
-
- RejectedRef(String source, String destination) {
- myDestination = destination;
- mySource = source;
- }
- }
-}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushRepoResult.java b/plugins/git4idea/src/git4idea/push/GitPushRepoResult.java
index 361fdade3398..360d3dbe20c2 100644
--- a/plugins/git4idea/src/git4idea/push/GitPushRepoResult.java
+++ b/plugins/git4idea/src/git4idea/push/GitPushRepoResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -15,150 +15,134 @@
*/
package git4idea.push;
-import com.intellij.openapi.util.text.StringUtil;
-import git4idea.GitBranch;
+import git4idea.GitLocalBranch;
+import git4idea.GitRemoteBranch;
+import git4idea.update.GitUpdateResult;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import static git4idea.util.GitUIUtil.bold;
-import static git4idea.util.GitUIUtil.code;
+import java.util.Comparator;
/**
- * If an error happens, all push is unsuccessful, for all branches.
- * Otherwise we've got separate results for branches.
+ * Result of pushing one repository.
+ *
+ * Includes information about the number of pushed commits (or -1 if undefined),
+ * and tells whether the repository was updated after the push was rejected.
+ *
+ * @see git4idea.push.GitPushNativeResult
*/
-final class GitPushRepoResult {
+class GitPushRepoResult {
enum Type {
- NOT_PUSHING,
SUCCESS,
- SOME_REJECTED,
+ NEW_BRANCH,
+ FORCED,
+ REJECTED,
ERROR,
- CANCEL,
- NOT_AUTHORIZED
+ NOT_PUSHED;
}
- private final Type myType;
- private final String myOutput;
- private final Map myBranchResults;
+ static Comparator TYPE_COMPARATOR = new Comparator() {
+ @Override
+ public int compare(Type o1, Type o2) {
+ return o1.ordinal() - o2.ordinal();
+ }
+ };
- GitPushRepoResult(@NotNull Type type, @NotNull Map resultsByBranch, @NotNull String output) {
+ @NotNull private final Type myType;
+ private final int myCommits;
+ @NotNull private final String mySourceBranch;
+ @NotNull private final String myTargetBranch;
+ @Nullable private final String myError;
+ @Nullable private final GitUpdateResult myUpdateResult;
+
+ @NotNull
+ static GitPushRepoResult convertFromNative(@NotNull GitPushNativeResult result,
+ int commits, @NotNull GitLocalBranch source, @NotNull GitRemoteBranch target) {
+ return new GitPushRepoResult(convertType(result.getType()), commits, source.getFullName(), target.getFullName(),
+ result.getErrorOutput(), null);
+ }
+
+ @NotNull
+ static GitPushRepoResult notPushed(GitLocalBranch source, GitRemoteBranch target) {
+ return new GitPushRepoResult(Type.NOT_PUSHED, -1, source.getFullName(), target.getFullName(), null, null);
+ }
+
+ @NotNull
+ static GitPushRepoResult addUpdateResult(GitPushRepoResult original, GitUpdateResult updateResult) {
+ return new GitPushRepoResult(original.getType(), original.getNumberOfPushedCommits(), original.getSourceBranch(),
+ original.getTargetBranch(), original.getError(), updateResult);
+ }
+
+ private GitPushRepoResult(@NotNull Type type, int pushedCommits, @NotNull String sourceBranch, @NotNull String targetBranch,
+ @Nullable String error, @Nullable GitUpdateResult result) {
myType = type;
- myBranchResults = resultsByBranch;
- myOutput = output;
- }
-
- @NotNull
- static GitPushRepoResult success(@NotNull Map resultsByBranch, @NotNull String output) {
- return new GitPushRepoResult(Type.SUCCESS, resultsByBranch, output);
- }
-
- @NotNull
- static GitPushRepoResult error(@NotNull Map resultsByBranch, @NotNull String output) {
- return new GitPushRepoResult(Type.ERROR, resultsByBranch, output);
- }
-
- @NotNull
- static GitPushRepoResult someRejected(@NotNull Map resultsByBranch, @NotNull String output) {
- return new GitPushRepoResult(Type.SOME_REJECTED, resultsByBranch, output);
- }
-
- @NotNull
- public static GitPushRepoResult cancelled(@NotNull String output) {
- return new GitPushRepoResult(Type.CANCEL, Collections.emptyMap(), output);
- }
-
- @NotNull
- public static GitPushRepoResult notAuthorized(@NotNull String output) {
- return new GitPushRepoResult(Type.NOT_AUTHORIZED, Collections.emptyMap(), output);
- }
-
- @NotNull
- static GitPushRepoResult notPushed() {
- return new GitPushRepoResult(Type.NOT_PUSHING, Collections.emptyMap(), "");
+ myCommits = pushedCommits;
+ mySourceBranch = sourceBranch;
+ myTargetBranch = targetBranch;
+ myError = error;
+ myUpdateResult = result;
}
@NotNull
Type getType() {
return myType;
}
-
- boolean isOneOfErrors() {
- return myType == Type.ERROR || myType == Type.CANCEL || myType == Type.NOT_AUTHORIZED;
+
+ @Nullable
+ GitUpdateResult getUpdateResult() {
+ return myUpdateResult;
}
- @NotNull
- String getOutput() {
- return myOutput;
- }
-
- @NotNull
- Map getBranchResults() {
- return myBranchResults;
- }
-
- @NotNull
- GitPushRepoResult remove(@NotNull GitBranch branch) {
- Map resultsByBranch = new HashMap();
- for (Map.Entry entry : myBranchResults.entrySet()) {
- GitBranch b = entry.getKey();
- if (!b.equals(branch)) {
- resultsByBranch.put(b, entry.getValue());
- }
- }
- return new GitPushRepoResult(myType, resultsByBranch, myOutput);
- }
-
- boolean isEmpty() {
- return myBranchResults.isEmpty();
+ int getNumberOfPushedCommits() {
+ return myCommits;
}
/**
- * Merges the given results to this result.
- * In the case of conflict (i.e. different results for a branch), current result is preferred over the previous one.
+ * Returns the branch we were pushing from, in the full-name format, e.g. {@code refs/heads/master}.
*/
- void mergeFrom(@NotNull GitPushRepoResult repoResult) {
- for (Map.Entry entry : repoResult.myBranchResults.entrySet()) {
- GitBranch branch = entry.getKey();
- GitPushBranchResult branchResult = entry.getValue();
- if (!myBranchResults.containsKey(branch)) { // otherwise current result is preferred
- myBranchResults.put(branch, branchResult);
- }
- }
+ @NotNull
+ String getSourceBranch() {
+ return mySourceBranch;
+ }
+
+ /**
+ * Returns the branch we were pushing to, in the full-name format, e.g. {@code refs/remotes/origin/master}.
+ */
+ @NotNull
+ String getTargetBranch() {
+ return myTargetBranch;
+ }
+
+ @Nullable
+ String getError() {
+ return myError;
}
@NotNull
- String getPerBranchesNonErrorReport() {
- StringBuilder sb = new StringBuilder();
- int i = 0;
- for (Map.Entry entry : myBranchResults.entrySet()) {
- GitBranch branch = entry.getKey();
- GitPushBranchResult branchResult = entry.getValue();
-
- if (branchResult.isSuccess()) {
- sb.append(bold(branch.getName()) + ": pushed " + commits(branchResult.getNumberOfPushedCommits()));
- }
- else if (branchResult.isNewBranch()) {
- sb.append(bold(branch.getName()) + " pushed to new branch " + bold(branchResult.getTargetBranchName()));
- }
- else {
- sb.append(code(branch.getName())).append(": rejected");
- }
-
- if (i < myBranchResults.size() - 1) {
- sb.append("
");
- }
+ private static Type convertType(@NotNull GitPushNativeResult.Type nativeType) {
+ switch (nativeType) {
+ case SUCCESS:
+ return Type.SUCCESS;
+ case FORCED_UPDATE:
+ return Type.FORCED;
+ case NEW_REF:
+ return Type.NEW_BRANCH;
+ case REJECTED:
+ return Type.REJECTED;
+ case UP_TO_DATE:
+ return Type.SUCCESS;
+ case ERROR:
+ return Type.ERROR;
+ case DELETED:
+ default:
+ throw new IllegalArgumentException("Conversion is not supported: " + nativeType);
}
- return sb.toString();
}
- @NotNull
- private static String commits(int commitNum) {
- return commitNum + " " + StringUtil.pluralize("commit", commitNum);
+ @Override
+ public String toString() {
+ return String.format("%s (%d, '%s'), update: %s}", myType, myCommits, mySourceBranch, myUpdateResult);
}
-
}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushResult.java b/plugins/git4idea/src/git4idea/push/GitPushResult.java
index 1d105eaea0f1..e4fc19a2bc33 100644
--- a/plugins/git4idea/src/git4idea/push/GitPushResult.java
+++ b/plugins/git4idea/src/git4idea/push/GitPushResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -16,367 +16,50 @@
package git4idea.push;
import com.intellij.history.Label;
-import com.intellij.history.LocalHistory;
-import com.intellij.ide.BrowserUtil;
-import com.intellij.notification.Notification;
-import com.intellij.notification.NotificationListener;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vcs.VcsException;
-import com.intellij.openapi.vcs.VcsNotifier;
-import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx;
-import com.intellij.openapi.vcs.update.ActionInfo;
-import com.intellij.openapi.vcs.update.UpdateInfoTree;
import com.intellij.openapi.vcs.update.UpdatedFiles;
-import git4idea.GitBranch;
-import git4idea.GitRevisionNumber;
-import git4idea.GitUtil;
-import git4idea.branch.GitBranchUtil;
-import git4idea.merge.MergeChangeCollector;
import git4idea.repo.GitRepository;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import javax.swing.event.HyperlinkEvent;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.Map;
+/**
+ * Combined push result for all affected repositories in the project.
+ */
class GitPushResult {
- private static final Logger LOG = Logger.getInstance(GitPushResult.class);
+ @NotNull private final Map myResults;
+ @NotNull private final UpdatedFiles myUpdatedFiles;
+ @Nullable private final Label myBeforeUpdateLabel;
+ @Nullable private final Label myAfterUpdateLabel;
- private final Project myProject;
- private final Map myResults = new HashMap();
- private final Map myUpdateStarts = new HashMap();
- private Label myBeforeUpdateLabel;
-
- GitPushResult(@NotNull Project project) {
- this(project, null, new HashMap());
- }
-
- private GitPushResult(@NotNull Project project, @Nullable Label beforeUpdateLabel, @NotNull Map updateStarts) {
- myProject = project;
- myBeforeUpdateLabel = beforeUpdateLabel;
- for (Map.Entry entry : updateStarts.entrySet()) {
- myUpdateStarts.put(entry.getKey(), entry.getValue());
- }
- }
-
- void append(@NotNull GitRepository repository, @NotNull GitPushRepoResult result) {
- myResults.put(repository, result);
- }
-
- /**
- * For the specified repositories remembers the revision when update process (actually, auto-update inside the push process) has started.
- */
- void markUpdateStartIfNotMarked(@NotNull Collection repositories) {
- if (myUpdateStarts.isEmpty()) {
- myBeforeUpdateLabel = LocalHistory.getInstance().putSystemLabel(myProject, "Before push");
- }
- for (GitRepository repository : repositories) {
- if (!myUpdateStarts.containsKey(repository)) {
- String currentRevision = repository.getCurrentRevision();
- if (currentRevision != null) {
- myUpdateStarts.put(repository, new GitRevisionNumber(currentRevision));
- }
- }
- }
- }
-
- boolean wasErrorCancelOrNotAuthorized() {
- for (GitPushRepoResult repoResult : myResults.values()) {
- if (repoResult.isOneOfErrors()) {
- return true;
- }
- }
- return false;
+ GitPushResult(@NotNull Map results,
+ @NotNull UpdatedFiles files,
+ @Nullable Label beforeUpdateLabel,
+ @Nullable Label afterUpdateLabel) {
+ myResults = results;
+ myUpdatedFiles = files;
+ myAfterUpdateLabel = beforeUpdateLabel;
+ myBeforeUpdateLabel = afterUpdateLabel;
}
@NotNull
- private GroupedResult group() {
- final Map successfulResults = new HashMap();
- final Map errorResults = new HashMap();
- final Map rejectedResults = new HashMap();
-
- for (Map.Entry entry : myResults.entrySet()) {
- GitRepository repository = entry.getKey();
- GitPushRepoResult repoResult = entry.getValue();
- switch (repoResult.getType()) {
- case SUCCESS:
- successfulResults.put(repository, repoResult);
- break;
- case ERROR:
- case CANCEL:
- case NOT_AUTHORIZED:
- errorResults.put(repository, repoResult);
- break;
- case NOT_PUSHING:
- break;
- case SOME_REJECTED:
- rejectedResults.put(repository, repoResult);
- break;
- }
- }
- return new GroupedResult(successfulResults, errorResults, rejectedResults);
- }
-
- boolean isEmpty() {
- return myResults.isEmpty();
+ public Map getResults() {
+ return myResults;
}
@NotNull
- GitPushResult remove(@NotNull Map repoBranchPairs) {
- GitPushResult result = new GitPushResult(myProject, myBeforeUpdateLabel, myUpdateStarts);
- for (Map.Entry entry : myResults.entrySet()) {
- GitRepository repository = entry.getKey();
- GitPushRepoResult repoResult = entry.getValue();
- if (repoBranchPairs.containsKey(repository)) {
- GitPushRepoResult adjustedResult = repoResult.remove(repoBranchPairs.get(repository));
- if (!repoResult.isEmpty()) {
- result.append(repository, adjustedResult);
- }
- } else {
- result.append(repository, repoResult);
- }
- }
- return result;
+ public UpdatedFiles getUpdatedFiles() {
+ return myUpdatedFiles;
}
- /**
- * Merges the given results to this result.
- * In the case of conflict (i.e. different results for a repository-branch pair), current result is preferred over the previous one.
- */
- void mergeFrom(@Nullable GitPushResult previousResult) {
- if (previousResult == null) {
- return;
- }
-
- for (Map.Entry entry : previousResult.myResults.entrySet()) {
- GitRepository repository = entry.getKey();
- GitPushRepoResult repoResult = entry.getValue();
- if (myResults.containsKey(repository)) {
- myResults.get(repository).mergeFrom(previousResult.myResults.get(repository));
- } else {
- append(repository, repoResult);
- }
- }
-
- for (Map.Entry entry : previousResult.myUpdateStarts.entrySet()) {
- myUpdateStarts.put(entry.getKey(), entry.getValue());
- }
- myBeforeUpdateLabel = previousResult.myBeforeUpdateLabel;
+ @Nullable
+ public Label getBeforeUpdateLabel() {
+ return myBeforeUpdateLabel;
}
- @NotNull
- Map getRejectedPushesFromCurrentBranchToTrackedBranch(GitPushInfo pushInfo) {
- final Map rejectedPushesForCurrentBranch = new HashMap();
- for (Map.Entry entry : group().myRejectedResults.entrySet()) {
- GitRepository repository = entry.getKey();
- GitBranch currentBranch = repository.getCurrentBranch();
- if (currentBranch == null) {
- continue;
- }
- GitPushRepoResult repoResult = entry.getValue();
- GitPushBranchResult curBranchResult = repoResult.getBranchResults().get(currentBranch);
-
- if (curBranchResult == null) {
- continue;
- }
-
- String trackedBranchName;
- try {
- String simpleName = GitBranchUtil.getTrackedBranchName(myProject, repository.getRoot(), currentBranch.getName());
- if (simpleName == null) {
- continue;
- }
- if (simpleName.startsWith(GitBranch.REFS_HEADS_PREFIX)) {
- simpleName = simpleName.substring(GitBranch.REFS_HEADS_PREFIX.length());
- }
- String remote = GitBranchUtil.getTrackedRemoteName(myProject, repository.getRoot(), currentBranch.getName());
- if (remote == null) {
- continue;
- }
- trackedBranchName = remote + "/" + simpleName;
- }
- catch (VcsException e) {
- LOG.info("Couldn't get tracked branch for branch " + currentBranch, e);
- continue;
- }
- if (!pushInfo.getPushSpecs().get(repository).getDest().getName().equals(trackedBranchName)) {
- // push from current branch was rejected, but it was a push not to the tracked branch => ignore
- continue;
- }
- if (curBranchResult.isRejected()) {
- rejectedPushesForCurrentBranch.put(repository, currentBranch);
- }
- }
- return rejectedPushesForCurrentBranch;
- }
-
- /**
- * Constructs the HTML-formatted message from error outputs of failed repositories and notify.
- * If there is only 1 repository in the project, just returns the error without writing the repository url (to avoid confusion for people
- * with only 1 root ever).
- * Otherwise adds repository URL to the error that repository produced.
- *
- * The procedure also includes collecting for updated files (if an auto-update was performed during the push), which may be lengthy.
- */
- void createPushNotificationAndNotify() {
- final UpdatedFiles updatedFiles = collectUpdatedFiles();
-
- GroupedResult groupedResult = group();
-
- boolean error = !groupedResult.myErrorResults.isEmpty();
- boolean rejected = !groupedResult.myRejectedResults.isEmpty();
- boolean success = !groupedResult.mySuccessfulResults.isEmpty();
-
- boolean onlyError = error && !rejected && !success;
- boolean onlyRejected = rejected && !error && !success;
- final boolean onlySuccess = success && !rejected && !error;
-
- int pushedCommitsNumber = calcPushedCommitTotalNumber(myResults);
-
- String title;
- if (error) {
- if (onlyError) {
- title = "Push failed";
- } else {
- title = "Push partially failed";
- if (success) {
- title += ", " + commits(pushedCommitsNumber) + " pushed";
- }
- }
- } else if (rejected) {
- if (onlyRejected) {
- title = "Push rejected";
- } else {
- title = "Push partially rejected, " + commits(pushedCommitsNumber) + " pushed";
- }
- } else {
- title = "Push successful";
- }
-
- String errorReport = reportForGroup(groupedResult.myErrorResults, GroupedResult.Type.ERROR);
- String successReport = reportForGroup(groupedResult.mySuccessfulResults, GroupedResult.Type.SUCCESS);
- String rejectedReport = reportForGroup(groupedResult.myRejectedResults, GroupedResult.Type.REJECT);
-
- StringBuilder sb = new StringBuilder();
- sb.append(errorReport);
- sb.append(rejectedReport);
- sb.append(successReport);
-
- if (!updatedFiles.isEmpty()) {
- sb.append("View files updated during the push");
- }
-
- NotificationListener viewUpdateFilesListener = new ViewUpdatedFilesNotificationListener(updatedFiles);
- VcsNotifier vcsNotifier = VcsNotifier.getInstance(myProject);
-
- if (onlySuccess) {
- vcsNotifier.notifySuccess(title, sb.toString(), viewUpdateFilesListener);
- }
- else if (error) {
- vcsNotifier.notifyError(title, sb.toString(), viewUpdateFilesListener);
- }
- else {
- vcsNotifier.notifyImportantWarning(title, sb.toString(), viewUpdateFilesListener);
- }
- }
-
- @NotNull
- private UpdatedFiles collectUpdatedFiles() {
- UpdatedFiles updatedFiles = UpdatedFiles.create();
- for (Map.Entry updatedRepository : myUpdateStarts.entrySet()) {
- GitRepository repository = updatedRepository.getKey();
- final MergeChangeCollector collector = new MergeChangeCollector(myProject, repository.getRoot(), updatedRepository.getValue());
- final ArrayList exceptions = new ArrayList();
- collector.collect(updatedFiles, exceptions);
- for (VcsException exception : exceptions) {
- LOG.info(exception);
- }
- }
- return updatedFiles;
- }
-
- private static int calcPushedCommitTotalNumber(@NotNull Map successfulResults) {
- int sum = 0;
- for (GitPushRepoResult pushRepoResult : successfulResults.values()) {
- for (GitPushBranchResult branchResult : pushRepoResult.getBranchResults().values()) {
- sum += branchResult.getNumberOfPushedCommits();
- }
- }
- return sum;
- }
-
- @NotNull
- private String reportForGroup(@NotNull Map groupResult, @NotNull GroupedResult.Type resultType) {
- StringBuilder sb = new StringBuilder();
- for (Map.Entry entry : groupResult.entrySet()) {
- GitRepository repository = entry.getKey();
- GitPushRepoResult result = entry.getValue();
-
- if (!GitUtil.justOneGitRepository(myProject)) {
- sb.append("" + repository.getPresentableUrl() + ":
");
- }
- if (resultType == GroupedResult.Type.SUCCESS || resultType == GroupedResult.Type.REJECT) {
- sb.append(result.getPerBranchesNonErrorReport());
- } else {
- sb.append(result.getOutput());
- }
- sb.append("
");
- }
- return sb.toString();
- }
-
-
- private static class GroupedResult {
- enum Type {
- SUCCESS,
- REJECT,
- ERROR
- }
-
- private final Map mySuccessfulResults;
- private final Map myErrorResults;
- private final Map myRejectedResults;
-
- GroupedResult(@NotNull Map successfulResults,
- @NotNull Map errorResults,
- @NotNull Map rejectedResults) {
- mySuccessfulResults = successfulResults;
- myErrorResults = errorResults;
- myRejectedResults = rejectedResults;
- }
- }
-
- @NotNull
- private static String commits(int commitNum) {
- return commitNum + " " + StringUtil.pluralize("commit", commitNum);
- }
-
- private class ViewUpdatedFilesNotificationListener implements NotificationListener {
- private final UpdatedFiles myUpdatedFiles;
-
- public ViewUpdatedFilesNotificationListener(UpdatedFiles updatedFiles) {
- myUpdatedFiles = updatedFiles;
- }
-
- @Override
- public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
- if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
- if (event.getDescription().equals("UpdatedFiles")) {
- ProjectLevelVcsManagerEx vcsManager = ProjectLevelVcsManagerEx.getInstanceEx(myProject);
- UpdateInfoTree tree = vcsManager.showUpdateProjectInfo(myUpdatedFiles, "Update", ActionInfo.UPDATE, false);
- tree.setBefore(myBeforeUpdateLabel);
- tree.setAfter(LocalHistory.getInstance().putSystemLabel(myProject, "After push"));
- }
- else {
- BrowserUtil.browse(event.getDescription());
- }
- }
- }
+ @Nullable
+ public Label getAfterUpdateLabel() {
+ return myAfterUpdateLabel;
}
}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushResultNotification.java b/plugins/git4idea/src/git4idea/push/GitPushResultNotification.java
new file mode 100644
index 000000000000..2637041926b4
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/push/GitPushResultNotification.java
@@ -0,0 +1,219 @@
+/*
+ * 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 git4idea.push;
+
+import com.intellij.dvcs.DvcsUtil;
+import com.intellij.dvcs.repo.RepositoryUtil;
+import com.intellij.history.Label;
+import com.intellij.ide.BrowserUtil;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationListener;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.VcsNotifier;
+import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx;
+import com.intellij.openapi.vcs.update.ActionInfo;
+import com.intellij.openapi.vcs.update.UpdateInfoTree;
+import com.intellij.openapi.vcs.update.UpdatedFiles;
+import com.intellij.util.Function;
+import com.intellij.util.containers.ContainerUtil;
+import git4idea.branch.GitBranchUtil;
+import git4idea.repo.GitRepository;
+import git4idea.update.GitUpdateResult;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.event.HyperlinkEvent;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+class GitPushResultNotification extends Notification {
+
+ public static final String VIEW_FILES_UPDATED_DURING_THE_PUSH = "View files updated during the push";
+
+ public static final String UPDATE_WITH_RESOLVED_CONFLICTS = "push has been cancelled, because there were conflicts during update.
" +
+ "Check that conflicts were resolved correctly, and invoke push again.";
+ public static final String INCOMPLETE_UPDATE = "push has been cancelled, because not all conflicts were resolved during update.
" +
+ "Resolve the conflicts and invoke push again.";
+ public static final String UPDATE_WITH_ERRORS = "push was rejected, and update failed with error";
+
+ private static final Logger LOG = Logger.getInstance(GitPushResultNotification.class);
+
+ public GitPushResultNotification(@NotNull String groupDisplayId,
+ @NotNull String title,
+ @NotNull String content,
+ @NotNull NotificationType type,
+ @Nullable NotificationListener listener) {
+ super(groupDisplayId, title, content, type, listener);
+ }
+
+ static GitPushResultNotification create(Project project, GitPushResult pushResult, boolean multiRepoProject) {
+ GroupedPushResult grouped = GroupedPushResult.group(pushResult.getResults());
+
+ String title;
+ NotificationType type;
+ if (!grouped.errors.isEmpty()) {
+ if (!grouped.successful.isEmpty()) {
+ title = "Push partially failed";
+ }
+ else {
+ title = "Push failed";
+ }
+ type = NotificationType.ERROR;
+ }
+ else if (!grouped.rejected.isEmpty()) {
+ if (!grouped.successful.isEmpty()) {
+ title = "Push partially rejected";
+ }
+ else {
+ title = "Push rejected";
+ }
+ type = NotificationType.WARNING;
+ }
+ else {
+ title = "Push successful";
+ type = NotificationType.INFORMATION;
+ }
+
+ String description = formDescription(pushResult.getResults(), multiRepoProject);
+
+ ViewUpdatedFilesNotificationListener listener = null;
+ UpdatedFiles updatedFiles = pushResult.getUpdatedFiles();
+ if (!updatedFiles.isEmpty()) {
+ description += "
" + VIEW_FILES_UPDATED_DURING_THE_PUSH;
+ listener = new ViewUpdatedFilesNotificationListener(project, updatedFiles,
+ pushResult.getBeforeUpdateLabel(), pushResult.getAfterUpdateLabel());
+ }
+
+ NotificationGroup group = type == NotificationType.INFORMATION ?
+ VcsNotifier.NOTIFICATION_GROUP_ID :
+ VcsNotifier.IMPORTANT_ERROR_NOTIFICATION;
+
+ return new GitPushResultNotification(group.getDisplayId(), title, description, type, listener);
+ }
+
+ private static String formDescription(Map results, final boolean multiRepoProject) {
+ List> entries = ContainerUtil.sorted(results.entrySet(),
+ new Comparator>() {
+ @Override
+ public int compare(Map.Entry o1, Map.Entry o2) {
+ // successful first
+ int compareResultTypes = GitPushRepoResult.TYPE_COMPARATOR.compare(o1.getValue().getType(), o2.getValue().getType());
+ if (compareResultTypes != 0) {
+ return compareResultTypes;
+ }
+ return RepositoryUtil.REPOSITORY_COMPARATOR.compare(o1.getKey(), o2.getKey());
+ }
+ });
+
+ return StringUtil.join(entries, new Function, String>() {
+ @Override
+ public String fun(Map.Entry entry) {
+ GitRepository repository = entry.getKey();
+ GitPushRepoResult result = entry.getValue();
+
+ String description = formRepoDescription(result);
+ if (!multiRepoProject) {
+ description = StringUtil.capitalize(description);
+ }
+ else {
+ description = DvcsUtil.getShortRepositoryName(repository) + ": " + description;
+ }
+ return description;
+ }
+ }, "
");
+ }
+
+ private static String formRepoDescription(GitPushRepoResult result) {
+ String description;
+ String sourceBranch = GitBranchUtil.stripRefsPrefix(result.getSourceBranch());
+ String targetBranch = GitBranchUtil.stripRefsPrefix(result.getTargetBranch());
+ switch (result.getType()) {
+ case SUCCESS:
+ int commits = result.getNumberOfPushedCommits();
+ description = String.format("pushed %d %s to %s", commits, StringUtil.pluralize("commit", commits), targetBranch);
+ break;
+ case NEW_BRANCH:
+ description = String.format("pushed %s to new branch %s", sourceBranch, targetBranch);
+ break;
+ case FORCED:
+ description = String.format("force pushed %s to %s", sourceBranch, targetBranch);
+ break;
+ case REJECTED:
+ description = formDescriptionBasedOnUpdateResult(result.getUpdateResult(), targetBranch);
+ break;
+ case ERROR:
+ description = String.format("failed with error: " + result.getError());
+ break;
+ default:
+ LOG.error("Unexpected push result: " + result);
+ description = "";
+ break;
+ }
+ return description;
+ }
+
+ private static String formDescriptionBasedOnUpdateResult(GitUpdateResult updateResult, String targetBranch) {
+ if (updateResult == null || updateResult == GitUpdateResult.SUCCESS || updateResult == GitUpdateResult.NOTHING_TO_UPDATE) {
+ return String.format("push to %s was rejected", targetBranch);
+ }
+ else if (updateResult == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS) {
+ return UPDATE_WITH_RESOLVED_CONFLICTS;
+ }
+ else if (updateResult == GitUpdateResult.INCOMPLETE) {
+ return INCOMPLETE_UPDATE;
+ }
+ else {
+ return UPDATE_WITH_ERRORS;
+ }
+ }
+
+ private static class ViewUpdatedFilesNotificationListener implements NotificationListener {
+
+ private final Project myProject;
+ private final UpdatedFiles myUpdatedFiles;
+ private final Label myBeforeUpdateLabel;
+ private final Label myAfterUpdateLabel;
+
+ public ViewUpdatedFilesNotificationListener(@NotNull Project project, @NotNull UpdatedFiles updatedFiles, Label label, Label label1) {
+ myProject = project;
+ myUpdatedFiles = updatedFiles;
+ myBeforeUpdateLabel = label;
+ myAfterUpdateLabel = label1;
+ }
+
+ @Override
+ public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
+ if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
+ if (event.getDescription().equals("UpdatedFiles")) {
+ ProjectLevelVcsManagerEx vcsManager = ProjectLevelVcsManagerEx.getInstanceEx(myProject);
+ UpdateInfoTree tree = vcsManager.showUpdateProjectInfo(myUpdatedFiles, "Update", ActionInfo.UPDATE, false);
+ tree.setBefore(myBeforeUpdateLabel);
+ tree.setAfter(myAfterUpdateLabel);
+ }
+ else {
+ BrowserUtil.browse(event.getDescription());
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushSpec.java b/plugins/git4idea/src/git4idea/push/GitPushSource.java
similarity index 52%
rename from plugins/git4idea/src/git4idea/push/GitPushSpec.java
rename to plugins/git4idea/src/git4idea/push/GitPushSource.java
index 11beefbd3043..146c854564a8 100644
--- a/plugins/git4idea/src/git4idea/push/GitPushSpec.java
+++ b/plugins/git4idea/src/git4idea/push/GitPushSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -15,41 +15,26 @@
*/
package git4idea.push;
+import com.intellij.dvcs.push.PushSource;
import git4idea.GitLocalBranch;
-import git4idea.GitRemoteBranch;
-import git4idea.repo.GitRemote;
import org.jetbrains.annotations.NotNull;
-/**
- * @author Kirill Likhodedov
- */
-public class GitPushSpec {
+class GitPushSource implements PushSource {
- @NotNull private final GitLocalBranch mySource;
- @NotNull private final GitRemoteBranch myDest;
+ @NotNull private final GitLocalBranch myBranch;
- GitPushSpec(@NotNull GitLocalBranch source, @NotNull GitRemoteBranch dest) {
- myDest = dest;
- mySource = source;
+ GitPushSource(@NotNull GitLocalBranch branch) {
+ myBranch = branch;
}
@NotNull
- public GitRemote getRemote() {
- return myDest.getRemote();
- }
-
- @NotNull
- public GitLocalBranch getSource() {
- return mySource;
- }
-
- @NotNull
- public GitRemoteBranch getDest() {
- return myDest;
- }
-
@Override
- public String toString() {
- return mySource + "->" + myDest;
+ public String getPresentation() {
+ return myBranch.getName();
+ }
+
+ @NotNull
+ public GitLocalBranch getBranch() {
+ return myBranch;
}
}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushSupport.java b/plugins/git4idea/src/git4idea/push/GitPushSupport.java
new file mode 100644
index 000000000000..8c948af7635f
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/push/GitPushSupport.java
@@ -0,0 +1,134 @@
+/*
+ * 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 git4idea.push;
+
+import com.intellij.dvcs.push.OutgoingCommitsProvider;
+import com.intellij.dvcs.push.PushSupport;
+import com.intellij.dvcs.push.PushTargetPanel;
+import com.intellij.dvcs.push.Pusher;
+import com.intellij.dvcs.repo.RepositoryManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.AbstractVcs;
+import com.intellij.util.ObjectUtils;
+import git4idea.*;
+import git4idea.branch.GitBranchUtil;
+import git4idea.repo.GitBranchTrackInfo;
+import git4idea.repo.GitRemote;
+import git4idea.repo.GitRepository;
+import git4idea.repo.GitRepositoryManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+public class GitPushSupport extends PushSupport {
+
+ private static final Logger LOG = Logger.getInstance(GitPushSupport.class);
+
+ @NotNull private final GitRepositoryManager myRepositoryManager;
+ @NotNull private final GitVcs myVcs;
+ @NotNull private final Pusher myPusher;
+ @NotNull private final OutgoingCommitsProvider myOutgoingCommitsProvider;
+
+ // instantiated from plugin.xml
+ @SuppressWarnings("UnusedDeclaration")
+ private GitPushSupport(@NotNull Project project, @NotNull GitRepositoryManager repositoryManager) {
+ myRepositoryManager = repositoryManager;
+ myVcs = ObjectUtils.assertNotNull(GitVcs.getInstance(project));
+ myPusher = new GitPusher(project);
+ myOutgoingCommitsProvider = new GitOutgoingCommitsProvider(project);
+ }
+
+ @NotNull
+ @Override
+ public AbstractVcs getVcs() {
+ return myVcs;
+ }
+
+ @NotNull
+ @Override
+ public Pusher getPusher() {
+ return myPusher;
+ }
+
+ @NotNull
+ @Override
+ public OutgoingCommitsProvider getOutgoingCommitsProvider() {
+ return myOutgoingCommitsProvider;
+ }
+
+ @Nullable
+ @Override
+ public GitPushTarget getDefaultTarget(@NotNull GitRepository repository) {
+ GitLocalBranch currentBranch = repository.getCurrentBranch();
+ if (currentBranch == null) {
+ return null;
+ }
+ GitBranchTrackInfo trackInfo = GitBranchUtil.getTrackInfoForBranch(repository, currentBranch);
+ if (trackInfo != null) {
+ return new GitPushTarget(trackInfo.getRemoteBranch(), false);
+ }
+ return proposeTargetForNewBranch(repository, currentBranch);
+ }
+
+ private static GitPushTarget proposeTargetForNewBranch(GitRepository repository, GitLocalBranch currentBranch) {
+ Collection remotes = repository.getRemotes();
+ if (remotes.isEmpty()) {
+ return null; // TODO need to propose to declare new remote
+ }
+ else if (remotes.size() == 1) {
+ return makeTargetForNewBranch(repository, remotes.iterator().next(), currentBranch);
+ }
+ else {
+ GitRemote remote = GitUtil.getDefaultRemote(remotes);
+ if (remote == null) {
+ remote = remotes.iterator().next();
+ }
+ return makeTargetForNewBranch(repository, remote, currentBranch);
+ }
+ }
+
+ @NotNull
+ private static GitPushTarget makeTargetForNewBranch(@NotNull GitRepository repository,
+ @NotNull GitRemote remote,
+ @NotNull GitLocalBranch currentBranch) {
+ GitRemoteBranch existingRemoteBranch = GitPushTarget.findRemoteBranch(repository, remote, currentBranch.getName());
+ if (existingRemoteBranch != null) {
+ return new GitPushTarget(existingRemoteBranch, false);
+ }
+ return new GitPushTarget(new GitStandardRemoteBranch(remote, currentBranch.getName(), GitBranch.DUMMY_HASH), true);
+ }
+
+ @NotNull
+ @Override
+ public GitPushSource getSource(@NotNull GitRepository repository) {
+ return new GitPushSource(repository.getCurrentBranch()); // TODO assert: detached head => not possible to push
+ }
+
+ @NotNull
+ @Override
+ public RepositoryManager getRepositoryManager() {
+ return myRepositoryManager;
+ }
+
+ @NotNull
+ @Override
+ public PushTargetPanel createTargetPanel(@NotNull GitRepository repository, @Nullable GitPushTarget defaultTarget) {
+ return new GitPushTargetPanel(repository, defaultTarget);
+ }
+
+}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushTarget.java b/plugins/git4idea/src/git4idea/push/GitPushTarget.java
new file mode 100644
index 000000000000..084eabbad826
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/push/GitPushTarget.java
@@ -0,0 +1,99 @@
+/*
+ * 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 git4idea.push;
+
+import com.intellij.dvcs.push.PushTarget;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Condition;
+import com.intellij.util.containers.ContainerUtil;
+import git4idea.GitBranch;
+import git4idea.GitRemoteBranch;
+import git4idea.GitStandardRemoteBranch;
+import git4idea.branch.GitBranchUtil;
+import git4idea.repo.GitRemote;
+import git4idea.repo.GitRepository;
+import git4idea.validators.GitRefNameValidator;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.text.ParseException;
+import java.util.Collection;
+
+class GitPushTarget implements PushTarget {
+
+ private static final Logger LOG = Logger.getInstance(GitPushTarget.class);
+
+ @NotNull private final GitRemoteBranch myRemoteBranch;
+ private final boolean myIsNewBranchCreated;
+
+ GitPushTarget(@NotNull GitRemoteBranch remoteBranch, boolean isNewBranchCreated) {
+ myRemoteBranch = remoteBranch;
+ myIsNewBranchCreated = isNewBranchCreated;
+ }
+
+ @NotNull
+ GitRemoteBranch getBranch() {
+ return myRemoteBranch;
+ }
+
+ boolean isNewBranchCreated() {
+ return myIsNewBranchCreated;
+ }
+
+ @NotNull
+ static GitPushTarget parse(@NotNull GitRepository repository, @NotNull String remoteName, @NotNull String branchName) throws
+ ParseException {
+ if (!GitRefNameValidator.getInstance().checkInput(branchName)) {
+ throw new ParseException("Invalid destination branch name: " + branchName, -1);
+ }
+
+ GitRemote remote = findRemote(repository.getRemotes(), remoteName);
+ if (remote == null) {
+ LOG.error("Remote [" + remoteName + "] is not found among " + repository.getRemotes());
+ throw new ParseException("Invalid remote: " + remoteName, -1);
+ }
+
+ GitRemoteBranch existingRemoteBranch = findRemoteBranch(repository, remote, branchName);
+ if (existingRemoteBranch != null) {
+ return new GitPushTarget(existingRemoteBranch, false);
+ }
+ GitRemoteBranch rb = new GitStandardRemoteBranch(remote, branchName, GitBranch.DUMMY_HASH);
+ return new GitPushTarget(rb, true);
+ }
+
+ @Nullable
+ private static GitRemote findRemote(@NotNull Collection remotes, @NotNull final String candidate) {
+ return ContainerUtil.find(remotes, new Condition() {
+ @Override
+ public boolean value(GitRemote remote) {
+ return remote.getName().equals(candidate);
+ }
+ });
+ }
+
+ @Nullable
+ static GitRemoteBranch findRemoteBranch(@NotNull GitRepository repository, @NotNull final GitRemote remote,
+ @NotNull final String nameAtRemote) {
+ return ContainerUtil.find(repository.getBranches().getRemoteBranches(), new Condition() {
+ @Override
+ public boolean value(GitRemoteBranch remoteBranch) {
+ return remoteBranch.getRemote().equals(remote) &&
+ remoteBranch.getNameForRemoteOperations().equals(GitBranchUtil.stripRefsPrefix(nameAtRemote));
+ }
+ });
+ }
+
+}
diff --git a/plugins/git4idea/src/git4idea/push/GitPushTargetPanel.java b/plugins/git4idea/src/git4idea/push/GitPushTargetPanel.java
new file mode 100644
index 000000000000..aa8ce0b54287
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/push/GitPushTargetPanel.java
@@ -0,0 +1,205 @@
+/*
+ * 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 git4idea.push;
+
+import com.intellij.dvcs.push.PushTargetPanel;
+import com.intellij.dvcs.push.ui.PushTargetTextField;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.ListPopup;
+import com.intellij.openapi.ui.popup.PopupStep;
+import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.ClickListener;
+import com.intellij.ui.ColoredTreeCellRenderer;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.ui.awt.RelativePoint;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.util.Function;
+import com.intellij.util.ObjectUtils;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.UIUtil;
+import git4idea.GitRemoteBranch;
+import git4idea.repo.GitRemote;
+import git4idea.repo.GitRepository;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.text.ParseException;
+import java.util.Comparator;
+import java.util.List;
+
+class GitPushTargetPanel extends PushTargetPanel {
+
+ private static final Logger LOG = Logger.getInstance(GitPushTargetPanel.class);
+
+ private static final Comparator REMOTE_BRANCH_COMPARATOR = new MyRemoteBranchComparator();
+ public static final String SEPARATOR = " \u25BE ";
+
+ private final GitRepository myRepository;
+ private final PushTargetTextField myTargetTextField;
+ private final JLabel myRemoteLabel;
+
+ @Nullable private GitPushTarget myCurrentTarget;
+
+ public GitPushTargetPanel(@NotNull GitRepository repository, @Nullable GitPushTarget defaultTarget) {
+ myRepository = repository;
+
+ myCurrentTarget = defaultTarget;
+
+ String initialBranch;
+ String initialRemote;
+ if (defaultTarget == null) {
+ // TODO
+ initialBranch = "";
+ initialRemote = "No remotes";
+ }
+ else {
+ initialBranch = getTextFieldText(defaultTarget);
+ initialRemote = getRemoteLabelText(defaultTarget.getBranch().getRemote().getName());
+ }
+
+ myTargetTextField = new PushTargetTextField(repository.getProject(), getTargetNames(myRepository), initialBranch);
+
+ myRemoteLabel = new JBLabel(initialRemote);
+ new ClickListener() {
+ @Override
+ public boolean onClick(@NotNull MouseEvent event, int clickCount) {
+ showRemoteSelector();
+ return true;
+ }
+ }.installOn(myRemoteLabel);
+
+ setLayout(new BorderLayout());
+ setOpaque(false);
+ add(myRemoteLabel, BorderLayout.WEST);
+ add(myTargetTextField, BorderLayout.CENTER);
+ }
+
+ private void showRemoteSelector() {
+ final List remotes = ContainerUtil.map(myRepository.getRemotes(), new Function() {
+ @Override
+ public String fun(GitRemote remote) {
+ return remote.getName();
+ }
+ });
+
+ ListPopup popup = JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep(null, remotes) {
+ @Override
+ public PopupStep onChosen(String selectedValue, boolean finalChoice) {
+ myRemoteLabel.setText(getRemoteLabelText(selectedValue));
+ return super.onChosen(selectedValue, finalChoice);
+ }
+ });
+ // underneath, but so that list popup items are aligned with the label
+ popup.show(new RelativePoint(myRemoteLabel, new Point(-UIUtil.getListCellHPadding(), myRemoteLabel.getHeight())));
+ }
+
+ @Override
+ public void render(@NotNull ColoredTreeCellRenderer renderer) {
+ String targetName = myTargetTextField.getText();
+ if (StringUtil.isEmptyOrSpaces(targetName)) {
+ renderer.append("Specify remote branch to push", SimpleTextAttributes.ERROR_ATTRIBUTES, this);
+ }
+ else {
+ GitPushTarget target = getValue();
+ renderer.append(myRemoteLabel.getText(), SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, this);
+ if (target.isNewBranchCreated()) {
+ renderer.append("+", SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, this);
+ }
+ renderer.append(target.getBranch().getNameForRemoteOperations() + SEPARATOR, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, this);
+ }
+ }
+
+ @NotNull
+ @Override
+ public GitPushTarget getValue() {
+ return ObjectUtils.assertNotNull(myCurrentTarget);
+ }
+
+ @NotNull
+ private static String getTextFieldText(@Nullable GitPushTarget target) {
+ return (target != null ? target.getBranch().getNameForRemoteOperations() : "");
+ }
+
+ private static String getRemoteLabelText(@NotNull String selectedValue) {
+ return selectedValue + SEPARATOR;
+ }
+
+ @Override
+ public void fireOnCancel() {
+ myTargetTextField.setText(getTextFieldText(myCurrentTarget));
+ }
+
+ @Override
+ public void fireOnChange() {
+ String remoteName = myRemoteLabel.getText().replace(SEPARATOR, "");
+ String branchName = myTargetTextField.getText();
+ try {
+ myCurrentTarget = GitPushTarget.parse(myRepository, remoteName, branchName);
+ }
+ catch (ParseException e) {
+ LOG.error("Invalid remote name shouldn't be allowed. [" + remoteName + ", " + branchName + "]", e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ValidationInfo verify() {
+ try {
+ String remoteLabel = myRemoteLabel.getText().replace(SEPARATOR, "");
+ GitPushTarget.parse(myRepository, remoteLabel, myTargetTextField.getText());
+ return null;
+ }
+ catch (ParseException e) {
+ return new ValidationInfo(e.getMessage(), myTargetTextField);
+ }
+ }
+
+ @NotNull
+ public static List getTargetNames(@NotNull GitRepository repository) {
+ List remoteBranches = ContainerUtil.sorted(repository.getBranches().getRemoteBranches(), REMOTE_BRANCH_COMPARATOR);
+ return ContainerUtil.map(remoteBranches, new Function() {
+ @Override
+ public String fun(GitRemoteBranch branch) {
+ return branch.getNameForRemoteOperations();
+ }
+ });
+ }
+
+ private static class MyRemoteBranchComparator implements Comparator {
+ @Override
+ public int compare(GitRemoteBranch o1, GitRemoteBranch o2) {
+ String remoteName1 = o1.getRemote().getName();
+ String remoteName2 = o2.getRemote().getName();
+ int remoteComparison = remoteName1.compareTo(remoteName2);
+ if (remoteComparison != 0) {
+ if (remoteName1.equals(GitRemote.ORIGIN_NAME)) {
+ return -1;
+ }
+ if (remoteName2.equals(GitRemote.ORIGIN_NAME)) {
+ return 1;
+ }
+ return remoteComparison;
+ }
+ return o1.getNameForLocalOperations().compareTo(o2.getNameForLocalOperations());
+ }
+ }
+}
diff --git a/plugins/git4idea/src/git4idea/push/GitPusher.java b/plugins/git4idea/src/git4idea/push/GitPusher.java
index 1dfcdfaf327d..06e613715dec 100644
--- a/plugins/git4idea/src/git4idea/push/GitPusher.java
+++ b/plugins/git4idea/src/git4idea/push/GitPusher.java
@@ -15,549 +15,33 @@
*/
package git4idea.push;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.progress.Task;
+import com.intellij.dvcs.push.PushSpec;
+import com.intellij.dvcs.push.Pusher;
+import com.intellij.dvcs.push.VcsPushOptionValue;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.DialogWrapper;
-import com.intellij.openapi.vcs.VcsException;
-import com.intellij.openapi.vcs.VcsNotifier;
-import com.intellij.openapi.vcs.update.UpdatedFiles;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.ui.UIUtil;
-import git4idea.*;
-import git4idea.branch.GitBranchPair;
-import git4idea.commands.Git;
-import git4idea.commands.GitCommandResult;
-import git4idea.commands.GitLineHandlerListener;
-import git4idea.commands.GitStandardProgressAnalyzer;
-import git4idea.config.GitConfigUtil;
-import git4idea.config.GitVcsSettings;
-import git4idea.config.UpdateMethod;
-import git4idea.history.GitHistoryUtils;
-import git4idea.repo.GitBranchTrackInfo;
-import git4idea.repo.GitRemote;
+import git4idea.GitUtil;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
-import git4idea.settings.GitPushSettings;
-import git4idea.update.GitUpdateProcess;
-import git4idea.update.GitUpdateResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.Map;
-/**
- * Collects information to push and performs the push.
- */
-public final class GitPusher {
-
- /**
- * if diff-log is not available (new branch is created, for example), we show a few recent commits made on the branch
- */
- static final int RECENT_COMMITS_NUMBER = 5;
-
- @Deprecated
- static final GitRemoteBranch NO_TARGET_BRANCH = new GitStandardRemoteBranch(GitRemote.DOT, "", GitBranch.DUMMY_HASH);
-
- private static final Logger LOG = Logger.getInstance(GitPusher.class);
- private static final String INDICATOR_TEXT = "Pushing";
- private static final int MAX_PUSH_ATTEMPTS = 10;
+class GitPusher extends Pusher {
@NotNull private final Project myProject;
@NotNull private final GitRepositoryManager myRepositoryManager;
- @NotNull private final ProgressIndicator myProgressIndicator;
- @NotNull private final Collection myRepositories;
- @NotNull private final GitVcsSettings mySettings;
- @NotNull private final GitPushSettings myPushSettings;
- @NotNull private final Git myGit;
- @NotNull private final GitPlatformFacade myPlatformFacade;
- public static void showPushDialogAndPerformPush(@NotNull Project project, @NotNull GitPlatformFacade facade) {
- final GitPushDialog dialog = new GitPushDialog(project);
- dialog.show();
- if (dialog.isOK()) {
- runPushInBackground(project, facade, dialog);
- }
- }
-
- private static void runPushInBackground(@NotNull final Project project, @NotNull final GitPlatformFacade facade,
- @NotNull final GitPushDialog dialog) {
- Task.Backgroundable task = new Task.Backgroundable(project, INDICATOR_TEXT, false) {
- @Override
- public void run(@NotNull ProgressIndicator indicator) {
- new GitPusher(project, facade, indicator).push(dialog.getPushInfo());
- }
- };
- GitVcs.runInBackground(task);
- }
-
- // holds settings chosen in GitRejectedPushUpdate dialog to reuse if the next push is rejected again.
- static class UpdateSettings {
- private final boolean myUpdateAllRoots;
- private final UpdateMethod myUpdateMethod;
-
- private UpdateSettings(boolean updateAllRoots, UpdateMethod updateMethod) {
- myUpdateAllRoots = updateAllRoots;
- myUpdateMethod = updateMethod;
- }
-
- public boolean shouldUpdateAllRoots() {
- return myUpdateAllRoots;
- }
-
- public UpdateMethod getUpdateMethod() {
- return myUpdateMethod;
- }
-
- public boolean shouldUpdate() {
- return getUpdateMethod() != null;
- }
-
- @Override
- public String toString() {
- return String.format("UpdateSettings{myUpdateAllRoots=%s, myUpdateMethod=%s}", myUpdateAllRoots, myUpdateMethod);
- }
- }
-
- public GitPusher(@NotNull Project project, @NotNull GitPlatformFacade facade, @NotNull ProgressIndicator indicator) {
+ GitPusher(@NotNull Project project) {
myProject = project;
- myPlatformFacade = facade;
- myProgressIndicator = indicator;
- myRepositoryManager = GitUtil.getRepositoryManager(myProject);
- myRepositories = myRepositoryManager.getRepositories();
- mySettings = GitVcsSettings.getInstance(myProject);
- myPushSettings = GitPushSettings.getInstance(myProject);
- myGit = ServiceManager.getService(Git.class);
+ myRepositoryManager = GitUtil.getRepositoryManager(project);
}
- /**
- * @param pushSpecs which branches in which repositories should be pushed.
- * The most common situation is all repositories in the project with a single currently active branch for
- * each of them.
- * @throws VcsException if couldn't query 'git log' about commits to be pushed.
- * @return
- */
- @NotNull
- GitCommitsByRepoAndBranch collectCommitsToPush(@NotNull Map pushSpecs) throws VcsException {
- Map> reposAndBranchesToPush = prepareRepositoriesAndBranchesToPush(pushSpecs);
-
- Map commitsByRepoAndBranch = new HashMap();
- for (GitRepository repository : myRepositories) {
- List branchPairs = reposAndBranchesToPush.get(repository);
- if (branchPairs == null) {
- continue;
- }
- GitCommitsByBranch commitsByBranch = collectsCommitsToPush(repository, branchPairs);
- commitsByRepoAndBranch.put(repository, commitsByBranch);
- }
- return new GitCommitsByRepoAndBranch(commitsByRepoAndBranch);
- }
-
- @NotNull
- private Map> prepareRepositoriesAndBranchesToPush(@NotNull Map pushSpecs) throws VcsException {
- Map> res = new HashMap>();
- for (GitRepository repository : myRepositories) {
- GitPushSpec pushSpec = pushSpecs.get(repository);
- if (pushSpec == null) {
- continue;
- }
- res.put(repository, Collections.singletonList(new GitBranchPair(pushSpec.getSource(), pushSpec.getDest())));
- }
- return res;
- }
-
- @NotNull
- private GitCommitsByBranch collectsCommitsToPush(@NotNull GitRepository repository, @NotNull List sourcesDestinations)
- throws VcsException {
- Map commitsByBranch = new HashMap();
-
- for (GitBranchPair sourceDest : sourcesDestinations) {
- GitLocalBranch source = sourceDest.getBranch();
- GitRemoteBranch dest = sourceDest.getDest();
- assert dest != null : "Destination branch can't be null here for branch " + source;
-
- List commits;
- GitPushBranchInfo.Type type;
- if (dest == NO_TARGET_BRANCH) {
- commits = collectRecentCommitsOnBranch(repository, source);
- type = GitPushBranchInfo.Type.NO_TRACKED_OR_TARGET;
- }
- else if (GitUtil.repoContainsRemoteBranch(repository, dest)) {
- commits = collectCommitsToPush(repository, source.getName(), dest.getName());
- type = GitPushBranchInfo.Type.STANDARD;
- }
- else {
- commits = collectRecentCommitsOnBranch(repository, source);
- type = GitPushBranchInfo.Type.NEW_BRANCH;
- }
- commitsByBranch.put(source, new GitPushBranchInfo(source, dest, commits, type));
- }
-
- return new GitCommitsByBranch(commitsByBranch);
- }
-
- private List collectRecentCommitsOnBranch(GitRepository repository, GitBranch source) throws VcsException {
- return GitHistoryUtils.history(myProject, repository.getRoot(), "--max-count=" + RECENT_COMMITS_NUMBER, source.getName());
- }
-
- @NotNull
- private List collectCommitsToPush(@NotNull GitRepository repository, @NotNull String source, @NotNull String destination)
- throws VcsException {
- return GitHistoryUtils.history(myProject, repository.getRoot(), destination + ".." + source);
- }
-
- /**
- * Makes push, shows the result in a notification. If push for current branch is rejected, shows a dialog proposing to update.
- */
- public void push(@NotNull GitPushInfo pushInfo) {
- push(pushInfo, null, null, 0);
- }
-
- /**
- * Makes push, shows the result in a notification. If push for current branch is rejected, shows a dialog proposing to update.
- * If {@code previousResult} and {@code updateSettings} are set, it means that this push is not the first, but is after a successful update.
- * In that case, if push is rejected again, the dialog is not shown, and update is performed automatically with the previously chosen
- * option.
- * Also, at the end results are merged and are shown in a single notification.
- */
- private void push(@NotNull GitPushInfo pushInfo, @Nullable GitPushResult previousResult, @Nullable UpdateSettings updateSettings, int attempt) {
- GitPushResult result = tryPushAndGetResult(pushInfo);
- handleResult(pushInfo, result, previousResult, updateSettings, attempt);
- }
-
- @NotNull
- private GitPushResult tryPushAndGetResult(@NotNull GitPushInfo pushInfo) {
- GitPushResult pushResult = new GitPushResult(myProject);
-
- GitCommitsByRepoAndBranch commits = pushInfo.getCommits();
- for (GitRepository repository : commits.getRepositories()) {
- if (commits.get(repository).getAllCommits().size() == 0) { // don't push repositories where there is nothing to push. Note that when a branch is created, several recent commits are stored in the pushInfo.
- continue;
- }
- GitPushRepoResult repoResult = pushRepository(pushInfo, commits, repository);
- if (repoResult.getType() == GitPushRepoResult.Type.NOT_PUSHING) {
- continue;
- }
- pushResult.append(repository, repoResult);
- GitPushRepoResult.Type resultType = repoResult.getType();
- if (resultType == GitPushRepoResult.Type.CANCEL || resultType == GitPushRepoResult.Type.NOT_AUTHORIZED) { // don't proceed if user has cancelled or couldn't login
- break;
- }
- }
- myRepositoryManager.updateAllRepositories(); // new remote branch may be created
- return pushResult;
- }
-
- @NotNull
- private GitPushRepoResult pushRepository(@NotNull GitPushInfo pushInfo,
- @NotNull GitCommitsByRepoAndBranch commits,
- @NotNull GitRepository repository) {
- GitPushSpec pushSpec = pushInfo.getPushSpecs().get(repository);
- GitSimplePushResult simplePushResult = pushAndGetSimpleResult(repository, pushSpec);
- String output = simplePushResult.getOutput();
- switch (simplePushResult.getType()) {
- case SUCCESS:
- return successOrErrorRepoResult(commits, repository, output, true);
- case ERROR:
- return successOrErrorRepoResult(commits, repository, output, false);
- case REJECT:
- return getResultFromRejectedPush(commits, repository, simplePushResult);
- case NOT_AUTHORIZED:
- return GitPushRepoResult.notAuthorized(output);
- case CANCEL:
- return GitPushRepoResult.cancelled(output);
- case NOT_PUSHED:
- return GitPushRepoResult.notPushed();
- default:
- return GitPushRepoResult.cancelled(output);
- }
- }
-
- @NotNull
- private static GitPushRepoResult getResultFromRejectedPush(@NotNull GitCommitsByRepoAndBranch commits,
- @NotNull GitRepository repository,
- @NotNull GitSimplePushResult simplePushResult) {
- Collection rejectedBranches = simplePushResult.getRejectedBranches();
-
- Map resultMap = new HashMap();
- GitCommitsByBranch commitsByBranch = commits.get(repository);
- boolean pushedBranchWasRejected = false;
- for (GitBranch branch : commitsByBranch.getBranches()) {
- GitPushBranchResult branchResult;
- if (branchInRejected(branch, rejectedBranches)) {
- branchResult = GitPushBranchResult.rejected();
- pushedBranchWasRejected = true;
- }
- else {
- branchResult = successfulResultForBranch(commitsByBranch, branch);
- }
- resultMap.put(branch, branchResult);
- }
-
- if (pushedBranchWasRejected) {
- return GitPushRepoResult.someRejected(resultMap, simplePushResult.getOutput());
- } else {
- // The rejectedDetector detected rejected push of the branch which had nothing to push (but is behind the upstream). We are not counting it.
- return GitPushRepoResult.success(resultMap, simplePushResult.getOutput());
- }
- }
-
- @NotNull
- private GitSimplePushResult pushAndGetSimpleResult(@NotNull GitRepository repository, @NotNull GitPushSpec pushSpec) {
- if (pushSpec.getDest() == NO_TARGET_BRANCH) {
- return GitSimplePushResult.notPushed();
- }
-
- GitRemote remote = pushSpec.getRemote();
- Collection pushUrls = remote.getPushUrls();
- if (pushUrls.isEmpty()) {
- LOG.error("No urls or pushUrls are defined for " + remote);
- return GitSimplePushResult.error("There are no URLs defined for remote " + remote.getName());
- }
- String url = pushUrls.iterator().next();
- GitSimplePushResult pushResult;
- pushResult = pushNatively(repository, pushSpec, url);
-
- if (pushResult.getType() == GitSimplePushResult.Type.SUCCESS) {
- setUpstream(repository, pushSpec.getSource(), pushSpec.getRemote(), pushSpec.getDest());
- }
-
- return pushResult;
- }
-
- private static void setUpstream(@NotNull GitRepository repository,
- @NotNull GitLocalBranch source, @NotNull GitRemote remote, @NotNull GitRemoteBranch dest) {
- if (!branchTrackingInfoIsSet(repository, source)) {
- Project project = repository.getProject();
- VirtualFile root = repository.getRoot();
- String branchName = source.getName();
- try {
- boolean rebase = getMergeOrRebaseConfig(project, root);
- GitConfigUtil.setValue(project, root, "branch." + branchName + ".remote", remote.getName());
- GitConfigUtil.setValue(project, root, "branch." + branchName + ".merge",
- GitBranch.REFS_HEADS_PREFIX + dest.getNameForRemoteOperations());
- if (rebase) {
- GitConfigUtil.setValue(project, root, "branch." + branchName + ".rebase", "true");
- }
- }
- catch (VcsException e) {
- LOG.error(String.format("Couldn't set up tracking for source branch %s, target branch %s, remote %s in root %s",
- source, dest, remote, repository), e);
- VcsNotifier.getInstance(project).notifyWeakError("Couldn't set up branch tracking");
- }
- }
- }
-
- private static boolean getMergeOrRebaseConfig(Project project, VirtualFile root) throws VcsException {
- String autoSetupRebase = GitConfigUtil.getValue(project, root, GitConfigUtil.BRANCH_AUTOSETUP_REBASE);
- return autoSetupRebase != null && (autoSetupRebase.equals("remote") || autoSetupRebase.equals("always"));
- }
-
- private static boolean branchTrackingInfoIsSet(@NotNull GitRepository repository, @NotNull GitLocalBranch source) {
- for (GitBranchTrackInfo trackInfo : repository.getBranchTrackInfos()) {
- if (trackInfo.getLocalBranch().equals(source)) {
- return true;
- }
- }
- return false;
- }
-
- @NotNull
- private GitSimplePushResult pushNatively(GitRepository repository, GitPushSpec pushSpec, @NotNull String url) {
- GitPushRejectedDetector rejectedDetector = new GitPushRejectedDetector();
- GitLineHandlerListener progressListener = GitStandardProgressAnalyzer.createListener(myProgressIndicator);
- GitCommandResult res = myGit.push(repository, pushSpec, url, rejectedDetector, progressListener);
- if (rejectedDetector.rejected()) {
- Collection rejectedBranches = rejectedDetector.getRejectedBranches();
- return GitSimplePushResult.reject(rejectedBranches);
- }
- else if (res.success()) {
- return GitSimplePushResult.success();
- }
- else {
- return GitSimplePushResult.error(res.getErrorOutputAsHtmlString());
- }
- }
-
- @NotNull
- private static GitPushRepoResult successOrErrorRepoResult(@NotNull GitCommitsByRepoAndBranch commits, @NotNull GitRepository repository, @NotNull String output, boolean success) {
- GitPushRepoResult repoResult;
- Map resultMap = new HashMap();
- GitCommitsByBranch commitsByBranch = commits.get(repository);
- for (GitBranch branch : commitsByBranch.getBranches()) {
- GitPushBranchResult branchResult = success ?
- successfulResultForBranch(commitsByBranch, branch) :
- GitPushBranchResult.error();
- resultMap.put(branch, branchResult);
- }
- repoResult = success ? GitPushRepoResult.success(resultMap, output) : GitPushRepoResult.error(resultMap, output);
- return repoResult;
- }
-
- @NotNull
- private static GitPushBranchResult successfulResultForBranch(@NotNull GitCommitsByBranch commitsByBranch, @NotNull GitBranch branch) {
- GitPushBranchInfo branchInfo = commitsByBranch.get(branch);
- if (branchInfo.isNewBranchCreated()) {
- return GitPushBranchResult.newBranch(branchInfo.getDestBranch().getName());
- }
- return GitPushBranchResult.success(branchInfo.getCommits().size());
- }
-
- private static boolean branchInRejected(@NotNull GitBranch branch, @NotNull Collection rejectedBranches) {
- String branchName = branch.getName();
- final String REFS_HEADS = "refs/heads/";
- if (branchName.startsWith(REFS_HEADS)) {
- branchName = branchName.substring(REFS_HEADS.length());
- }
-
- for (String rejectedBranch : rejectedBranches) {
- if (rejectedBranch.equals(branchName) || (rejectedBranch.startsWith(REFS_HEADS) && rejectedBranch.substring(REFS_HEADS.length()).equals(branchName))) {
- return true;
- }
- }
- return false;
- }
-
- // if all repos succeeded, show notification.
- // if all repos failed, show notification.
- // if some repos failed, show notification with both results.
- // if in failed repos, some branches were rejected, it is a warning-type, but not an error.
- // if in a failed repo, current branch was rejected, propose to update the branch. Don't do it if not current branch was rejected,
- // since it is difficult to update such a branch.
- // if in a failed repo, a branch was rejected that had nothing to push, don't notify about the rejection.
- // Besides all of the above, don't confuse users with 1 repository with all this "repository/root" stuff;
- // don't confuse users which push only a single branch with all this "branch" stuff.
- private void handleResult(@NotNull GitPushInfo pushInfo, @NotNull GitPushResult result, @Nullable GitPushResult previousResult, @Nullable UpdateSettings updateSettings,
- int pushAttempt) {
- result.mergeFrom(previousResult);
-
- if (result.isEmpty()) {
- VcsNotifier.getInstance(myProject).notifyInfo("Nothing to push");
- }
- else if (result.wasErrorCancelOrNotAuthorized()) {
- // if there was an error on any repo, we won't propose to update even if current branch of a repo was rejected
- result.createPushNotificationAndNotify();
- }
- else {
- // there were no errors, but there might be some rejected branches on some of the repositories
- // => for current branch propose to update and re-push it. For others just warn
- Map rejectedPushesForCurrentBranch = result.getRejectedPushesFromCurrentBranchToTrackedBranch(pushInfo);
-
- if (pushAttempt <= MAX_PUSH_ATTEMPTS && !rejectedPushesForCurrentBranch.isEmpty()) {
-
- LOG.info(
- String.format("Rejected pushes for current branches: %n%s%nUpdate settings: %s", rejectedPushesForCurrentBranch, updateSettings));
-
- if (updateSettings == null) {
- // show dialog only when push is rejected for the first time in a row, otherwise reuse previously chosen update method
- // and don't show the dialog again if user has chosen not to ask again
- updateSettings = readUpdateSettings();
- if (!mySettings.autoUpdateIfPushRejected()) {
- updateSettings = showDialogAndGetExitCode(rejectedPushesForCurrentBranch, updateSettings);
- saveUpdateSettings(updateSettings);
- }
- }
-
- if (updateSettings.shouldUpdate()) {
- Collection repositoriesToUpdate = getRootsToUpdate(rejectedPushesForCurrentBranch, updateSettings.shouldUpdateAllRoots());
- GitPushResult adjustedPushResult = result.remove(rejectedPushesForCurrentBranch);
- adjustedPushResult.markUpdateStartIfNotMarked(repositoriesToUpdate);
- boolean updateResult = update(repositoriesToUpdate, updateSettings.getUpdateMethod());
- if (updateResult) {
- myProgressIndicator.setText(INDICATOR_TEXT);
- GitPushInfo newPushInfo = pushInfo.retain(rejectedPushesForCurrentBranch);
- push(newPushInfo, adjustedPushResult, updateSettings, pushAttempt + 1);
- return; // don't notify - next push will notify all results in compound
- }
- }
-
- }
-
- result.createPushNotificationAndNotify();
- }
- }
-
- private void saveUpdateSettings(@NotNull UpdateSettings updateSettings) {
- UpdateMethod updateMethod = updateSettings.getUpdateMethod();
- if (updateMethod != null) { // null if user has pressed cancel
- myPushSettings.setUpdateAllRoots(updateSettings.shouldUpdateAllRoots());
- myPushSettings.setUpdateMethod(updateMethod);
- }
- }
-
- @NotNull
- private UpdateSettings readUpdateSettings() {
- boolean updateAllRoots = myPushSettings.shouldUpdateAllRoots();
- UpdateMethod updateMethod = myPushSettings.getUpdateMethod();
- return new UpdateSettings(updateAllRoots, updateMethod);
- }
-
- private UpdateSettings showDialogAndGetExitCode(final Map rejectedPushesForCurrentBranch,
- final UpdateSettings initialSettings) {
- final AtomicReference updateSettings = new AtomicReference