Merge "Support branch specific labels"
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 7671767..74f56b3 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -37,6 +37,13 @@
   which buck
 ----
 
+If you plan to use the link:#buck-daemon[Buck daemon] add a symbolic
+link in `~/bin` to the buckd executable:
+
+----
+  ln -s `pwd`/bin/buckd ~/bin/
+----
+
 
 [[eclipse]]
 Eclipse Integration
@@ -289,7 +296,6 @@
  )
 ----
 
-
 Caching Build Results
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -306,6 +312,29 @@
   EOF
 ----
 
+[[buck-daemon]]
+Using Buck daemon
+~~~~~~~~~~~~~~~~~
+
+Buck ships with daemon command `buckd`, which uses
+link:https://github.com/martylamb/nailgun[Nailgun] protocol for running
+Java programs from the command line without incurring the JVM startup
+overhead. Using a Buck daemon can save significant amounts of time as it
+avoids the overhead of starting a Java virtual machine, loading the buck class
+files and parsing the build files for each command. It is safe to run several
+buck daemons started from different project directories and they will not
+interfere with each other. Buck's documentation covers daemon in
+http://facebook.github.io/buck/command/buckd.html[buckd]. The trivial case is
+to run `buckd` from the project's root directory and use `buck` as usually:
+
+----
+$>buckd
+$>buck build gerrit
+Using buckd.
+[-] PARSING BUILD FILES...FINISHED 0.6s
+[-] BUILDING...FINISHED 0.2s
+----
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index d7e9fd7..f1c13f8 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -168,6 +168,11 @@
 +
 Changes that match 'MESSAGE' arbitrary string in the commit message body.
 
+[[comment]]
+comment:'TEXT'::
++
+Changes that match 'TEXT' string in any comment left by a reviewer.
+
 [[file]]
 file:^'REGEX'::
 +
@@ -187,12 +192,6 @@
 ones using a bracket expression). For example, to match all XML
 files named like 'name1.xml', 'name2.xml', and 'name3.xml' use
 `file:"^name[1-3].xml"`.
-+
-Currently this operator is only available on a watched project
-and may not be used in the search bar. The same holds true for web UI
-"My > Watched Changes", i. e. file:regex is used over the is:watched
-expression. It never produces any results, because the error message:
-"operator not permitted here: file:regex" is suppressed.
 
 [[has]]
 has:draft::
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index f56ef07..cb785b9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -114,6 +114,12 @@
     return create(username, null, username, (String[]) null);
   }
 
+  public TestAccount admin()
+      throws UnsupportedEncodingException, OrmException, JSchException {
+    return create("admin", "admin@example.com", "Administrator",
+      "Administrators");
+  }
+
   private AccountExternalId.Key getEmailKey(String email) {
     return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index a9ce827..de70796 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -84,7 +84,8 @@
     File tmp = TempFileUtil.createTempDirectory();
     Init init = new Init();
     int rc = init.main(new String[] {
-        "-d", tmp.getPath(), "--batch", "--no-auto-start"});
+        "-d", tmp.getPath(), "--batch", "--no-auto-start",
+        "--skip-plugins"});
     if (rc != 0) {
       throw new RuntimeException("Couldn't initialize site");
     }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectDeletedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectDeletedListener.java
new file mode 100644
index 0000000..b03f99c
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectDeletedListener.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.events;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/** Notified whenever a project is deleted on the master. */
+@ExtensionPoint
+public interface ProjectDeletedListener {
+  public interface Event {
+    String getProjectName();
+  }
+
+  void onProjectDeleted(Event event);
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
index c07ee51..0c7df3e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.IncludedInResolver;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -29,23 +30,15 @@
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 
 /** Creates a {@link IncludedInDetail} of a {@link Change}. */
 class IncludedInDetailFactory extends Handler<IncludedInDetail> {
-  private static final Logger log =
-      LoggerFactory.getLogger(IncludedInDetailFactory.class);
 
   interface Factory {
     IncludedInDetailFactory create(Change.Id id);
@@ -56,7 +49,6 @@
   private final GitRepositoryManager repoManager;
   private final Change.Id changeId;
 
-  private IncludedInDetail detail;
   private ChangeControl control;
 
   @Inject
@@ -92,11 +84,7 @@
           throw new InvalidRevisionException();
         }
 
-        detail = new IncludedInDetail();
-        detail.setBranches(includedIn(repo, rw, rev, Constants.R_HEADS));
-        detail.setTags(includedIn(repo, rw, rev, Constants.R_TAGS));
-
-        return detail;
+        return IncludedInResolver.resolve(repo, rw, rev);
       } finally {
         rw.release();
       }
@@ -104,32 +92,4 @@
       repo.close();
     }
   }
-
-  private List<String> includedIn(final Repository repo, final RevWalk rw,
-      final RevCommit rev, final String namespace) throws IOException,
-      MissingObjectException, IncorrectObjectTypeException {
-    final List<String> result = new ArrayList<String>();
-    for (final Ref ref : repo.getRefDatabase().getRefs(namespace).values()) {
-      final RevCommit tip;
-      try {
-        tip = rw.parseCommit(ref.getObjectId());
-      } catch (IncorrectObjectTypeException notCommit) {
-        // Its OK for a tag reference to point to a blob or a tree, this
-        // is common in the Linux kernel or git.git repository.
-        //
-        continue;
-      } catch (MissingObjectException notHere) {
-        // Log the problem with this branch, but keep processing.
-        //
-        log.warn("Reference " + ref.getName() + " in " + repo.getDirectory()
-            + " points to dangling object " + ref.getObjectId());
-        continue;
-      }
-
-      if (rw.isMergedInto(rev, tip)) {
-        result.add(ref.getName().substring(namespace.length()));
-      }
-    }
-    return result;
-  }
-}
+}
\ No newline at end of file
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 286565b..c98bd5d 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -48,6 +48,7 @@
 import com.google.inject.assistedinject.AssistedInject;
 
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.util.CharArraySet;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Field.Store;
@@ -112,7 +113,7 @@
 
   private static IndexWriterConfig getIndexWriterConfig(Config cfg, String name) {
     IndexWriterConfig writerConfig = new IndexWriterConfig(LUCENE_VERSION,
-        new StandardAnalyzer(LUCENE_VERSION));
+        new StandardAnalyzer(LUCENE_VERSION, CharArraySet.EMPTY_SET));
     writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
     double m = 1 << 20;
     writerConfig.setRAMBufferSizeMB(cfg.getLong("index", name, "ramBufferSize",
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index 0f0b208..ae1ab71 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -70,6 +70,9 @@
   @Option(name = "--no-auto-start", usage = "Don't automatically start daemon after init")
   private boolean noAutoStart;
 
+  @Option(name = "--skip-plugins", usage = "Don't install plugin")
+  private boolean skipPlugins = false;
+
   @Option(name = "--list-plugins", usage = "List available plugins")
   private boolean listPlugins;
 
@@ -90,22 +93,25 @@
     ErrorLogFile.errorOnlyConsole();
 
     final SiteInit init = createSiteInit();
-    final List<PluginData> plugins = InitPlugins.listPlugins(init.site);
-    ConsoleUI ui = ConsoleUI.getInstance(false);
-    verifyInstallPluginList(ui, plugins);
-    if (listPlugins) {
-      if (!plugins.isEmpty()) {
-        ui.message("Available plugins:\n");
-        for (PluginData plugin : plugins) {
-          ui.message(" * %s version %s\n", plugin.name, plugin.version);
+    if (!skipPlugins) {
+      final List<PluginData> plugins = InitPlugins.listPlugins(init.site);
+      ConsoleUI ui = ConsoleUI.getInstance(false);
+      verifyInstallPluginList(ui, plugins);
+      if (listPlugins) {
+        if (!plugins.isEmpty()) {
+          ui.message("Available plugins:\n");
+          for (PluginData plugin : plugins) {
+            ui.message(" * %s version %s\n", plugin.name, plugin.version);
+          }
+        } else {
+          ui.message("No plugins found.\n");
         }
-      } else {
-        ui.message("No plugins found.\n");
+        return 0;
       }
-      return 0;
     }
 
     init.flags.autoStart = !noAutoStart && init.site.isNew;
+    init.flags.skipPlugins = skipPlugins;
 
     final SiteRun run;
     try {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
index adbb03a..267b41a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
@@ -34,10 +34,14 @@
   /** Run the daemon (and open the web UI in a browser) after initialization. */
   public boolean autoStart;
 
+  /** Skip plugins */
+  public boolean skipPlugins;
+
   public final FileBasedConfig cfg;
   public final FileBasedConfig sec;
   public final List<String> installPlugins;
 
+
   @Inject
   InitFlags(final SitePaths site,
       final @InstallPlugins List<String> installPlugins) throws IOException,
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index e71f3d1..1c2d024 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -75,6 +75,10 @@
     mkdir(site.data_dir);
 
     for (InitStep step : steps) {
+      if (step instanceof InitPlugins
+          && flags.skipPlugins) {
+        continue;
+      }
       step.run();
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
new file mode 100644
index 0000000..b02f6f0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -0,0 +1,159 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.google.gerrit.server.change;
+
+import com.google.gerrit.common.data.IncludedInDetail;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Resolve in which tags and branches a commit is included.
+ */
+public class IncludedInResolver {
+
+  private static final Logger log = LoggerFactory
+      .getLogger(IncludedInResolver.class);
+
+  public static IncludedInDetail resolve(final Repository repo,
+      final RevWalk rw, final RevCommit commit) throws IOException {
+
+    Set<Ref> tags =
+        new HashSet<Ref>(repo.getRefDatabase().getRefs(Constants.R_TAGS)
+            .values());
+    Set<Ref> branches =
+        new HashSet<Ref>(repo.getRefDatabase().getRefs(Constants.R_HEADS)
+            .values());
+    Set<Ref> allTagsAndBranches = new HashSet<Ref>();
+    allTagsAndBranches.addAll(tags);
+    allTagsAndBranches.addAll(branches);
+    Set<Ref> allMatchingTagsAndBranches =
+        includedIn(repo, rw, commit, allTagsAndBranches);
+
+    IncludedInDetail detail = new IncludedInDetail();
+    detail
+        .setBranches(getMatchingRefNames(allMatchingTagsAndBranches, branches));
+    detail.setTags(getMatchingRefNames(allMatchingTagsAndBranches, tags));
+
+    return detail;
+  }
+
+  /**
+   * Resolves which tip refs include the target commit.
+   */
+  private static Set<Ref> includedIn(final Repository repo, final RevWalk rw,
+      final RevCommit target, final Set<Ref> tipRefs) throws IOException,
+      MissingObjectException, IncorrectObjectTypeException {
+
+    Set<Ref> result = new HashSet<Ref>();
+
+    Map<RevCommit, Set<Ref>> tipsAndCommits = parseCommits(repo, rw, tipRefs);
+
+    List<RevCommit> tips = new ArrayList<RevCommit>(tipsAndCommits.keySet());
+    Collections.sort(tips, new Comparator<RevCommit>() {
+      @Override
+      public int compare(RevCommit c1, RevCommit c2) {
+        return c1.getCommitTime() - c2.getCommitTime();
+      }
+    });
+
+    Set<RevCommit> targetReachableFrom = new HashSet<RevCommit>();
+    targetReachableFrom.add(target);
+
+    for (RevCommit tip : tips) {
+      boolean commitFound = false;
+      rw.resetRetain(RevFlag.UNINTERESTING);
+      rw.markStart(tip);
+      for (RevCommit commit : rw) {
+        if (targetReachableFrom.contains(commit)) {
+          commitFound = true;
+          targetReachableFrom.add(tip);
+          result.addAll(tipsAndCommits.get(tip));
+          break;
+        }
+      }
+      if (!commitFound) {
+        rw.markUninteresting(tip);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the short names of refs which are as well in the matchingRefs list
+   * as well as in the allRef list.
+   */
+  private static List<String> getMatchingRefNames(Set<Ref> matchingRefs,
+      Set<Ref> allRefs) {
+    List<String> refNames = new ArrayList<String>();
+    for (Ref matchingRef : matchingRefs) {
+      if (allRefs.contains(matchingRef)) {
+        refNames.add(Repository.shortenRefName(matchingRef.getName()));
+      }
+    }
+    return refNames;
+  }
+
+  /**
+   * Parse commit of ref and store the relation between ref and commit.
+   */
+  private static Map<RevCommit, Set<Ref>> parseCommits(final Repository repo,
+      final RevWalk rw, final Set<Ref> refs) throws IOException {
+    Map<RevCommit, Set<Ref>> result = new HashMap<RevCommit, Set<Ref>>();
+    for (Ref ref : refs) {
+      final RevCommit commit;
+      try {
+        commit = rw.parseCommit(ref.getObjectId());
+      } catch (IncorrectObjectTypeException notCommit) {
+        // Its OK for a tag reference to point to a blob or a tree, this
+        // is common in the Linux kernel or git.git repository.
+        //
+        continue;
+      } catch (MissingObjectException notHere) {
+        // Log the problem with this branch, but keep processing.
+        //
+        log.warn("Reference " + ref.getName() + " in " + repo.getDirectory()
+            + " points to dangling object " + ref.getObjectId());
+        continue;
+      }
+      Set<Ref> relatedRefs = result.get(commit);
+      if (relatedRefs == null) {
+        relatedRefs = new HashSet<Ref>();
+        result.put(commit, relatedRefs);
+      }
+      relatedRefs.add(ref);
+    }
+    return result;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index 5a6d441..f41d97d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -48,6 +48,7 @@
 import com.google.gerrit.server.change.ReviewerJson.ReviewerInfo;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.group.GroupsCollection;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.AddReviewerSender;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchProjectException;
@@ -94,6 +95,7 @@
   private final ChangeHooks hooks;
   private final AccountCache accountCache;
   private final ReviewerJson json;
+  private final ChangeIndexer indexer;
 
   @Inject
   PostReviewers(AccountsCollection accounts,
@@ -108,7 +110,8 @@
       @GerritServerConfig Config cfg,
       ChangeHooks hooks,
       AccountCache accountCache,
-      ReviewerJson json) {
+      ReviewerJson json,
+      ChangeIndexer indexer) {
     this.accounts = accounts;
     this.reviewerFactory = reviewerFactory;
     this.addReviewerSenderFactory = addReviewerSenderFactory;
@@ -122,6 +125,7 @@
     this.hooks = hooks;
     this.accountCache = accountCache;
     this.json = json;
+    this.indexer = indexer;
   }
 
   @Override
@@ -255,6 +259,7 @@
       db.rollback();
     }
 
+    indexer.index(rsrc.getChange());
     accountLoaderFactory.create(true).fill(result.reviewers);
     postAdd(rsrc.getChange(), result);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
index 2fbc79f..fd0596e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
@@ -15,18 +15,11 @@
 
 package com.google.gerrit.server.changedetail;
 
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
-
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.ReviewResult;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
@@ -35,7 +28,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.CreateChangeSender;
-import com.google.gerrit.server.mail.MailUtil.MailRecipients;
+import com.google.gerrit.server.mail.PatchSetNotificationSender;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
@@ -46,23 +39,10 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.FooterLine;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.Callable;
 
 public class PublishDraft implements Callable<ReviewResult> {
-  private static final Logger log =
-      LoggerFactory.getLogger(PublishDraft.class);
-
   public interface Factory {
     PublishDraft create(PatchSet.Id patchSetId);
   }
@@ -70,13 +50,8 @@
   private final ChangeControl.Factory changeControlFactory;
   private final ReviewDb db;
   private final ChangeHooks hooks;
-  private final GitRepositoryManager repoManager;
-  private final PatchSetInfoFactory patchSetInfoFactory;
-  private final ApprovalsUtil approvalsUtil;
-  private final AccountResolver accountResolver;
-  private final CreateChangeSender.Factory createChangeSenderFactory;
-  private final ReplacePatchSetSender.Factory replacePatchSetFactory;
   private final ChangeIndexer indexer;
+  private final PatchSetNotificationSender sender;
 
   private final PatchSet.Id patchSetId;
 
@@ -90,17 +65,13 @@
       final CreateChangeSender.Factory createChangeSenderFactory,
       final ReplacePatchSetSender.Factory replacePatchSetFactory,
       final ChangeIndexer indexer,
+      final PatchSetNotificationSender sender,
       @Assisted final PatchSet.Id patchSetId) {
     this.changeControlFactory = changeControlFactory;
     this.db = db;
     this.hooks = hooks;
-    this.repoManager = repoManager;
-    this.patchSetInfoFactory = patchSetInfoFactory;
-    this.approvalsUtil = approvalsUtil;
-    this.accountResolver = accountResolver;
-    this.createChangeSenderFactory = createChangeSenderFactory;
-    this.replacePatchSetFactory = replacePatchSetFactory;
     this.indexer = indexer;
+    this.sender = sender;
 
     this.patchSetId = patchSetId;
   }
@@ -153,7 +124,7 @@
         indexer.index(updatedChange);
         hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, db);
 
-        sendNotifications(control.getChange().getStatus() == Change.Status.DRAFT,
+        sender.send(control.getChange().getStatus() == Change.Status.DRAFT,
             (IdentifiedUser) control.getCurrentUser(), updatedChange, updatedPatchSet,
             labelTypes);
       }
@@ -161,66 +132,4 @@
 
     return result;
   }
-
-  private void sendNotifications(final boolean newChange,
-      final IdentifiedUser currentUser, final Change updatedChange,
-      final PatchSet updatedPatchSet, final LabelTypes labelTypes)
-      throws OrmException, IOException, PatchSetInfoNotAvailableException {
-    final Repository git = repoManager.openRepository(updatedChange.getProject());
-    try {
-      final RevWalk revWalk = new RevWalk(git);
-      final RevCommit commit;
-      try {
-        commit = revWalk.parseCommit(ObjectId.fromString(updatedPatchSet.getRevision().get()));
-      } finally {
-        revWalk.release();
-      }
-      final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
-      final List<FooterLine> footerLines = commit.getFooterLines();
-      final Account.Id me = currentUser.getAccountId();
-      final MailRecipients recipients =
-          getRecipientsFromFooters(accountResolver, updatedPatchSet, footerLines);
-      recipients.remove(me);
-
-      if (newChange) {
-        approvalsUtil.addReviewers(db, labelTypes, updatedChange, updatedPatchSet, info,
-            recipients.getReviewers(), Collections.<Account.Id> emptySet());
-        try {
-          CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
-          cm.setFrom(me);
-          cm.setPatchSet(updatedPatchSet, info);
-          cm.addReviewers(recipients.getReviewers());
-          cm.addExtraCC(recipients.getCcOnly());
-          cm.send();
-        } catch (Exception e) {
-          log.error("Cannot send email for new change " + updatedChange.getId(), e);
-        }
-      } else {
-        final List<PatchSetApproval> patchSetApprovals =
-            db.patchSetApprovals().byChange(updatedChange.getId()).toList();
-        final MailRecipients oldRecipients =
-            getRecipientsFromApprovals(patchSetApprovals);
-        approvalsUtil.addReviewers(db, labelTypes, updatedChange, updatedPatchSet, info,
-            recipients.getReviewers(), oldRecipients.getAll());
-        final ChangeMessage msg =
-            new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
-                ChangeUtil.messageUUID(db)), me,
-                updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
-        msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
-        try {
-          ReplacePatchSetSender cm = replacePatchSetFactory.create(updatedChange);
-          cm.setFrom(me);
-          cm.setPatchSet(updatedPatchSet, info);
-          cm.setChangeMessage(msg);
-          cm.addReviewers(recipients.getReviewers());
-          cm.addExtraCC(recipients.getCcOnly());
-          cm.send();
-        } catch (Exception e) {
-          log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
-        }
-      }
-    } finally {
-      git.close();
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 3f7acf6..50b8075 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
@@ -231,6 +232,7 @@
     DynamicMap.mapOf(binder(), CapabilityDefinition.class);
     DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
     DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
+    DynamicSet.setOf(binder(), ProjectDeletedListener.class);
     DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);
     DynamicSet.setOf(binder(), ChangeListener.class);
     DynamicSet.setOf(binder(), CommitValidationListener.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
index dd94577..ccd6c89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
@@ -126,7 +126,10 @@
 
   @Override
   public int getCost() {
-    return pred.getCost();
+    // Index queries are assumed to be cheaper than any other type of query, so
+    // so try to make sure they get picked. Note that pred's cost may be higher
+    // because it doesn't know whether it's being used in an index query or not.
+    return 0;
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
new file mode 100644
index 0000000..3484dd8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
@@ -0,0 +1,149 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.google.gerrit.server.mail;
+
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.mail.MailUtil.MailRecipients;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterLine;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class PatchSetNotificationSender {
+  private static final Logger log =
+      LoggerFactory.getLogger(PatchSetNotificationSender.class);
+
+  private final ReviewDb db;
+  private final GitRepositoryManager repoManager;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final ApprovalsUtil approvalsUtil;
+  private final AccountResolver accountResolver;
+  private final CreateChangeSender.Factory createChangeSenderFactory;
+  private final ReplacePatchSetSender.Factory replacePatchSetFactory;
+
+  @Inject
+  public PatchSetNotificationSender(ReviewDb db,
+      ChangeHooks hooks,
+      GitRepositoryManager repoManager,
+      PatchSetInfoFactory patchSetInfoFactory,
+      ApprovalsUtil approvalsUtil,
+      AccountResolver accountResolver,
+      CreateChangeSender.Factory createChangeSenderFactory,
+      ReplacePatchSetSender.Factory replacePatchSetFactory,
+      ChangeIndexer indexer) {
+    this.db = db;
+    this.repoManager = repoManager;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+    this.approvalsUtil = approvalsUtil;
+    this.accountResolver = accountResolver;
+    this.createChangeSenderFactory = createChangeSenderFactory;
+    this.replacePatchSetFactory = replacePatchSetFactory;
+  }
+
+  public void send(final boolean newChange,
+      final IdentifiedUser currentUser, final Change updatedChange,
+      final PatchSet updatedPatchSet, final LabelTypes labelTypes)
+      throws OrmException, IOException, PatchSetInfoNotAvailableException {
+    final Repository git = repoManager.openRepository(updatedChange.getProject());
+    try {
+      final RevWalk revWalk = new RevWalk(git);
+      final RevCommit commit;
+      try {
+        commit = revWalk.parseCommit(ObjectId.fromString(
+            updatedPatchSet.getRevision().get()));
+      } finally {
+        revWalk.release();
+      }
+      final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
+      final List<FooterLine> footerLines = commit.getFooterLines();
+      final Account.Id me = currentUser.getAccountId();
+      final MailRecipients recipients =
+          getRecipientsFromFooters(accountResolver, updatedPatchSet, footerLines);
+      recipients.remove(me);
+
+      if (newChange) {
+        approvalsUtil.addReviewers(db, labelTypes,
+            updatedChange, updatedPatchSet, info,
+            recipients.getReviewers(), Collections.<Account.Id> emptySet());
+        try {
+          CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
+          cm.setFrom(me);
+          cm.setPatchSet(updatedPatchSet, info);
+          cm.addReviewers(recipients.getReviewers());
+          cm.addExtraCC(recipients.getCcOnly());
+          cm.send();
+        } catch (Exception e) {
+          log.error("Cannot send email for new change " + updatedChange.getId(), e);
+        }
+      } else {
+        final List<PatchSetApproval> patchSetApprovals =
+            db.patchSetApprovals().byChange(
+                updatedChange.getId()).toList();
+        final MailRecipients oldRecipients =
+            getRecipientsFromApprovals(patchSetApprovals);
+        approvalsUtil.addReviewers(db, labelTypes, updatedChange,
+            updatedPatchSet, info, recipients.getReviewers(),
+            oldRecipients.getAll());
+        final ChangeMessage msg =
+            new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
+                ChangeUtil.messageUUID(db)), me,
+                updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
+        msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
+        try {
+          ReplacePatchSetSender cm = replacePatchSetFactory.create(updatedChange);
+          cm.setFrom(me);
+          cm.setPatchSet(updatedPatchSet, info);
+          cm.setChangeMessage(msg);
+          cm.addReviewers(recipients.getReviewers());
+          cm.addExtraCC(recipients.getCcOnly());
+          cm.send();
+        } catch (Exception e) {
+          log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
+        }
+      }
+    } finally {
+      git.close();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
index 0555fc7..5282b49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -162,12 +162,15 @@
   }
 
   private ChangeDataSource source() {
+    int minCost = Integer.MAX_VALUE;
+    Predicate<ChangeData> s = null;
     for (Predicate<ChangeData> p : getChildren()) {
-      if (p instanceof ChangeDataSource) {
-        return (ChangeDataSource) p;
+      if (p instanceof ChangeDataSource && p.getCost() < minCost) {
+        s = p;
+        minCost = p.getCost();
       }
     }
-    return null;
+    return (ChangeDataSource) s;
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 72c7138..bdce7f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gson.Gson;
 import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -229,7 +230,7 @@
     final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
     int cnt = queries.size();
 
-    // Begin all queries, possibly asynchronously.
+    // Parse and rewrite all queries.
     List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
     List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
     for (int i = 0; i < cnt; i++) {
@@ -251,9 +252,16 @@
       sources.add(a);
     }
 
+    // Run each query asynchronously, if supported.
+    List<ResultSet<ChangeData>> matches = Lists.newArrayListWithCapacity(cnt);
+    for (ChangeDataSource s : sources) {
+      matches.add(s.read());
+    }
+    sources = null;
+
     List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
     for (int i = 0; i < cnt; i++) {
-      List<ChangeData> results = Lists.newArrayList(sources.get(i).read());
+      List<ChangeData> results = matches.get(i).toList();
       Collections.sort(results, sortkeyAfter != null ? cmpAfter : cmpBefore);
       if (results.size() > maxLimit) {
         moreResults = true;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
new file mode 100644
index 0000000..ae58819
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
@@ -0,0 +1,205 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// 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.google.gerrit.server.change;
+
+import com.google.gerrit.common.data.IncludedInDetail;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class IncludedInResolverTest extends RepositoryTestCase {
+
+  // Branch names
+  private static final String BRANCH_MASTER = "master";
+  private static final String BRANCH_1_0 = "rel-1.0";
+  private static final String BRANCH_1_3 = "rel-1.3";
+  private static final String BRANCH_2_0 = "rel-2.0";
+  private static final String BRANCH_2_5 = "rel-2.5";
+
+  // Tag names
+  private static final String TAG_1_0 = "1.0";
+  private static final String TAG_1_0_1 = "1.0.1";
+  private static final String TAG_1_3 = "1.3";
+  private static final String TAG_2_0_1 = "2.0.1";
+  private static final String TAG_2_0 = "2.0";
+  private static final String TAG_2_5 = "2.5";
+  private static final String TAG_2_5_ANNOTATED = "2.5-annotated";
+  private static final String TAG_2_5_ANNOTATED_TWICE = "2.5-annotated_twice";
+
+  // Commits
+  private RevCommit commit_initial;
+  private RevCommit commit_v1_3;
+  private RevCommit commit_v2_5;
+
+  private List<String> expTags = new ArrayList<String>();
+  private List<String> expBranches = new ArrayList<String>();
+
+  private RevWalk revWalk;
+
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+
+    /*- The following graph will be created.
+
+      o   tag 2.5, 2.5_annotated, 2.5_annotated_twice
+      |\
+      | o tag 2.0.1
+      | o tag 2.0
+      o | tag 1.3
+      |/
+      o   c3
+
+      | o tag 1.0.1
+      |/
+      o   tag 1.0
+      o   c2
+      o   c1
+
+     */
+
+    Git git = new Git(db);
+    revWalk = new RevWalk(db);
+    // Version 1.0
+    commit_initial = git.commit().setMessage("c1").call();
+    git.commit().setMessage("c2").call();
+    RevCommit commit_v1_0 = git.commit().setMessage("version 1.0").call();
+    git.tag().setName(TAG_1_0).setObjectId(commit_v1_0).call();
+    RevCommit c3 = git.commit().setMessage("c3").call();
+    // Version 1.01
+    createAndCheckoutBranch(commit_v1_0, BRANCH_1_0);
+    RevCommit commit_v1_0_1 =
+        git.commit().setMessage("verREFS_HEADS_RELsion 1.0.1").call();
+    git.tag().setName(TAG_1_0_1).setObjectId(commit_v1_0_1).call();
+    // Version 1.3
+    createAndCheckoutBranch(c3, BRANCH_1_3);
+    commit_v1_3 = git.commit().setMessage("version 1.3").call();
+    git.tag().setName(TAG_1_3).setObjectId(commit_v1_3).call();
+    // Version 2.0
+    createAndCheckoutBranch(c3, BRANCH_2_0);
+    RevCommit commit_v2_0 = git.commit().setMessage("version 2.0").call();
+    git.tag().setName(TAG_2_0).setObjectId(commit_v2_0).call();
+    RevCommit commit_v2_0_1 = git.commit().setMessage("version 2.0.1").call();
+    git.tag().setName(TAG_2_0_1).setObjectId(commit_v2_0_1).call();
+
+    // Version 2.5
+    createAndCheckoutBranch(commit_v1_3, BRANCH_2_5);
+    git.merge().include(commit_v2_0_1).setCommit(false)
+        .setFastForward(FastForwardMode.NO_FF).call();
+    commit_v2_5 = git.commit().setMessage("version 2.5").call();
+    git.tag().setName(TAG_2_5).setObjectId(commit_v2_5).setAnnotated(false)
+        .call();
+    Ref ref_tag_2_5_annotated =
+        git.tag().setName(TAG_2_5_ANNOTATED).setObjectId(commit_v2_5)
+            .setAnnotated(true).call();
+    RevTag tag_2_5_annotated =
+        revWalk.parseTag(ref_tag_2_5_annotated.getObjectId());
+    git.tag().setName(TAG_2_5_ANNOTATED_TWICE).setObjectId(tag_2_5_annotated)
+        .setAnnotated(true).call();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    revWalk.release();
+    super.tearDown();
+  }
+
+  @Test
+  public void resolveLatestCommit() throws Exception {
+    // Check tip commit
+    IncludedInDetail detail = resolve(commit_v2_5);
+
+    // Check that only tags and branches which refer the tip are returned
+    expTags.add(TAG_2_5);
+    expTags.add(TAG_2_5_ANNOTATED);
+    expTags.add(TAG_2_5_ANNOTATED_TWICE);
+    assertEquals(expTags, detail.getTags());
+    expBranches.add(BRANCH_2_5);
+    assertEquals(expBranches, detail.getBranches());
+  }
+
+  @Test
+  public void resolveFirstCommit() throws Exception {
+    // Check first commit
+    IncludedInDetail detail = resolve(commit_initial);
+
+    // Check whether all tags and branches are returned
+    expTags.add(TAG_1_0);
+    expTags.add(TAG_1_0_1);
+    expTags.add(TAG_1_3);
+    expTags.add(TAG_2_0);
+    expTags.add(TAG_2_0_1);
+    expTags.add(TAG_2_5);
+    expTags.add(TAG_2_5_ANNOTATED);
+    expTags.add(TAG_2_5_ANNOTATED_TWICE);
+    assertEquals(expTags, detail.getTags());
+
+    expBranches.add(BRANCH_MASTER);
+    expBranches.add(BRANCH_1_0);
+    expBranches.add(BRANCH_1_3);
+    expBranches.add(BRANCH_2_0);
+    expBranches.add(BRANCH_2_5);
+    assertEquals(expBranches, detail.getBranches());
+  }
+
+  @Test
+  public void resolveBetwixtCommit() throws Exception {
+    // Check a commit somewhere in the middle
+    IncludedInDetail detail = resolve(commit_v1_3);
+
+    // Check whether all succeeding tags and branches are returned
+    expTags.add(TAG_1_3);
+    expTags.add(TAG_2_5);
+    expTags.add(TAG_2_5_ANNOTATED);
+    expTags.add(TAG_2_5_ANNOTATED_TWICE);
+    assertEquals(expTags, detail.getTags());
+
+    expBranches.add(BRANCH_1_3);
+    expBranches.add(BRANCH_2_5);
+    assertEquals(expBranches, detail.getBranches());
+  }
+
+  private IncludedInDetail resolve(RevCommit commit) throws Exception {
+    return IncludedInResolver.resolve(db, revWalk, commit);
+  }
+
+  private void assertEquals(List<String> list1, List<String> list2) {
+    Collections.sort(list1);
+    Collections.sort(list2);
+    Assert.assertEquals(list1, list2);
+  }
+
+  private void createAndCheckoutBranch(ObjectId objectId, String branchName)
+      throws IOException {
+    String fullBranchName = "refs/heads/" + branchName;
+    super.createBranch(objectId, fullBranchName);
+    super.checkoutBranch(fullBranchName);
+  }
+}