From b8161808a783fc155257ffa080cd05c8d29d2946 Mon Sep 17 00:00:00 2001 From: irengrig Date: Wed, 2 Feb 2011 14:47:13 +0300 Subject: [PATCH] git log: correctly do update() for cherry-pick (not allowed for current branch commits, not allowed for merge commits (additional logic required for that)); show stashed commits --- .../openapi/vcs/CollectionsMultiplier.java | 61 ++++++ plugins/git4idea/git4idea.iml | 1 + .../src/git4idea/changes/GitChangeUtils.java | 21 +- .../src/git4idea/history/GitHistoryUtils.java | 34 +++ .../src/git4idea/history/GitLogParser.java | 17 +- .../src/git4idea/history/GitLogRecord.java | 8 +- .../history/browser/ChangesFilter.java | 16 ++ .../git4idea/history/browser/GitCommit.java | 11 + .../history/browser/LowLevelAccessImpl.java | 17 +- .../history/wholeTree/ByRootLoader.java | 207 ++++++++++++++++++ .../history/wholeTree/DetailsCache.java | 31 ++- .../history/wholeTree/GitLogFilters.java | 101 +++++++++ .../git4idea/history/wholeTree/GitLogUI.java | 84 ++++--- .../history/wholeTree/LoadAlgorithm.java | 25 +-- .../history/wholeTree/LoadController.java | 51 ++--- .../git4idea/history/wholeTree/Loader.java | 4 +- .../wholeTree/LoaderAndRefresherImpl.java | 6 +- .../git4idea/history/wholeTree/Mediator.java | 7 +- .../history/wholeTree/MediatorImpl.java | 15 +- .../src/git4idea/ui/GitUnstashDialog.java | 71 ++---- .../git4idea/src/git4idea/ui/StashInfo.java | 58 +++++ .../src/git4idea/update/GitStashUtils.java | 30 +++ 22 files changed, 678 insertions(+), 198 deletions(-) create mode 100644 platform/vcs-api/src/com/intellij/openapi/vcs/CollectionsMultiplier.java create mode 100644 plugins/git4idea/src/git4idea/history/wholeTree/ByRootLoader.java create mode 100644 plugins/git4idea/src/git4idea/history/wholeTree/GitLogFilters.java create mode 100644 plugins/git4idea/src/git4idea/ui/StashInfo.java diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/CollectionsMultiplier.java b/platform/vcs-api/src/com/intellij/openapi/vcs/CollectionsMultiplier.java new file mode 100644 index 000000000000..156fa3ca1200 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/CollectionsMultiplier.java @@ -0,0 +1,61 @@ +/* + * 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 com.intellij.openapi.vcs; + +import com.intellij.util.Consumer; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author irengrig + * Date: 2/2/11 + * Time: 10:11 AM + * Cartesian product + */ +public class CollectionsMultiplier { + private List> myInner; + + public void add(@Nullable final List list) { + if (list == null || list.isEmpty()) return; + if (myInner == null) { + myInner = Collections.singletonList(list); + return; + } + final List> copy = myInner; + myInner = new ArrayList>(); + for (T t : list) { + for (List existing : copy) { + final ArrayList newList = new ArrayList(existing); + newList.add(t); + myInner.add(newList); + } + } + } + + public boolean isEmpty() { + return myInner == null; + } + + public void iterateResult(final Consumer> consumer) { + if (myInner == null) return; + for (List list : myInner) { + consumer.consume(list); + } + } +} diff --git a/plugins/git4idea/git4idea.iml b/plugins/git4idea/git4idea.iml index 3d79be268cb7..830aa37f58f3 100644 --- a/plugins/git4idea/git4idea.iml +++ b/plugins/git4idea/git4idea.iml @@ -32,6 +32,7 @@ + diff --git a/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java b/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java index a413b1a8104b..16e6467e84f3 100644 --- a/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java +++ b/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java @@ -34,7 +34,6 @@ import git4idea.commands.GitCommand; import git4idea.commands.GitSimpleHandler; import git4idea.commands.StringScanner; import git4idea.history.browser.SHAHash; -import git4idea.history.wholeTree.CommitI; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; @@ -301,10 +300,12 @@ public class GitChangeUtils { } @Nullable - public static SHAHash commitExists(final Project project, final VirtualFile root, final String anyReference) { + public static SHAHash commitExists(final Project project, final VirtualFile root, final String anyReference, + final String... parameters) { GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG); h.setNoSSH(true); h.setSilent(true); + h.addParameters(parameters); h.addParameters("--max-count=1", "--pretty=%H", "--encoding=UTF-8", "\"" + anyReference + "\"", "--"); try { final String output = h.run().trim(); @@ -316,6 +317,22 @@ public class GitChangeUtils { } } + public static boolean isAnyLevelChild(final Project project, final VirtualFile root, final SHAHash parent, + final String anyReferenceChild) { + GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.MERGE_BASE); + h.setNoSSH(true); + h.setSilent(true); + h.addParameters("\"" + parent.getValue() + "\"","\"" + anyReferenceChild + "\"", "--"); + try { + final String output = h.run().trim(); + if (StringUtil.isEmptyOrSpaces(output)) return false; + return parent.getValue().equals(output.trim()); + } + catch (VcsException e) { + return false; + } + } + @Nullable public static SHAHash commitExistsByComment(final Project project, final VirtualFile root, final String anyReference) { GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG); diff --git a/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java b/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java index 1467493ad418..816551f7bde7 100644 --- a/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java +++ b/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java @@ -37,13 +37,17 @@ import com.intellij.util.Consumer; import com.intellij.util.concurrency.Semaphore; import git4idea.*; import git4idea.commands.*; +import git4idea.config.GitConfigUtil; import git4idea.history.browser.GitCommit; import git4idea.history.browser.SHAHash; import git4idea.history.browser.SymbolicRefs; import git4idea.history.wholeTree.AbstractHash; import git4idea.history.wholeTree.CommitHashPlusParents; +import git4idea.ui.GitUIUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.atomic.AtomicReference; @@ -534,6 +538,36 @@ public class GitHistoryUtils { return null; } + @Nullable + public static List> loadStashStackAsCommits(@NotNull Project project, @NotNull VirtualFile root, + SymbolicRefs refs, final String... parameters) throws VcsException { + GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.STASH); + GitLogParser parser = new GitLogParser(SHORT_HASH, HASH, COMMIT_TIME, AUTHOR_NAME, AUTHOR_TIME, AUTHOR_EMAIL, COMMITTER_NAME, COMMITTER_EMAIL, SHORT_PARENTS, REF_NAMES, SHORT_REF_LOG_SELECTOR, SUBJECT, BODY); + h.setSilent(true); + h.setNoSSH(true); + h.addParameters("list"); + h.addParameters(parameters); + h.addParameters(parser.getPretty()); + parser.parseStatusBeforeName(true); + + String out; + try { + h.setCharset(Charset.forName(GitConfigUtil.getLogEncoding(project, root))); + out = h.run(); + } + catch (VcsException e) { + GitUIUtil.showOperationError(project, e, h.printableCommandLine()); + return null; + } + final List gitLogRecords = parser.parse(out); + final List> result = new ArrayList>(); + for (GitLogRecord gitLogRecord : gitLogRecords) { + final GitCommit gitCommit = createCommit(project, refs, root, gitLogRecord); + result.add(new Pair(gitLogRecord.getShortenedRefLog(), gitCommit)); + } + return result; + } + public static List commitsDetails(Project project, FilePath path, SymbolicRefs refs, final Collection commitsIds) throws VcsException { diff --git a/plugins/git4idea/src/git4idea/history/GitLogParser.java b/plugins/git4idea/src/git4idea/history/GitLogParser.java index 07f9c4e23bea..1903b62315cb 100644 --- a/plugins/git4idea/src/git4idea/history/GitLogParser.java +++ b/plugins/git4idea/src/git4idea/history/GitLogParser.java @@ -15,22 +15,9 @@ */ package git4idea.history; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Function; -import com.intellij.util.containers.Convertor; -import git4idea.GitContentRevision; -import git4idea.GitRevisionNumber; -import git4idea.history.wholeTree.AbstractHash; -import java.io.File; import java.util.*; /** @@ -80,8 +67,8 @@ class GitLogParser { * These are the pieces of information about a commit which we want to get from 'git log'. */ enum GitLogOption { - SHORT_HASH("h"), HASH("H"), COMMIT_TIME("ct"), AUTHOR_NAME("an"), AUTHOR_TIME("at"), AUTHOR_EMAIL("ae"), COMMITTER_NAME("cn"), COMMITTER_EMAIL("ce"), SUBJECT("s"), BODY("b"), - SHORT_PARENTS("p"), PARENTS("P"), REF_NAMES("d"); + SHORT_HASH("h"), HASH("H"), COMMIT_TIME("ct"), AUTHOR_NAME("an"), AUTHOR_TIME("at"), AUTHOR_EMAIL("ae"), COMMITTER_NAME("cn"), + COMMITTER_EMAIL("ce"), SUBJECT("s"), BODY("b"), SHORT_PARENTS("p"), PARENTS("P"), REF_NAMES("d"), SHORT_REF_LOG_SELECTOR("gd"); private String myPlaceholder; private GitLogOption(String placeholder) { myPlaceholder = placeholder; } diff --git a/plugins/git4idea/src/git4idea/history/GitLogRecord.java b/plugins/git4idea/src/git4idea/history/GitLogRecord.java index 143d7221f3e8..fc2a93d79710 100644 --- a/plugins/git4idea/src/git4idea/history/GitLogRecord.java +++ b/plugins/git4idea/src/git4idea/history/GitLogRecord.java @@ -31,12 +31,7 @@ import git4idea.GitUtil; import git4idea.history.wholeTree.AbstractHash; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; import static git4idea.history.GitLogParser.GitLogOption.*; @@ -90,6 +85,7 @@ class GitLogRecord { String getCommitterEmail() { return lookup(COMMITTER_EMAIL); } String getSubject() { return lookup(SUBJECT); } String getBody() { return lookup(BODY); } + String getShortenedRefLog() { return lookup(SHORT_REF_LOG_SELECTOR); } // access methods with some formatting or conversion diff --git a/plugins/git4idea/src/git4idea/history/browser/ChangesFilter.java b/plugins/git4idea/src/git4idea/history/browser/ChangesFilter.java index 7eab34c11434..c785612ec32c 100644 --- a/plugins/git4idea/src/git4idea/history/browser/ChangesFilter.java +++ b/plugins/git4idea/src/git4idea/history/browser/ChangesFilter.java @@ -21,6 +21,7 @@ import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.changes.FilePathsHelper; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ArrayUtil; import com.intellij.util.PairProcessor; import git4idea.GitUtil; import org.jetbrains.annotations.NotNull; @@ -31,6 +32,21 @@ import java.util.regex.Pattern; public class ChangesFilter { + public static void filtersToParameters(Collection filters, List parameters) { + for (Filter filter : filters) { + filter.getCommandParametersFilter().applyToCommandLine(parameters); + } + } + + public static String[] filtersToParameterArray(Collection filters) { + if (filters == null || filters.isEmpty()) return ArrayUtil.EMPTY_STRING_ARRAY; + final ArrayList strings = new ArrayList(); + for (Filter filter : filters) { + filter.getCommandParametersFilter().applyToCommandLine(strings); + } + return strings.toArray(new String[strings.size()]); + } + public abstract static class Merger { private final Collection myFilters; private MemoryFilter myResult; diff --git a/plugins/git4idea/src/git4idea/history/browser/GitCommit.java b/plugins/git4idea/src/git4idea/history/browser/GitCommit.java index 62ae1432f672..71f2af4485c7 100644 --- a/plugins/git4idea/src/git4idea/history/browser/GitCommit.java +++ b/plugins/git4idea/src/git4idea/history/browser/GitCommit.java @@ -16,7 +16,9 @@ package git4idea.history.browser; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.ObjectsConvertor; import com.intellij.openapi.vcs.changes.Change; +import com.intellij.util.containers.Convertor; import git4idea.history.wholeTree.AbstractHash; import org.jetbrains.annotations.NotNull; @@ -212,4 +214,13 @@ public class GitCommit { public void setOnTracked(boolean onTracked) { myOnTracked = onTracked; } + + public List getConvertedParents() { + return ObjectsConvertor.convert(getParentsHashes(), new Convertor() { + @Override + public AbstractHash convert(String o) { + return AbstractHash.create(o); + } + }); + } } diff --git a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java index 9db1e22fe7be..ffd72352ba15 100644 --- a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java +++ b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java @@ -54,9 +54,7 @@ public class LowLevelAccessImpl implements LowLevelAccess { final AsynchConsumer consumer, Getter isCanceled, int useMaxCnt) throws VcsException { final List parameters = new ArrayList(); - for (ChangesFilter.Filter filter : filters) { - filter.getCommandParametersFilter().applyToCommandLine(parameters); - } + ChangesFilter.filtersToParameters(filters, parameters); if (! startingPoints.isEmpty()) { for (String startingPoint : startingPoints) { @@ -110,6 +108,13 @@ public class LowLevelAccessImpl implements LowLevelAccess { refs.setTrackedRemote(current.getTrackedRemoteName(myProject, myRoot)); } refs.setUsername(GitConfigUtil.getValue(myProject, myRoot, GitConfigUtil.USER_NAME)); + // todo + /*GitStashUtils.loadStashStack(myProject, myRoot, new Consumer() { + @Override + public void consume(StashInfo stashInfo) { + + } + });*/ return refs; } @@ -125,10 +130,8 @@ public class LowLevelAccessImpl implements LowLevelAccess { parameters.add("--max-count=" + useMaxCnt); } - for (ChangesFilter.Filter filter : filters) { - filter.getCommandParametersFilter().applyToCommandLine(parameters); - } - + ChangesFilter.filtersToParameters(filters, parameters); + if (! startingPoints.isEmpty()) { for (String startingPoint : startingPoints) { parameters.add(startingPoint); diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/ByRootLoader.java b/plugins/git4idea/src/git4idea/history/wholeTree/ByRootLoader.java new file mode 100644 index 000000000000..be33f969f000 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/ByRootLoader.java @@ -0,0 +1,207 @@ +/* + * 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.history.wholeTree; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.Consumer; +import com.intellij.util.continuation.ContinuationContext; +import com.intellij.util.continuation.TaskDescriptor; +import com.intellij.util.continuation.Where; +import git4idea.changes.GitChangeUtils; +import git4idea.history.GitHistoryUtils; +import git4idea.history.browser.*; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author irengrig + * Date: 2/1/11 + * Time: 6:30 PM + * + * We wouldn't include it into growth controller (not many rows loaded) + */ +public class ByRootLoader extends TaskDescriptor { + private final Project myProject; + private final LoaderAndRefresherImpl.MyRootHolder myRootHolder; + private final LowLevelAccess myLowLevelAccess; + private final Mediator myMediator; + private final DetailsCache myDetailsCache; + private SymbolicRefs mySymbolicRefs; + private final Mediator.Ticket myTicket; + private final UsersIndex myUsersIndex; + private final Collection myStartingPoints; + @NotNull + private final GitLogFilters myGitLogFilters; + + public ByRootLoader(Project project, + LoaderAndRefresherImpl.MyRootHolder rootHolder, + Mediator mediator, + DetailsCache detailsCache, + Mediator.Ticket ticket, UsersIndex usersIndex, GitLogFilters gitLogFilters, final Collection startingPoints) { + super("Initial checks", Where.POOLED); + myProject = project; + myRootHolder = rootHolder; + myUsersIndex = usersIndex; + myStartingPoints = startingPoints; + myLowLevelAccess = new LowLevelAccessImpl(myProject, myRootHolder.getRoot()); + myMediator = mediator; + myDetailsCache = detailsCache; + myTicket = ticket; + myGitLogFilters = gitLogFilters; + } + + @Override + public void run(ContinuationContext context) { + final ProgressIndicator pi = ProgressManager.getInstance().getProgressIndicator(); + progress(pi, "Load branches and tags"); + initSymbRefs(); + progress(pi, "Load stashed"); + loadStash(); + progress(pi, "Try to load by reference"); + loadByHashesAside(context); + } + + private void progress(final ProgressIndicator pi, final String progress) { + if (pi != null) { + pi.checkCanceled(); + pi.setText(progress); + } + } + + private void loadStash() { + // start is not on a branch + if (myStartingPoints != null && (! myStartingPoints.isEmpty())) return; + + final List details = new ArrayList(); + final List commits = new ArrayList(); + final Map stashMap = new HashMap(); + final List> parents = myGitLogFilters.isEmpty() ? new ArrayList>() : null; + + myGitLogFilters.callConsumer(new Consumer>() { + @Override + public void consume(List filters) { + try { + final List> stash = GitHistoryUtils.loadStashStackAsCommits(myProject, myRootHolder.getRoot(), + mySymbolicRefs, ChangesFilter.filtersToParameterArray(filters)); + if (stash == null) return; + for (Pair pair : stash) { + final GitCommit gitCommit = pair.getSecond(); + if (stashMap.containsKey(gitCommit.getShortHash())) continue; + + details.add(gitCommit); + if (parents != null) { + parents.add(gitCommit.getConvertedParents()); + } + commits.add(createCommitI(gitCommit)); + stashMap.put(gitCommit.getShortHash(), pair.getFirst()); + } + } + catch (VcsException e) { + myMediator.acceptException(e); + } + } + }, true); + + myDetailsCache.putStash(myRootHolder.getRoot(), stashMap); + // does not work + //myDetailsCache.acceptAnswer(details, myRootHolder.getRoot()); + myMediator.appendResult(myTicket, commits, parents); + } + + // if there're filters -> parents shouldn't be loaded + public void loadByHashesAside(final ContinuationContext context) { + final List result = new ArrayList(); + final Set controlSet = new HashSet(); + + final List hashes = myGitLogFilters.getPossibleReferencies(); + if (hashes == null) return; + myGitLogFilters.callConsumer(new Consumer>() { + @Override + public void consume(List filters) { + for (String hash : hashes) { + try { + final SHAHash shaHash = GitChangeUtils.commitExists(myProject, myRootHolder.getRoot(), hash, ChangesFilter.filtersToParameterArray(filters)); + if (shaHash == null) continue; + if (controlSet.contains(shaHash)) continue; + controlSet.add(shaHash); + + if (myStartingPoints != null && (! myStartingPoints.isEmpty())) { + boolean matches = false; + for (String startingPoint : myStartingPoints) { + if(GitChangeUtils.isAnyLevelChild(myProject, myRootHolder.getRoot(), shaHash, startingPoint)) { + matches = true; + break; + } + } + if (! matches) continue; + } + final List commits = myLowLevelAccess.getCommitDetails(Collections.singletonList(shaHash.getValue()), mySymbolicRefs); + if (commits.isEmpty()) continue; + + myDetailsCache.acceptAnswer(commits, myRootHolder.getRoot()); + appendCommits(result, commits); + } + catch (VcsException e1) { + continue; + } + } + } + }, false); + + if (! result.isEmpty()) { + final StepType stepType = myMediator.appendResult(myTicket, result, null); + // here we react only on "stop", not on "pause" + if (StepType.STOP.equals(stepType)) { + context.cancelEverything(); + } + } + } + + private void appendCommits(List result, List commits) { + for (GitCommit commit : commits) { + CommitI commitObj = createCommitI(commit); + result.add(commitObj); + } + } + + private CommitI createCommitI(GitCommit commit) { + CommitI commitObj = + new Commit(commit.getShortHash().getString(), commit.getDate().getTime(), myUsersIndex.put(commit.getAuthor())); + commitObj = myRootHolder.decorateByRoot(commitObj); + return commitObj; + } + + private void initSymbRefs() { + if (mySymbolicRefs == null) { + try { + mySymbolicRefs = myLowLevelAccess.getRefs(); + myMediator.reportSymbolicRefs(myTicket, myRootHolder.getRoot(), mySymbolicRefs); + } + catch (VcsException e) { + myMediator.acceptException(e); + } + } + } + + public SymbolicRefs getSymbolicRefs() { + return mySymbolicRefs; + } +} diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java b/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java index 0d5c30e68302..cea830af3525 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java @@ -20,26 +20,30 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.SLRUMap; import git4idea.history.browser.GitCommit; +import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author irengrig */ public class DetailsCache { private final static int ourSize = 400; - private boolean mySomethingIsMissing; private final SLRUMap, GitCommit> myCache; private final SLRUMap, List> myBranches; private final DetailsLoaderImpl myDetailsLoader; private final ModalityState myModalityState; private AbstractCalledLater myRefresh; + private final Map> myStash; private final Object myLock; public DetailsCache(final Project project, final UIRefresh uiRefresh, final DetailsLoaderImpl detailsLoader, final ModalityState modalityState) { myDetailsLoader = detailsLoader; myModalityState = modalityState; + myStash = new HashMap>(); myRefresh = new AbstractCalledLater(project, myModalityState) { @Override public void run() { @@ -47,7 +51,6 @@ public class DetailsCache { } }; myLock = new Object(); - mySomethingIsMissing = false; myCache = new SLRUMap, GitCommit>(ourSize, 50); myBranches = new SLRUMap, List>(10, 10); } @@ -60,9 +63,6 @@ public class DetailsCache { public void acceptQuestion(final MultiMap hashes) { if (hashes.isEmpty()) return; - synchronized (myLock) { - mySomethingIsMissing = ! hashes.isEmpty(); - } myDetailsLoader.load(hashes); } @@ -71,9 +71,6 @@ public class DetailsCache { for (GitCommit commit : commits) { myCache.put(new Pair(root, commit.getShortHash()), commit); } -// if (mySomethingIsMissing) { - mySomethingIsMissing = false; -// } } myRefresh.callMe(); } @@ -94,9 +91,25 @@ public class DetailsCache { } } - public void resetBranchesCache() { + public void resetAsideCaches() { synchronized (myLock) { myBranches.clear(); + myStash.clear(); + myCache.clear(); + } + } + + public void putStash(final VirtualFile root, final Map stash) { + synchronized (myLock) { + myStash.put(root, stash); + } + } + + @Nullable + public String getStashName(final VirtualFile root, final AbstractHash hash) { + synchronized (myLock) { + final Map map = myStash.get(root); + return map == null ? null : map.get(hash); } } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogFilters.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogFilters.java new file mode 100644 index 000000000000..639386002470 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogFilters.java @@ -0,0 +1,101 @@ +/* + * 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.history.wholeTree; + +import com.google.common.collect.Sets; +import com.intellij.util.Consumer; +import git4idea.history.browser.ChangesFilter; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * @author irengrig + * Date: 2/1/11 + * Time: 7:27 PM + */ +public class GitLogFilters { + @Nullable + private final ChangesFilter.Comment myCommentFilter; + @Nullable + private final Set myCommitterFilters; + @Nullable + private final Set myStructureFilters; + @Nullable + private final List myPossibleReferencies; + + public GitLogFilters() { + this(null, null, null, null); + } + + public GitLogFilters(@Nullable ChangesFilter.Comment commentFilter, + @Nullable Set committerFilters, + @Nullable Set structureFilters, @Nullable List possibleReferencies) { + myCommentFilter = commentFilter; + myCommitterFilters = committerFilters; + myStructureFilters = structureFilters; + myPossibleReferencies = possibleReferencies; + } + + public void callConsumer(final Consumer> consumer, boolean takeComment) { + final List> filters = new ArrayList>(); + if (takeComment && myCommentFilter != null) { + filters.add(Collections.singletonMap(myCommentFilter, myCommentFilter).keySet()); + } + if (myCommitterFilters != null) { + filters.add(myCommitterFilters); + } + if (myStructureFilters != null) { + filters.add(myStructureFilters); + } + final Set> cartesian = Sets.cartesianProduct(filters); + if (cartesian.isEmpty()) { + consumer.consume(Collections.emptyList()); + } else { + for (List list : cartesian) { + consumer.consume(list); + } + } + } + + @Nullable + public ChangesFilter.Comment getCommentFilter() { + return myCommentFilter; + } + + @Nullable + public Set getCommitterFilters() { + return myCommitterFilters; + } + + @Nullable + public Set getStructureFilters() { + return myStructureFilters; + } + + public boolean isEmpty() { + return myCommentFilter == null && (myCommitterFilters == null || myCommitterFilters.isEmpty()) && + (myStructureFilters == null || myStructureFilters.isEmpty()); + } + + @Nullable + public List getPossibleReferencies() { + return myPossibleReferencies; + } +} diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java index b48d540f3782..d3fa323f079e 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java @@ -365,7 +365,7 @@ public class GitLogUI implements Disposable { if (gitCommit == null) return; final List branches = myDetailsCache.getBranches(root, commit.getHash()); if (branches != null) { - myDetails.putBranches(gitCommit, branches); + myDetails.putBranches(root, gitCommit, branches); } final Application application = ApplicationManager.getApplication(); application.executeOnPooledThread(new Runnable() { @@ -383,7 +383,7 @@ public class GitLogUI implements Disposable { if (myDetails.isMissingBranchesInfo() && afterRows.length == 1 && afterRows[0] == rows[0]) { final CommitI afterCommit = myTableModel.getCommitAt(rows[0]); if (afterCommit.holdsDecoration() || (! afterCommit.equals(commit))) return; - myDetails.putBranches(gitCommit, branches); + myDetails.putBranches(root, gitCommit, branches); } } }, ModalityState.NON_MODAL, myProject.getDisposed()); @@ -888,22 +888,27 @@ public class GitLogUI implements Disposable { } private Color getLogicBackground(final boolean isSelected, final int row) { - final Color bkgColor; + Color bkgColor; final CommitI commitAt = myTableModel.getCommitAt(row); GitCommit gitCommit = null; + VirtualFile root = null; if (commitAt != null & (! commitAt.holdsDecoration())) { - gitCommit = myDetailsCache.convert(commitAt.selectRepository(myRootsUnderVcs), commitAt.getHash()); + root = commitAt.selectRepository(myRootsUnderVcs); + gitCommit = myDetailsCache.convert(root, commitAt.getHash()); } if (isSelected) { bkgColor = UIUtil.getTableSelectionBackground(); } else { - if (gitCommit != null && gitCommit.isOnLocal() && gitCommit.isOnTracked()) { - bkgColor = Colors.commonThisBranch; - } else if (gitCommit != null && gitCommit.isOnLocal()) { - bkgColor = Colors.ownThisBranch; - } else { - bkgColor = UIUtil.getTableBackground(); + bkgColor = UIUtil.getTableBackground(); + if (gitCommit != null) { + if (myDetailsCache.getStashName(root, gitCommit.getShortHash()) != null) { + bkgColor = Colors.stashed; + } else if (gitCommit.isOnLocal() && gitCommit.isOnTracked()) { + bkgColor = Colors.commonThisBranch; + } else if (gitCommit.isOnLocal()) { + bkgColor = Colors.ownThisBranch; + } } } return bkgColor; @@ -986,7 +991,7 @@ public class GitLogUI implements Disposable { private void reloadRequest() { myState = StepType.CONTINUE; final int was = myTableModel.getRowCount(); - myDetailsCache.resetBranchesCache(); + myDetailsCache.resetAsideCaches(); final Collection startingPoints = mySelectedBranch == null ? Collections.emptyList() : Collections.singletonList(mySelectedBranch); myDescriptionRenderer.resetIcons(); final boolean commentFilterEmpty = StringUtil.isEmptyOrSpaces(myPreviousFilter); @@ -995,18 +1000,18 @@ public class GitLogUI implements Disposable { if (commentFilterEmpty && (myUserFilterI.myFilter == null)) { myUsersSearchContext.clear(); - myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, Collections.>emptyList(), null); + myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, new GitLogFilters()); } else { - final List> filters = new ArrayList>(); - + ChangesFilter.Comment comment = null; if (! commentFilterEmpty) { final Pair> preparse = preparse(myPreviousFilter); final String first = preparse.getFirst(); - filters.add(Collections.singletonList(new ChangesFilter.Comment(first))); + comment = new ChangesFilter.Comment(first); } + Set userFilters = null; if (myUserFilterI.myFilter != null) { final String[] strings = myUserFilterI.myFilter.split(","); - final List userFilters = new ArrayList(); + userFilters = new HashSet(); for (String string : strings) { string = string.trim(); if (string.length() == 0) continue; @@ -1014,10 +1019,11 @@ public class GitLogUI implements Disposable { final String regexp = StringUtil.escapeToRegexp(string); userFilters.add(new ChangesFilter.Committer(regexp)); } - filters.add(userFilters); } - myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, filters, commentFilterEmpty ? null : myPreviousFilter.split("[\\s]")); + final List possibleReferencies = commentFilterEmpty ? null : Arrays.asList(myPreviousFilter.split("[\\s]")); + myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, new GitLogFilters(comment, userFilters, null, + possibleReferencies)); } updateMoreVisibility(); selectionChanged(); @@ -1031,6 +1037,7 @@ public class GitLogUI implements Disposable { Color local = new Color(117,238,199); Color ownThisBranch = new Color(198,255,226); Color commonThisBranch = new Color(223,223,255); + Color stashed = new Color(225,225,225); } private class MySpecificDetails { @@ -1064,9 +1071,9 @@ public class GitLogUI implements Disposable { return scrollPane; } - public void putBranches(final GitCommit commit, final List branches) { + public void putBranches(VirtualFile root, final GitCommit commit, final List branches) { myMissingBranchesInfo = branches == null; - myJEditorPane.setText(parseDetails(commit, branches)); + myJEditorPane.setText(parseDetails(root, commit, branches)); } public boolean isMissingBranchesInfo() { @@ -1097,11 +1104,11 @@ public class GitLogUI implements Disposable { s.equals(currentBranch)))); } myMarksPanel.repaint(); - myJEditorPane.setText(parseDetails(commit, branches)); + myJEditorPane.setText(parseDetails(root, commit, branches)); } } - private String parseDetails(final GitCommit c, final List branches) { + private String parseDetails(VirtualFile root, final GitCommit c, final List branches) { final String hash = new HtmlHighlighter(c.getHash().getValue()).getResult(); final String author = new HtmlHighlighter(c.getAuthor()).getResult(); final String committer = new HtmlHighlighter(c.getCommitter()).getResult(); @@ -1114,14 +1121,19 @@ public class GitLogUI implements Disposable { }); final StringBuilder sb = new StringBuilder().append("").append(UIUtil.getCssFontDeclaration(UIUtil.getLabelFont())) - .append("" + "
Hash:").append( - hash).append("
Author:") + .append(""); + final String stashName = myDetailsCache.getStashName(root, c.getShortHash()); + if (! StringUtil.isEmptyOrSpaces(stashName)) { + sb.append(""); + } + sb.append("" + "" + "" + "" + ""); sb.append("
").append(stashName).append("
Hash:").append( + hash).append("
Author:") .append(author).append(" (").append(c.getAuthorEmail()).append(") at ") .append(DateFormatUtil.formatPrettyDateTime(c.getAuthorTime())) .append("
Commiter:") .append(committer).append(" (").append(c.getComitterEmail()).append(") at ") .append(DateFormatUtil.formatPrettyDateTime(c.getDate())).append( - "
Description:") + "
Description:") .append(comment).append("
Contained in branches:"); if (branches != null && (! branches.isEmpty())) { @@ -1232,8 +1244,26 @@ public class GitLogUI implements Disposable { @Override public void update(AnActionEvent e) { super.update(e); - final boolean enabled = getSelectedCommitsAndCheck() != null; - e.getPresentation().setEnabled(enabled); + e.getPresentation().setEnabled(enabled()); + } + + private boolean enabled() { + final MultiMap commitsAndCheck = getSelectedCommitsAndCheck(); + if (commitsAndCheck == null) return false; + for (VirtualFile root : commitsAndCheck.keySet()) { + final SymbolicRefs refs = myRefs.get(root); + final String currentBranch = refs == null ? null : (refs.getCurrent() == null ? null : refs.getCurrent().getName()); + if (currentBranch == null) continue; + final Collection commits = commitsAndCheck.get(root); + for (GitCommit commit : commits) { + if (commit.getParentsHashes().size() > 1) return false; + final List branches = myDetailsCache.getBranches(root, commit.getShortHash()); + if (branches != null && branches.contains(currentBranch)) { + return false; + } + } + } + return true; } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/LoadAlgorithm.java b/plugins/git4idea/src/git4idea/history/wholeTree/LoadAlgorithm.java index 71c5c4812a67..dd8f5ecea1e1 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/LoadAlgorithm.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/LoadAlgorithm.java @@ -31,13 +31,13 @@ public class LoadAlgorithm { private final Project myProject; private final List> myLoaders; - private final List myAbstractHashs; + private final List myShortLoaders; private final Continuation myContinuation; - public LoadAlgorithm(final Project project, final List> loaders, final List abstractHashs) { + public LoadAlgorithm(final Project project, final List> loaders, final List shortLoaders) { myProject = project; myLoaders = loaders; - myAbstractHashs = abstractHashs; + myShortLoaders = shortLoaders; myContinuation = new Continuation(myProject, false); } @@ -45,14 +45,14 @@ public class LoadAlgorithm { final ContinuationContext.GatheringContinuationContext initContext = new ContinuationContext.GatheringContinuationContext(); - if (myAbstractHashs != null) { - initContext.last(new TryHashes()); - } for (LoaderAndRefresher loader : myLoaders) { final LoaderFactory factory = new LoaderFactory(loader); final State state = new State(factory); state.scheduleSelf(initContext); } + for (ByRootLoader shortLoader : myShortLoaders) { + initContext.next(shortLoader); + } myContinuation.run(initContext.getList()); } @@ -67,19 +67,6 @@ public class LoadAlgorithm { myContinuation.resume(); } - private class TryHashes extends TaskDescriptor { - private TryHashes() { - super("Try load by hashes", Where.POOLED); - } - - @Override - public void run(ContinuationContext context) { - for (LoaderAndRefresher loader : myLoaders) { - loader.loadByHashesAside(myAbstractHashs); - } - } - } - private static class LoadTaskDescriptor extends TaskDescriptor { protected final State myState; private final LoaderAndRefresher myLoader; diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java b/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java index 764f1724f211..cfb518459f7f 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java @@ -16,10 +16,13 @@ import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.CalledInAwt; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import git4idea.history.NewGitUsersComponent; import git4idea.history.browser.ChangesFilter; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; /** * @author irengrig @@ -47,13 +50,13 @@ public class LoadController implements Loader { public void loadSkeleton(final Mediator.Ticket ticket, final RootsHolder rootsHolder, final Collection startingPoints, - final Collection> filters, - String[] possibleHashes, + final GitLogFilters filters, final LoadGrowthController loadGrowthController) { if (myPreviousAlgorithm != null) { myPreviousAlgorithm.stop(); } final List> list = new ArrayList>(); + final List shortLoaders = new ArrayList(); final List roots = rootsHolder.getRoots(); int i = 0; for (VirtualFile root : roots) { @@ -61,49 +64,23 @@ public class LoadController implements Loader { new LoaderAndRefresherImpl.OneRootHolder(root) : new LoaderAndRefresherImpl.ManyCaseHolder(i, rootsHolder); - if (filters.isEmpty()) { - final LoaderAndRefresherImpl loaderAndRefresher = - new LoaderAndRefresherImpl(ticket, Collections.emptyList(), myMediator, startingPoints, myDetailsCache, - myProject, rootHolder, myUsersIndex, loadGrowthController.getId()); - list.add(loaderAndRefresher); - } else { - Collection> reordered = new ArrayList>(); - final Iterator> iterator = filters.iterator(); - if (iterator.hasNext()) { - final Collection first = iterator.next(); - for (ChangesFilter.Filter filter : first) { - final ArrayList newList = new ArrayList(); - newList.add(filter); - reordered.add(newList); - } - } - while (iterator.hasNext()) { - final Collection next = iterator.next(); - final Collection> reorderedCopy = reordered; - reordered = new ArrayList>(); - for (ChangesFilter.Filter filter : next) { - for (Collection filterCollection : reorderedCopy) { - final ArrayList newList = new ArrayList(filterCollection); - newList.add(filter); - reordered.add(newList); - } - } - } - - for (Collection filterCollection : reordered) { + filters.callConsumer(new Consumer>() { + @Override + public void consume(final List filters) { final LoaderAndRefresherImpl loaderAndRefresher = - new LoaderAndRefresherImpl(ticket, filterCollection, myMediator, startingPoints, myDetailsCache, myProject, rootHolder, myUsersIndex, + new LoaderAndRefresherImpl(ticket, filters, myMediator, startingPoints, myDetailsCache, myProject, rootHolder, myUsersIndex, loadGrowthController.getId()); list.add(loaderAndRefresher); } - } + }, true); + + shortLoaders.add(new ByRootLoader(myProject, rootHolder, myMediator, myDetailsCache, ticket, myUsersIndex, filters, startingPoints)); ++ i; } myUsersComponent.acceptUpdate(myUsersIndex.getKeys()); - //final List abstractHashs = possibleHashes == null ? null : filterNumbers(possibleHashes); - myPreviousAlgorithm = new LoadAlgorithm(myProject, list, possibleHashes == null ? null : Arrays.asList(possibleHashes)); + myPreviousAlgorithm = new LoadAlgorithm(myProject, list, shortLoaders); myPreviousAlgorithm.execute(); } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/Loader.java b/plugins/git4idea/src/git4idea/history/wholeTree/Loader.java index 104b4f6b9790..bb03609c489a 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/Loader.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/Loader.java @@ -15,8 +15,6 @@ */ package git4idea.history.wholeTree; -import git4idea.history.browser.ChangesFilter; - import java.util.Collection; /** @@ -26,7 +24,7 @@ public interface Loader { void loadSkeleton(Mediator.Ticket ticket, RootsHolder rootsHolder, final Collection startingPoints, - final Collection> filters, String[] possibleHashes, LoadGrowthController loadGrowthController); + final GitLogFilters filters, LoadGrowthController loadGrowthController); void resume(); } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresherImpl.java b/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresherImpl.java index bf530341607a..499dd13e0584 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresherImpl.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresherImpl.java @@ -84,7 +84,7 @@ public class LoaderAndRefresherImpl implements LoaderAndRefresher() { @Override public Boolean get() { - return StepType.STOP.equals(myStepType); + return isInterrupted(); } }; myLowLevelAccess = new LowLevelAccessImpl(myProject, myRootHolder.getRoot()); @@ -105,7 +105,7 @@ public class LoaderAndRefresherImpl implements LoaderAndRefresher startingPoints, - final Collection> filters, - @Nullable String[] possibleHashes); + @Nullable final GitLogFilters filters); /** * @return false -> ticket already changed */ StepType appendResult(final Ticket ticket, final List result, - @Nullable final List> parents, - LoadGrowthController.ID id); + @Nullable final List> parents); void reportSymbolicRefs(final Ticket ticket, VirtualFile root, final SymbolicRefs symbolicRefs); diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/MediatorImpl.java b/plugins/git4idea/src/git4idea/history/wholeTree/MediatorImpl.java index 7ae3b6ac489e..c667ba99e843 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/MediatorImpl.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/MediatorImpl.java @@ -19,7 +19,6 @@ import com.intellij.openapi.vcs.CalledInBackground; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.committed.AbstractCalledLater; import com.intellij.openapi.vfs.VirtualFile; -import git4idea.history.browser.ChangesFilter; import git4idea.history.browser.SymbolicRefs; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; @@ -48,13 +47,12 @@ public class MediatorImpl implements Mediator { @CalledInBackground @Override - public StepType appendResult(final Ticket ticket, final List result, - final @Nullable List> parents, LoadGrowthController.ID id) { + public StepType appendResult(final Ticket ticket, final List result, final @Nullable List> parents) { if (! myTicket.equals(ticket)) { return StepType.STOP; } - myTableWrapper.appendResult(ticket, id, result, parents); + myTableWrapper.appendResult(ticket, result, parents); if (myTableWrapper.isSuspend()) { return StepType.PAUSE; } @@ -102,12 +100,11 @@ public class MediatorImpl implements Mediator { @Override public void reload(final RootsHolder rootsHolder, final Collection startingPoints, - final Collection> filters, - String[] possibleHashes) { + final GitLogFilters filters) { myTicket.increment(); myTableWrapper.reset(); myController.reset(); - myLoader.loadSkeleton(myTicket.copy(), rootsHolder, startingPoints, filters, possibleHashes, myController); + myLoader.loadSkeleton(myTicket.copy(), rootsHolder, startingPoints, filters, myController); } public void setLoader(Loader loader) { @@ -152,14 +149,13 @@ public class MediatorImpl implements Mediator { } @CalledInBackground - public void appendResult(final Ticket ticket, final LoadGrowthController.ID id, final List result, + public void appendResult(final Ticket ticket, final List result, final @Nullable List> parents) { new AbstractCalledLater(myProject, myState) { @Override public void run() { if (! myTicket.equals(ticket)) return; myTableModel.appendData(result, parents); - //myController.registerTime(id, result.get(result.size() - 1).getTime()); if (myController.isEmpty()) { myTableModel.restore(); mySuspend = false; @@ -173,7 +169,6 @@ public class MediatorImpl implements Mediator { } } } - //myUIRefresh.linesReloaded(myTableModel.isCut()); myUIRefresh.linesReloaded(mySuspend); if (myController.isEmpty()) { myUIRefresh.finished(); diff --git a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java index d1445dba7bdd..36865fd50fdb 100644 --- a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java +++ b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java @@ -22,23 +22,18 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; 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 com.intellij.ui.DocumentAdapter; +import com.intellij.util.Consumer; import git4idea.GitBranch; import git4idea.GitRevisionNumber; import git4idea.GitVcs; import git4idea.actions.GitShowAllSubmittedFilesAction; -import git4idea.commands.GitCommand; -import git4idea.commands.GitHandlerUtil; -import git4idea.commands.GitLineHandler; -import git4idea.commands.GitLineHandlerAdapter; -import git4idea.commands.GitSimpleHandler; -import git4idea.commands.StringScanner; -import git4idea.config.GitConfigUtil; +import git4idea.commands.*; import git4idea.config.GitVersionSpecialty; import git4idea.i18n.GitBundle; +import git4idea.update.GitStashUtils; import git4idea.validators.GitBranchNameValidator; import org.jetbrains.annotations.NotNull; @@ -48,7 +43,6 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.nio.charset.Charset; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -164,12 +158,12 @@ public class GitUnstashDialog extends DialogWrapper { public void actionPerformed(final ActionEvent e) { final StashInfo stash = getSelectedStash(); if (Messages.YES == Messages.showYesNoDialog(GitUnstashDialog.this.getContentPane(), - GitBundle.message("git.unstash.drop.confirmation.message", stash.myStash, stash.myMessage), - GitBundle.message("git.unstash.drop.confirmation.title", stash.myStash), Messages.getQuestionIcon())) { - ProgressManager.getInstance().run(new Task.Modal(myProject, "Removing stash " + stash.myStash, false) { + GitBundle.message("git.unstash.drop.confirmation.message", stash.getStash(), stash.getMessage()), + GitBundle.message("git.unstash.drop.confirmation.title", stash.getStash()), Messages.getQuestionIcon())) { + ProgressManager.getInstance().run(new Task.Modal(myProject, "Removing stash " + stash.getStash(), false) { @Override public void run(@NotNull ProgressIndicator indicator) { - GitSimpleHandler h = dropHandler(stash.myStash); + GitSimpleHandler h = dropHandler(stash.getStash()); try { h.run(); h.unsilence(); @@ -178,7 +172,7 @@ public class GitUnstashDialog extends DialogWrapper { try { //noinspection HardCodedStringLiteral if (ex.getMessage().startsWith("fatal: Needed a single revision")) { - h = dropHandler(translateStash(stash.myStash)); + h = dropHandler(translateStash(stash.getStash())); h.run(); } else { @@ -209,7 +203,7 @@ public class GitUnstashDialog extends DialogWrapper { public void actionPerformed(final ActionEvent e) { final VirtualFile root = getGitRoot(); String resolvedStash; - String selectedStash = getSelectedStash().myStash; + String selectedStash = getSelectedStash().getStash(); try { resolvedStash = GitRevisionNumber.resolve(myProject, root, selectedStash).asString(); } @@ -312,22 +306,12 @@ public class GitUnstashDialog extends DialogWrapper { private void refreshStashList() { final DefaultListModel listModel = (DefaultListModel)myStashList.getModel(); listModel.clear(); - GitSimpleHandler h = new GitSimpleHandler(myProject, getGitRoot(), GitCommand.STASH); - h.setSilent(true); - h.setNoSSH(true); - h.addParameters("list"); - String out; - try { - h.setCharset(Charset.forName(GitConfigUtil.getLogEncoding(myProject, getGitRoot()))); - out = h.run(); - } - catch (VcsException e) { - GitUIUtil.showOperationError(myProject, e, h.printableCommandLine()); - return; - } - for (StringScanner s = new StringScanner(out); s.hasMoreData();) { - listModel.addElement(new StashInfo(s.boundedToken(':'), s.boundedToken(':'), s.line().trim())); - } + GitStashUtils.loadStashStack(myProject, getGitRoot(), new Consumer() { + @Override + public void consume(StashInfo stashInfo) { + listModel.addElement(stashInfo); + } + }); myBranches.clear(); try { GitBranch.listAsStrings(myProject, getGitRoot(), false, true, myBranches, null); @@ -361,7 +345,7 @@ public class GitUnstashDialog extends DialogWrapper { else { h.addParameters("branch", branch); } - String selectedStash = getSelectedStash().myStash; + String selectedStash = getSelectedStash().getStash(); if (escaped) { selectedStash = translateStash(selectedStash); } else if (GitVersionSpecialty.NEEDS_QUOTES_IN_STASH_NAME.existsIn(myVcs.getVersion())) { // else if, because escaping {} also solves the issue @@ -438,27 +422,4 @@ public class GitUnstashDialog extends DialogWrapper { GitUIUtil.showOperationErrors(project, h.errors(), h.printableCommandLine()); } } - - /** - * Information about one stash. - */ - private static class StashInfo { - private final String myStash; // stash codename (stash@{1}) - private final String myBranch; - private final String myMessage; - private final String myText; // The formatted text representation - - public StashInfo(final String stash, final String branch, final String message) { - myStash = stash; - myBranch = branch; - myMessage = message; - myText = - GitBundle.message("unstash.stashes.item", StringUtil.escapeXml(stash), StringUtil.escapeXml(branch), StringUtil.escapeXml(message)); - } - - @Override - public String toString() { - return myText; - } - } } diff --git a/plugins/git4idea/src/git4idea/ui/StashInfo.java b/plugins/git4idea/src/git4idea/ui/StashInfo.java new file mode 100644 index 000000000000..4628b647c463 --- /dev/null +++ b/plugins/git4idea/src/git4idea/ui/StashInfo.java @@ -0,0 +1,58 @@ +/* + * 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.ui; + +import com.intellij.openapi.util.text.StringUtil; +import git4idea.i18n.GitBundle; + +/** + * Information about one stash. + */ +public class StashInfo { + private final String myStash; // stash codename (stash@{1}) + private final String myBranch; + private final String myMessage; + private final String myText; // The formatted text representation + + public StashInfo(final String stash, final String branch, final String message) { + myStash = stash; + myBranch = branch; + myMessage = message; + myText = + GitBundle.message("unstash.stashes.item", StringUtil.escapeXml(stash), StringUtil.escapeXml(branch), StringUtil.escapeXml(message)); + } + + @Override + public String toString() { + return myText; + } + + public String getStash() { + return myStash; + } + + public String getBranch() { + return myBranch; + } + + public String getMessage() { + return myMessage; + } + + public String getText() { + return myText; + } +} diff --git a/plugins/git4idea/src/git4idea/update/GitStashUtils.java b/plugins/git4idea/src/git4idea/update/GitStashUtils.java index 74731e6a7040..2bc25ebb954c 100644 --- a/plugins/git4idea/src/git4idea/update/GitStashUtils.java +++ b/plugins/git4idea/src/git4idea/update/GitStashUtils.java @@ -28,6 +28,7 @@ import com.intellij.openapi.vcs.changes.shelf.ShelvedChange; import com.intellij.openapi.vcs.changes.shelf.ShelvedChangeList; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import com.intellij.util.ui.UIUtil; import com.intellij.vcsUtil.VcsUtil; import git4idea.GitUtil; @@ -35,7 +36,11 @@ import git4idea.GitVcs; import git4idea.commands.GitCommand; import git4idea.commands.GitFileUtils; import git4idea.commands.GitSimpleHandler; +import git4idea.commands.StringScanner; +import git4idea.config.GitConfigUtil; import git4idea.config.GitVersion; +import git4idea.ui.GitUIUtil; +import git4idea.ui.StashInfo; import git4idea.vfs.GitVFSListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,6 +48,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.event.ChangeEvent; import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; import java.util.*; /** @@ -74,6 +80,30 @@ public class GitStashUtils { return !output.startsWith("No local changes to save"); } + public static void loadStashStack(@NotNull Project project, @NotNull VirtualFile root, Consumer consumer) { + loadStashStack(project, root, Charset.forName(GitConfigUtil.getLogEncoding(project, root)), consumer); + } + + public static void loadStashStack(@NotNull Project project, @NotNull VirtualFile root, final Charset charset, + final Consumer consumer) { + GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.STASH); + h.setSilent(true); + h.setNoSSH(true); + h.addParameters("list"); + String out; + try { + h.setCharset(charset); + out = h.run(); + } + catch (VcsException e) { + GitUIUtil.showOperationError(project, e, h.printableCommandLine()); + return; + } + for (StringScanner s = new StringScanner(out); s.hasMoreData();) { + consumer.consume(new StashInfo(s.boundedToken(':'), s.boundedToken(':'), s.line().trim())); + } + } + /** * Create stash for later use (it ignores exit code 1 [merge conflict]) *