diff --git a/plugins/git4idea/src/git4idea/update/GitFetchResult.java b/plugins/git4idea/src/git4idea/update/GitFetchResult.java index 164992dbb3e3..15e700f59a0b 100644 --- a/plugins/git4idea/src/git4idea/update/GitFetchResult.java +++ b/plugins/git4idea/src/git4idea/update/GitFetchResult.java @@ -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 myErrors = new ArrayList(); + private Collection myPrunedRefs = new ArrayList(); public enum Type { SUCCESS, @@ -83,4 +87,21 @@ public final class GitFetchResult { return myErrors; } + public void addPruneInfo(@NotNull Collection prunedRefs) { + myPrunedRefs.addAll(prunedRefs); + } + + @NotNull + public Collection getPrunedRefs() { + return myPrunedRefs; + } + + @NotNull + public String getAdditionalInfo() { + if (!myPrunedRefs.isEmpty()) { + return "Pruned obsolete remote " + pluralize("reference", myPrunedRefs.size()) + ": " + join(myPrunedRefs, ","); + } + return ""; + } + } diff --git a/plugins/git4idea/src/git4idea/update/GitFetcher.java b/plugins/git4idea/src/git4idea/update/GitFetcher.java index b27459f8d91a..b0e66e378c77 100644 --- a/plugins/git4idea/src/git4idea/update/GitFetcher.java +++ b/plugins/git4idea/src/git4idea/update/GitFetcher.java @@ -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 result = new AtomicReference(); 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 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 roots, @Nullable String errorNotificationTitle, boolean notifySuccess) { + Map additionalInfo = new HashMap(); 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 additionalInfo) { + if (additionalInfo.isEmpty()) { + return ""; + } + StringBuilder info = new StringBuilder(); + if (myRepositoryManager.moreThanOneRoot()) { + for (Map.Entry 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 myPrunedRefs = new ArrayList(); + + @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 getPrunedRefs() { + return myPrunedRefs; + } + } }