IDEA-77857 Git fetch with --prune

Detect which refs were deleted and display a minor notification about them.
This commit is contained in:
Kirill Likhodedov
2012-02-13 10:15:54 +04:00
parent e14c99577c
commit 7375e505d1
2 changed files with 94 additions and 10 deletions
@@ -21,6 +21,9 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import static com.intellij.openapi.util.text.StringUtil.join;
import static com.intellij.openapi.util.text.StringUtil.pluralize;
/**
* @author Kirill Likhodedov
*/
@@ -28,6 +31,7 @@ public final class GitFetchResult {
private final Type myType;
private Collection<Exception> myErrors = new ArrayList<Exception>();
private Collection<String> myPrunedRefs = new ArrayList<String>();
public enum Type {
SUCCESS,
@@ -83,4 +87,21 @@ public final class GitFetchResult {
return myErrors;
}
public void addPruneInfo(@NotNull Collection<String> prunedRefs) {
myPrunedRefs.addAll(prunedRefs);
}
@NotNull
public Collection<String> getPrunedRefs() {
return myPrunedRefs;
}
@NotNull
public String getAdditionalInfo() {
if (!myPrunedRefs.isEmpty()) {
return "Pruned obsolete remote " + pluralize("reference", myPrunedRefs.size()) + ": " + join(myPrunedRefs, ",");
}
return "";
}
}
@@ -19,9 +19,12 @@ import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitVcs;
import git4idea.NotificationManager;
import git4idea.commands.*;
import git4idea.jgit.GitHttpAdapter;
import git4idea.repo.GitRemote;
@@ -33,7 +36,11 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Kirill Likhodedov
@@ -62,7 +69,8 @@ public class GitFetcher {
GitRepository repository = myRepositoryManager.getRepositoryForRoot(root);
assert repository != null : "Repository can't be null for " + root + "\n" + myRepositoryManager;
GitFetchResult result = GitFetchResult.success();
// TODO need to have a fair compound result here
GitFetchResult fetchResult = GitFetchResult.success();
for (GitRemote remote : repository.getRemotes()) {
String url = remote.getFirstUrl();
if (url == null) {
@@ -70,32 +78,38 @@ public class GitFetcher {
}
if (GitHttpAdapter.shouldUseJGit(url)) {
GitFetchResult res = GitHttpAdapter.fetch(repository, remote, url);
myErrors.addAll(res.getErrors());
if (!res.isSuccess()) {
result = res;
res.addPruneInfo(fetchResult.getPrunedRefs());
fetchResult = res;
myErrors.addAll(fetchResult.getErrors());
if (!fetchResult.isSuccess()) {
break;
}
}
else {
GitFetchResult fetchResult = fetchNatively(root, remote);
GitFetchResult res = fetchNatively(root, remote);
res.addPruneInfo(fetchResult.getPrunedRefs());
fetchResult = res;
if (!fetchResult.isSuccess()) {
result = fetchResult;
break;
}
}
}
repository.update(GitRepository.TrackedTopic.BRANCHES);
return result;
return fetchResult;
}
private GitFetchResult fetchNatively(@NotNull VirtualFile root, @NotNull GitRemote remote) {
final GitLineHandlerPasswordRequestAware h = new GitLineHandlerPasswordRequestAware(myProject, root, GitCommand.FETCH);
h.addParameters("--prune");
h.addParameters(remote.getName());
final GitTask fetchTask = new GitTask(myProject, h, "Fetching...");
fetchTask.setProgressIndicator(myProgressIndicator);
fetchTask.setProgressAnalyzer(new GitStandardProgressAnalyzer());
GitFetchPruneDetector pruneDetector = new GitFetchPruneDetector();
h.addLineListener(pruneDetector);
final AtomicReference<GitFetchResult> result = new AtomicReference<GitFetchResult>();
fetchTask.execute(true, false, new GitTaskResultHandlerAdapter() {
@Override
@@ -120,6 +134,8 @@ public class GitFetcher {
result.set(GitFetchResult.error(myErrors));
}
});
result.get().addPruneInfo(pruneDetector.getPrunedRefs());
return result.get();
}
@@ -132,9 +148,9 @@ public class GitFetcher {
@NotNull GitFetchResult result,
@Nullable String errorNotificationTitle, @NotNull Collection<? extends Exception> errors) {
if (result.isSuccess()) {
GitVcs.NOTIFICATION_GROUP_ID.createNotification("Fetched successfully", NotificationType.INFORMATION).notify(project);
GitVcs.NOTIFICATION_GROUP_ID.createNotification("Fetched successfully" + result.getAdditionalInfo(), NotificationType.INFORMATION).notify(project);
} else if (result.isCancelled()) {
GitVcs.NOTIFICATION_GROUP_ID.createNotification("Fetch cancelled by user", NotificationType.WARNING).notify(project);
GitVcs.NOTIFICATION_GROUP_ID.createNotification("Fetch cancelled by user" + result.getAdditionalInfo(), NotificationType.WARNING).notify(project);
} else if (result.isNotAuthorized()) {
String title;
String description;
@@ -145,11 +161,12 @@ public class GitFetcher {
title = "Fetch failed";
description = "Couldn't authorize";
}
description += result.getAdditionalInfo();
GitUIUtil.notifyMessage(project, title, description, NotificationType.ERROR, true, null);
} else {
GitVcs instance = GitVcs.getInstance(project);
if (instance != null && instance.getExecutableValidator().isExecutableValid()) {
GitUIUtil.notifyMessage(project, "Fetch failed", null, NotificationType.ERROR, true, errors);
GitUIUtil.notifyMessage(project, "Fetch failed", result.getAdditionalInfo(), NotificationType.ERROR, true, errors);
}
}
}
@@ -165,8 +182,10 @@ public class GitFetcher {
* @return true if all fetches were successful, false if at least one fetch failed.
*/
public boolean fetchRootsAndNotify(@NotNull Collection<VirtualFile> roots, @Nullable String errorNotificationTitle, boolean notifySuccess) {
Map<VirtualFile, String> additionalInfo = new HashMap<VirtualFile, String>();
for (VirtualFile root : roots) {
GitFetchResult result = fetch(root);
additionalInfo.put(root, result.getAdditionalInfo());
if (!result.isSuccess()) {
displayFetchResult(myProject, result, errorNotificationTitle, getErrors());
return false;
@@ -175,6 +194,50 @@ public class GitFetcher {
if (notifySuccess) {
GitUIUtil.notifySuccess(myProject, "", "Fetched successfully");
}
String addInfo = makeAdditionalInfoByRoot(additionalInfo);
if (!StringUtil.isEmptyOrSpaces(addInfo)) {
NotificationManager.getInstance(myProject).notify(GitVcs.MINOR_NOTIFICATION, "Fetch details", addInfo, NotificationType.INFORMATION);
}
return true;
}
@NotNull
private String makeAdditionalInfoByRoot(@NotNull Map<VirtualFile, String> additionalInfo) {
if (additionalInfo.isEmpty()) {
return "";
}
StringBuilder info = new StringBuilder();
if (myRepositoryManager.moreThanOneRoot()) {
for (Map.Entry<VirtualFile, String> entry : additionalInfo.entrySet()) {
info.append(entry.getValue()).append(" in ").append(GitUIUtil.getShortRepositoryName(myProject, entry.getKey()));
}
}
else {
info.append(additionalInfo.values().iterator().next());
}
return info.toString();
}
private static class GitFetchPruneDetector extends GitLineHandlerAdapter {
private static final Pattern PRUNE_PATTERN = Pattern.compile("\\s*x\\s*\\[deleted\\].*->\\s*(\\S*)");
@NotNull private final Collection<String> myPrunedRefs = new ArrayList<String>();
@Override
public void onLineAvailable(String line, Key outputType) {
// x [deleted] (none) -> origin/frmari
Matcher matcher = PRUNE_PATTERN.matcher(line);
if (matcher.matches()) {
myPrunedRefs.add(matcher.group(1));
}
}
@NotNull
public Collection<String> getPrunedRefs() {
return myPrunedRefs;
}
}
}