Merge "Create and use a StarCache in the ChangeScreen"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index e7379ea..83c0789 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -712,6 +712,7 @@
 `Code Review`, above) must enable submit, and also must not block it.
 See above for details on each category.
 
+
 [[category_makeoneup]]
 Your Category Here
 ~~~~~~~~~~~~~~~~~~
@@ -780,6 +781,30 @@
 clear` is required to enable submit.
 
 
+Examples of typical roles in a project
+--------------------------------------
+
+Below follows a set of typical roles on a server and which access
+rights these roles typically should be granted. You may see them as
+general guide lines for a typical way to set up your project on a
+brand new Gerrit instance.
+
+[[examples_contributor]]
+Contributor
+~~~~~~~~~~~
+
+This is the typical user on a public server. They are able to read
+your project and upload new changes to it. They are able to give
+feedback on other changes as well, but are unable to block or approve
+any changes.
+
+Suggested access rights to grant:
+
+* <<category_read,`Read`>> on 'refs/heads/\*' and 'refs/tags/*'
+* <<category_push,`Push`>> to 'refs/for/refs/heads/\*' and 'refs/changes/*'
+* <<category_label-Code-Review,`Code review`>> with range '-1' to '+1'
+
+
 [[conversion_table]]
 Conversion table from 2.1.x series to 2.2.x series
 --------------------------------------------------
@@ -833,16 +858,6 @@
 Below you find a list of capabilities available:
 
 
-* Create Group
-
-* Create Project
-
-* Flush Caches
-
-* Kill Task
-
-* Priority
-
 * Start Replication
 
 * View Caches
@@ -866,13 +881,82 @@
 Create Account
 ~~~~~~~~~~~~~~
 
-Allow link:cmd-create-account.html['account creation over the ssh prompt'].
+Allow link:cmd-create-account.html[account creation over the ssh prompt].
 This capability allows the granted group members to create non-interactive
 service accounts.  These service accounts are generally used for automation
 and made to be members of the
 link:access-control.html#non-interactive_users['Non-Interactive users'] group.
 
 
+[[capability_createGroup]]
+Create Group
+~~~~~~~~~~~~
+
+Allow group creation.  Groups are used to grant users access to different
+actions in projects.  This capability allows the granted group members to
+either link:cmd-create-group.html[create new groups via ssh] or via the web UI.
+
+
+[[capability_createProject]]
+Create Project
+~~~~~~~~~~~~~~
+
+Allow project creation.  This capability allows the granted group to
+either link:cmd-create-project.html[create new git projects via ssh]
+or via the web UI.
+
+
+[[capability_flushCaches]]
+Flush Caches
+~~~~~~~~~~~~
+
+Allow the flushing of Gerrits caches.  This capability allows the granted
+group to link:cmd-flush-caches.html[flush some or all Gerrit caches via ssh].
+
+[NOTE]
+This capability doesn't imply permissions to the show-caches command.  For that
+you need the <<capability_viewCaches,view caches capability>>.
+
+
+[[capability_kill]]
+Kill Task
+~~~~~~~~~
+
+Allow the operation of the link:cmd-kill.html[kill command over ssh].  The
+kill command ends tasks that currently occupy the Gerrit server, usually
+a replication task or a user initiated task such as an upload-pack or
+recieve-pack.
+
+
+[[capability_priority]]
+Priority
+~~~~~~~~
+
+This capability allows users to use
+link:config-gerrit.html#sshd.batchThreads[the thread pool reserved] for
+link:access-control.html#non-interactive_users['Non-Interactive Users'].
+It's a binary value in that granted users either have access to the thread
+pool, or they don't.
+
+There are three modes for this capability and they're listed by rising
+priority:
+
+No capability configured.::
+The user isn't a member of a group with any priority capability granted. By
+default the user is then in the 'INTERACTIVE' thread pool.
+
+'BATCH'::
+If there's a thread pool configured for 'Non-Interactive Users' and a user is
+granted the priority capability with the 'BATCH' mode selected, the user ends
+up in the separate batch user thread pool. This is true unless the user is
+also granted the below 'INTERACTIVE' option.
+
+'INTERACTIVE'::
+If a user is granted the priority capability with the 'INTERACTIVE' option,
+regardless if they also have the 'BATCH' option or not, they are in the
+'INTERACTIVE' thread pool.
+
+
 [[capability_queryLimit]]
 Query Limit
 ~~~~~~~~~~~
diff --git a/Documentation/cmd-create-account.txt b/Documentation/cmd-create-account.txt
index 31bc482..98f950f 100644
--- a/Documentation/cmd-create-account.txt
+++ b/Documentation/cmd-create-account.txt
@@ -28,7 +28,8 @@
 ACCESS
 ------
 Caller must be a member of the privileged 'Administrators' group,
-or have been granted the 'Create Account' global capability.
+or have been granted
+link:access-control.html#capability_createAccount[the 'Create Account' global capability].
 
 SCRIPTING
 ---------
diff --git a/Documentation/cmd-create-group.txt b/Documentation/cmd-create-group.txt
index 7549c38..475d2c5 100644
--- a/Documentation/cmd-create-group.txt
+++ b/Documentation/cmd-create-group.txt
@@ -28,7 +28,8 @@
 ACCESS
 ------
 Caller must be a member of the privileged 'Administrators' group,
-or have been granted the 'Create Group' global capability.
+or have been granted
+link:access-control.html#capability_createGroup[the 'Create Group' global capability].
 
 SCRIPTING
 ---------
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index cc7e929..f22141c 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -39,7 +39,8 @@
 ACCESS
 ------
 Caller must be a member of the privileged 'Administrators' group,
-or have been granted the 'Create Project' global capability.
+or have been granted
+link:access-control.html#capability_createProject[the 'Create Project' global capability].
 
 SCRIPTING
 ---------
diff --git a/Documentation/cmd-flush-caches.txt b/Documentation/cmd-flush-caches.txt
index f8882c8..bc6fac5 100644
--- a/Documentation/cmd-flush-caches.txt
+++ b/Documentation/cmd-flush-caches.txt
@@ -26,7 +26,8 @@
 ACCESS
 ------
 Caller must be a member of the privileged 'Administrators' group,
-or have granted the 'Flush Caches' global capability.
+or in a group that have been granted
+link:access-control.html#capability_flushCaches[the 'Flush Caches' global capability].
 
 SCRIPTING
 ---------
diff --git a/Documentation/cmd-kill.txt b/Documentation/cmd-kill.txt
index 57a98f7..f09053e 100644
--- a/Documentation/cmd-kill.txt
+++ b/Documentation/cmd-kill.txt
@@ -19,7 +19,7 @@
 ACCESS
 ------
 Caller must be a member of the privileged 'Administrators' group,
-or have been granted the 'Kill Task' global capability.
+or have been granted link:access-control.html#capability_kill[the 'Kill Task' global capability].
 
 SCRIPTING
 ---------
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index 1bd4862..253bed1 100644
--- a/Documentation/cmd-query.txt
+++ b/Documentation/cmd-query.txt
@@ -15,6 +15,7 @@
   [--files]
   [--comments]
   [--commit-message]
+  [--dependencies]
   [--]
   <query>
   [limit:<n>]
@@ -75,6 +76,10 @@
 --commit-message::
 	Include the full commit message in the change description.
 
+--dependencies::
+	Show information about patch sets which depend on, or are needed by,
+	each patch set.
+
 limit:<n>::
 	Maximum number of results to return.  This is actually a
 	query operator, and not a command line option.	If more
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index ec55b57..d02382f 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1957,8 +1957,9 @@
 [[sshd.batchThreads]]sshd.batchThreads::
 +
 Number of threads to allocate for SSH command requests from
-non-interactive users. If equals to 0, then all non-interactive
-requests are executed in the same queue as interactive requests.
+link:access-control.html#non-interactive_users[non-interactive users].
+If equals to 0, then all non-interactive requests are executed in the same
+queue as interactive requests.
 +
 Any other value will remove the number of threads from the queue
 allocated to interactive users, and create a separate thread pool
diff --git a/ReleaseNotes/ReleaseNotes-2.3.txt b/ReleaseNotes/ReleaseNotes-2.3.txt
new file mode 100644
index 0000000..20b14c6
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.3.txt
@@ -0,0 +1,454 @@
+Release notes for Gerrit 2.3
+============================
+
+Gerrit 2.3 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.3.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.3.war]
+
+Schema Change
+-------------
+*WARNING:* This release contains schema changes.  To upgrade:
+----
+  java -jar gerrit.war init -d site_path
+----
+
+*WARNING:* Upgrading to 2.3.x requires the server be first upgraded
+to 2.1.7 (or a later 2.1.x version), and then to 2.3.x.
+
+If you are upgrading from 2.2.x.x, you may ignore this warning and
+upgrade directly to 2.3.x.
+
+
+New Features
+------------
+Drafts
+~~~~~~
+* New draft statuses and magic branches
++
+Adds draft status to Change. DRAFT status in change occurs before NEW
+and will be for a change that is not meant for review (yet).
+Also adds magic branches refs/drafts/ and refs/publish/ that
+will handle whether or not a patchset is a draft or goes straight to
+review. refs/for/ should be deprecated in favor of explicitly marking
+a patchset as a draft or directly to review.
+
+* Draft patchset and change visibility in UI
++
+If a patchset is a draft, adds a (DRAFT) label next to the revision
+(or gitweb link if it exists). If a change is a draft, adds a (DRAFT)
+next to the subject and changes the status appropriately.
+
+* Publish draft patchsets in UI and SSH
++
+Adds Publish button to draft patchsets in UI and an option to
+publish draft patchsets in the review ssh command. Publishing a draft
+patchset makes it visible. Publishing a draft patchset in a draft
+change irreversibly upgrades the change status to NEW.
+
+* Delete draft changes and patchsets
++
+Adds ability to delete draft changes and patchsets that are not meant
+or fit for code review. Deleting a draft patchset also deletes the
+corresponding ref from the repository and decrements the next patch
+set number for the change if necessary. Deleting a draft change
+deletes all of its (draft) patchsets.
+
+* Add pushing drafts to refs/drafts/
++
+Pushing to refs/drafts/ will now push a draft patchset. If this is the
+first patch set, change created will be in draft status. Pushing a
+draft patchset to a draft change keeps it in draft status. Pushing
+a non-draft patchset (with refs/publish/ or refs/for/, they do the
+same thing) to a draft change turns it into a non-draft change.
+Draft patchsets cannot be submitted.
+
+* When pushing changes as drafts, output [DRAFT] next to the change link
+
+
+Web
+~~~
+* issue 203 Create project through web interface
++
+Add a new panel in the Admin->Projects Screen.  It
+enables the users that are allowed to create projects
+via command-line to create them also via web interface.
+
+* Suggest parent for 'create-project' in the UI.
++
+Add a list of parent suggestions for 'create project'
+in the UI, so the user can select a parent for the new
+project from a list of projects that are already parents to
+other projects.
+
+* issue 981 Fix diffs skipping one line
++
+Don't show '... skipping 1 common line ...'.  The text to show this
+takes up just as much space as showing the line which was skipped.
+
+* issue 18 Support expanding lines of context in diff
++
+Allow lines of context which were skipped in the side-by-side diff
+view to be expanded.  This makes it easier to get more code context
+when needed but not show huge amounts of unneeded data.
+
+* Move checkbox to mark file as reviewed into title bar
+
+* Redirect the user to the reverted change (when reverting).
+
+* On group rename update the group name in the page title
+
+* In ProjectAccessScreen add link to history of project.config in gitweb
+
+* Removed superfluous 'comment' for patch history table.
+
+* Make OpenID login images transparent
+
+* Disable SSH Keys in the web UI if SSHD is disabled
+
+
+SSH
+~~~
+* Adds --description (-d) option to ls-projects
++
+Allows listing of projects together with their respective
+description.
+
+* ls-projects: new option to list all accessible projects
++
+Add a new option '--all' to the 'ls-projects' SSH command to display
+all projects that are accessible by the calling user account. Besides
+the projects that the calling user account has been granted 'READ'
+access to, this includes all projects that are owned by the calling
+user account (even if for these projects the 'READ' access right is
+not assigned to the calling user account).
+
+* Suggest parent for 'create-project' in the SSH command
++
+Add an option '--suggest-parents' which will print out
+a list of projects that are already parents to another
+projects, thus it can help user to find a suitable
+parent for the new project.
+
+* Support reparenting all children of a parent project
++
+This change adds a new option to the 'set-project-parent' command that
+allows reparenting all child projects of one parent project to another
+parent project.
+
+* set-parent-project: evict child projects from project cache
+
+* Add ssh command to list groups.
+
+* ls-groups: add option to list groups for a project
++
+Add an option to the ls-groups SSH command that allows to list only
+those groups for which any permission is assigned to a project.
+
+* ls-groups: Add option to only list groups that are visible to all
+
+* ls-groups: Support listing groups by group type
+
+* ls-groups: Support listing of groups for a user
+
+* Add new SSH command to rename groups
+
+* Support for --file option for ssh queries.
++
+Allows user to list files and attributes (ADDED,
+MODIFIED, DELETED, RENAMED, COPIED) when querying for
+patch sets.
+
+* Output full commit message in query results
+
+* Option for SSHD review-cmd to always publish the message.
++
+"--force-message" option for the SSHD review command,
+which allows Gerrit to publish the "--message", even if the
+labels could not be applied due to change being closed.
+
+
+Config
+~~~~~~
+* issue 349 Apply states for projects (active, readonly and hidden)
++
+Active state indicates the project is regular and is the default value.
++
+Read Only means that users can see the project if read permission is
+granted, but all modification operations are disabled.
++
+Hidden means the project is not visible for those who are not owners
+
+* Enable case insensitive login to Gerrit WebUI for LDAP authentication
++
+Gerrit treats user names as case sensitive, while some LDAP servers
+don't. On first login to Gerrit the user enters his user name and
+Gerrit queries LDAP for it. Since LDAP is case-insensitive with regards
+to  the username, the LDAP authentication succeeds regardless in
+which case the user typed in his user name. The username is stored in
+Gerrit exactly as entered by the user. For further logins the user
+always has to use the same case. If the user specifies his user name in
+a different case Gerrit tries to create a new account which fails with
+"Cannot assign user name ... to account ...; name already in use.".
+This error occurs because the LDAP query resolves to the same LDAP
+user and storing the username for SSH (which is by default always
+lower case) fails because such an entry exists already for the first
+account that the user created.
++
+This change introduces a new configuration parameter that converts the
+user name always to lower case before doing the LDAP authentication.
+By this the login to the Gerrit WebUI gets case insensitive. If this
+configuration parameter is set, the user names for all existing
+accounts have to be converted to lower case. This change includes a
+server program to do this conversion.
+
+* Enable case insensitive authentication for git operations
++
+A new configuration parameter is introduced that converts the username
+that is received to authenticate a git operation to lower case for
+looking up the user account in Gerrit.
++
+By setting this parameter a case insensitive authentication for the
+git operations can be achieved, if it is ensured that the usernames in
+Gerrit (scheme 'username') are stored in lower case (e.g. if the
+parameter 'ldap.accountSshUserName' is set to
+'${sAMAccountName.toLowerCase}').
+
+* Support replication to local folder
+
+* Read timeout parameter for LDAP connections: ldap.readTimeout
++
+This helps prevent a very slow LDAP server from blocking
+all SSH command creation threads.
+
+* Introduce a git maxObjectSizeLimit in the [recieve] config
++
+This limits the size of uploaded files
+
+* Make 'Anonymous Coward' configurable
+
+* Add property to configure path separator in URLs for a gitweb service
+
+* Customize link-name pointing to gitweb-service.
++
+Previously the link to the external gitweb-type
+pages said "(gitweb)" regardless if using cgit
+or a custom service.
+
+* Support gitweb.type=disabled
+
+* rules.enable: Support disabling per project prolog rules in gerrit.config
+
+* Allow site administrators to define Git-over-HTTP mirror URL
+
+* Allow sshd.listenAddress = off to disable the daemon
+
+* daemon: Allow httpd without sshd
+
+* Allow disabling certain features of HostPageServlet
++
+These features are: user agent detection and automatic refresh
+logic associated with the site header, footer and CSS.
+
+
+Dev
+~~~
+* Fix 'No source code is available for type org.eclipse.jgit.lib.Constants'
+
+* Fix miscellaneous compiler warnings
+
+* Add entries to .gitignore for m2e settings/preference files
+
+* Package source JARs for antlr, httpd, server
+
+* pom.xml: change gerrit-war's dependency on gerrit-main to runtime
++
+This only seems to matter to IntelliJ, since the Main class is
+provided to the war via an overlay in gerrit-war/pom.xml
+
+* Fixed the full name of the MAVEN2_CLASSPATH_CONTAINER
++
+Fixes java.lang.NoClassDefFoundError: com/google/gwt/dev/DevMode
+
+
+Miscellaneous
+~~~~~~~~~~~~~
+* Allow superprojects to subscribe to submodules updates
++
+The feature introduced in this commit allows superprojects to
+subscribe to submodules updates.
++
+When a commit is merged to a project, the commit content is scanned
+to identify if it registers submodules (if the commit contains new
+gitlinks and .gitmodules file with required info) and if so, a new
+submodule subscription is registered.
++
+When a new commit of a registered submodule is merged, gerrit
+automatically updates the subscribers to the submodule with new
+commit having the updated gitlinks.
++
+The most notable benefit of the feature is to not require
+to push/merge commits of super projects (subscribers) with gitlinks
+whenever a project being a submodule is updated. It is only
+required to push commits with gitlinks when they are created
+(and in this case it is also required to push .gitmodules file).
+
+* Allow Realm to participate when linking an account identity
++
+When linking a new user identity to an exisiting account, permit the
+Realm to observe the new incoming identity and the current account,
+and to alter the request. This enables a Realm to observe when a
+user verifies a new email address link.
+
+* issue 871 Show latest patchset with cherry-picked merge
++
+When a change is published via the cherry-pick merge strategy,
+show the final commit as a patchset in the change history.
+This now makes it possible to search for the cherry-picked SHA1.
+
+* issue 871 Display hash of the cherry-pick merge in comment
+
+* Added more verbose messages when changes are being rejected
+
+* Display proper error message when LDAP is unavailable
+
+* Clarify error msg when user's not allowed to '--force push'.
+
+* ContainerAuthFilter: fail with FORBIDDEN if username not set
+
+* Resolve 'Project Owners' group if it is included into another group
+
+* Hide SSH URL in email footers if SSH is disabled
+
+* Sort the jar files from the war before adding to classpath.
+
+* Apply user preferences when loading site
+
+* Ensure HttpLog can always get the user identity
+
+* Prevent comments spam for abandoned commit
++
+If some change was abandoned but later submitted (e.g. by
+cherry-picking it to a another branch) then pushing a new branch
+that contains this change no longer adds a new comment.
+
+* Make Address, EmailHeader visible to other EmailSenders
+
+* Use transactions to handle comments when possible
+
+* Try to use transactions when creating changes
+
+* gerrit.sh: disown doesn't accept pid as a argument, fix script
+
+* gerrit.sh: detach gerrit properly so it won't keep bad ssh sessions open.
+
+* Cache list of all groups in the group cache
+
+* issue 1161 Evict project in user cache on save of project meta data
+
+* Ensure that the site paths are resolved to their canonical form (for Windows)
+
+* Connect Velocity to slf4j
+
+* Expose project permissionOnly status via JSON-RPC
+
+* Make HEAD of All-Projects point to refs/meta/config
+
+* issue 1158 Added support for European style dates
+
+* Make macros in email templates local to the template
+
+* Support http://server/project for Git access
+
+* Use _ instead of $ for implementation-detail Prolog predicates
+
+* Update the Sign In anchor with current URL
++
+Always update the href of the Sign In anchor in the menu bar with
+the current page URL after /login/, making the redirect process
+bring users back to the current view after sign in.
+
+* Improve validation of email registration tokens
+
+
+Upgrades
+--------
+* Upgrade to gwtorm 1.2
+
+* Upgrade to JGit 1.1.0.201109151100-r.119-gb4495d1
++
+This is needed because of this change:
+https://gerrit-review.googlesource.com/#/c/30450/
+
+* Support Velocity 1.5 (as well as previous 1.6.4)
+
+
+Bug Fixes
+---------
+* Avoid NPE when group is missing
+
+* Do not fail with NPE if context path of request is null
+
+* Fix NPE in set-project-parent command if parent is not specified
+
+* Only send mail to author and committer if they are registered to prevent an NPE
+
+* Avoid potential NPE when querying the queue.
+
+* Allow loading Project Access when there is no refs/meta/config
+
+* Fix calculation of project name if repo is not existing
++
+If a project inherits from a non existing parent, prevent a
+StringIndexOutOfBoundsException.
+
+* Fix: Supress "Error on refs/cache-automerge" warnings.
+
+* Don't allow registering for cleanup after cleanup runs
++
+This prevents leaking a database connection.
+
+* issue 807 Fix: Tags are not replicated properly
+
+* Prevent smtp rejected users from rejecting emails for all users
+
+* Fix token saving redirect in container auth
++
+Update the jump page that redirects users from /#TOKEN to
+/login/TOKEN.  This forces using the container based
+authentication.  Also correct "/login//" to be just "/login/".
+
+* Use custom error messages for Git-over-HTTP
++
+Ensure clients see messages related to contributor agreement not
+being activated even if they push over HTTP.
+
+* Avoid double key event for GroupReferenceBox
+
+* Fix git push authentication over HTTP
+
+* Fix http://login/ redirect bug
+
+* Fix missing targets in /login/ URLs
+
+* set-project-parent: if update of 1 project fails continue with others
+
+* Verify the case of the project name before opening git repository
+
+* Update top level SUBMITTING_PATCHES URLs
+
+
+Documentation
+-------------
+* Some updates to the design docs
+
+* cmd-index: Fix link to documentation of rename-group command
+
+* Update documentation for testing SSH connection
+
+* Bypass review updated with 2.2.x permissions
+
+* Add documentation for 'peer_keys'
+
+* Improve 'Push Merge Commit' access right documentation
+
+* Access control: capabilities
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 8d223ad..13da60a 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -1,6 +1,11 @@
 Gerrit Code Review - Release Notes
 ==================================
 
+[[2_3]]
+Version 2.3.x
+-------------
+* link:ReleaseNotes-2.3.html[2.3]
+
 [[2_2]]
 Version 2.2.x
 -------------
diff --git a/gerrit-ehcache/.gitignore b/gerrit-ehcache/.gitignore
index 903c6c8..20251d4 100644
--- a/gerrit-ehcache/.gitignore
+++ b/gerrit-ehcache/.gitignore
@@ -1,4 +1,5 @@
 /target
 /.classpath
 /.project
+/.settings/org.eclipse.m2e.core.prefs
 /.settings/org.maven.ide.eclipse.prefs
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAttribute.java
index bb5abe4..9810f59 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeAttribute.java
@@ -39,4 +39,7 @@
     public List<TrackingIdAttribute> trackingIds;
     public PatchSetAttribute currentPatchSet;
     public List<PatchSetAttribute> patchSets;
+
+    public List<DependencyAttribute> dependsOn;
+    public List<DependencyAttribute> neededBy;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/DependencyAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/DependencyAttribute.java
new file mode 100644
index 0000000..47fbdac
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/DependencyAttribute.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2012 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.events;
+
+public class DependencyAttribute {
+  public String id;
+  public String number;
+  public String revision;
+  public String ref;
+  public Boolean isCurrentPatchSet;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index bc9047d..4d34b71 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -22,13 +22,18 @@
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetAncestor;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.client.TrackingId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -47,16 +52,18 @@
   private final Provider<String> urlProvider;
   private final ApprovalTypes approvalTypes;
   private final PatchListCache patchListCache;
+  private final SchemaFactory<ReviewDb> schema;
 
   @Inject
   EventFactory(AccountCache accountCache,
       @CanonicalWebUrl @Nullable Provider<String> urlProvider,
       ApprovalTypes approvalTypes,
-      PatchListCache patchListCache) {
+      PatchListCache patchListCache, SchemaFactory<ReviewDb> schema) {
     this.accountCache = accountCache;
     this.urlProvider = urlProvider;
     this.approvalTypes = approvalTypes;
     this.patchListCache = patchListCache;
+    this.schema = schema;
   }
 
   /**
@@ -110,6 +117,61 @@
     a.status = change.getStatus();
   }
 
+  public void addDependencies(ChangeAttribute ca, Change change) {
+    ca.dependsOn = new ArrayList<DependencyAttribute>();
+    ca.neededBy = new ArrayList<DependencyAttribute>();
+    try {
+      final ReviewDb db = schema.open();
+      try {
+        final PatchSet.Id psId = change.currentPatchSetId();
+        for (PatchSetAncestor a : db.patchSetAncestors().ancestorsOf(psId)) {
+          for (PatchSet p :
+              db.patchSets().byRevision(a.getAncestorRevision())) {
+            Change c = db.changes().get(p.getId().getParentKey());
+            ca.dependsOn.add(newDependsOn(c, p));
+          }
+        }
+
+        final RevId revId = db.patchSets().get(psId).getRevision();
+        for (PatchSetAncestor a : db.patchSetAncestors().descendantsOf(revId)) {
+          final PatchSet p = db.patchSets().get(a.getPatchSet());
+          final Change c = db.changes().get(p.getId().getParentKey());
+          ca.neededBy.add(newNeededBy(c, p));
+        }
+      } finally {
+        db.close();
+      }
+    } catch (OrmException e) {
+      // Squash DB exceptions and leave dependency lists partially filled.
+    }
+    // Remove empty lists so a confusing label won't be displayed in the output.
+    if (ca.dependsOn.isEmpty()) {
+      ca.dependsOn = null;
+    }
+    if (ca.neededBy.isEmpty()) {
+      ca.neededBy = null;
+    }
+  }
+
+  private DependencyAttribute newDependsOn(Change c, PatchSet ps) {
+    DependencyAttribute d = newDependencyAttribute(c, ps);
+    d.isCurrentPatchSet = c.currPatchSetId().equals(ps.getId());
+    return d;
+  }
+
+  private DependencyAttribute newNeededBy(Change c, PatchSet ps) {
+    return newDependencyAttribute(c, ps);
+  }
+
+  private DependencyAttribute newDependencyAttribute(Change c, PatchSet ps) {
+    DependencyAttribute d = new DependencyAttribute();
+    d.number = c.getId().toString();
+    d.id = c.getKey().toString();
+    d.revision = ps.getRevision().get();
+    d.ref = ps.getRefName();
+    return d;
+  }
+
   public void addTrackingIds(ChangeAttribute a, Collection<TrackingId> ids) {
     if (!ids.isEmpty()) {
       a.trackingIds = new ArrayList<TrackingIdAttribute>(ids.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
index c2fe1a2..660b818 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
@@ -17,7 +17,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.ReceiveCommits.MessageSender;
 import com.google.gerrit.server.git.WorkQueue.Executor;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.util.RequestScopePropagator;
@@ -34,6 +33,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.PreReceiveHook;
 import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Result;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -112,26 +112,24 @@
   }
 
   private class MessageSenderOutputStream extends OutputStream {
-    private final MessageSender messageSender = rc.getMessageSender();
-
     @Override
     public void write(int b) {
-      messageSender.sendBytes(new byte[]{(byte)b});
+      rc.getMessageSender().sendBytes(new byte[]{(byte)b});
     }
 
     @Override
     public void write(byte[] what, int off, int len) {
-      messageSender.sendBytes(what, off, len);
+      rc.getMessageSender().sendBytes(what, off, len);
     }
 
     @Override
     public void write(byte[] what) {
-      messageSender.sendBytes(what);
+      rc.getMessageSender().sendBytes(what);
     }
 
     @Override
     public void flush() {
-      messageSender.flush();
+      rc.getMessageSender().flush();
     }
   }
 
@@ -167,14 +165,16 @@
           timeoutMillis, TimeUnit.MILLISECONDS);
     } catch (ExecutionException e) {
       log.warn("Error in ReceiveCommits", e);
-      rc.getMessageSender().sendError("internal error while processing changes");
+      rc.addError("internal error while processing changes");
       // ReceiveCommits has tried its best to catch errors, so anything at this
       // point is very bad.
       for (final ReceiveCommand c : commands) {
-        if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
-          rc.reject(c, "internal error");
+        if (c.getResult() == Result.NOT_ATTEMPTED) {
+          c.setResult(Result.REJECTED_OTHER_REASON, "internal error");
         }
       }
+    } finally {
+      rc.sendMessages();
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index 63edabea..dbb849c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -253,7 +253,17 @@
   private void sendDone() {
     spinnerState = NO_SPINNER;
     StringBuilder s = format();
-    s.append(", done    \n");
+    boolean any = false;
+    for (Task t : tasks) {
+      if (t.count != 0) {
+        any = true;
+        break;
+      }
+    }
+    if (any) {
+      s.append(",");
+    }
+    s.append(" done    \n");
     send(s);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 544673d..7be2b23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -405,11 +405,11 @@
     messages.add(new Message(message, false));
   }
 
-  private void addError(String error) {
+  void addError(String error) {
     messages.add(new Message(error, true));
   }
 
-  private void sendMessages() {
+  void sendMessages() {
     for (Message m : messages) {
       if (m.isError) {
         messageSender.sendError(m.message);
@@ -421,86 +421,82 @@
 
   void processCommands(final Collection<ReceiveCommand> commands,
       final MultiProgressMonitor progress) {
-    try {
-      newProgress = progress.beginSubTask("new", UNKNOWN);
-      replaceProgress = progress.beginSubTask("updated", UNKNOWN);
-      closeProgress = progress.beginSubTask("closed", UNKNOWN);
-      commandProgress = progress.beginSubTask("refs", commands.size());
+    newProgress = progress.beginSubTask("new", UNKNOWN);
+    replaceProgress = progress.beginSubTask("updated", UNKNOWN);
+    closeProgress = progress.beginSubTask("closed", UNKNOWN);
+    commandProgress = progress.beginSubTask("refs", commands.size());
 
-      parseCommands(commands);
-      if (newChange != null
-          && newChange.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
-        createNewChanges();
+    parseCommands(commands);
+    if (newChange != null
+        && newChange.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
+      createNewChanges();
+    }
+    newProgress.end();
+
+    doReplaces();
+    replaceProgress.end();
+
+    for (final ReceiveCommand c : commands) {
+      if (c.getResult() == Result.OK) {
+        switch (c.getType()) {
+          case CREATE:
+            if (isHead(c)) {
+              autoCloseChanges(c);
+            }
+            break;
+
+          case UPDATE: // otherwise known as a fast-forward
+            tagCache.updateFastForward(project.getNameKey(),
+                c.getRefName(),
+                c.getOldId(),
+                c.getNewId());
+            if (isHead(c)) {
+              autoCloseChanges(c);
+            }
+            break;
+
+          case UPDATE_NONFASTFORWARD:
+            if (isHead(c)) {
+              autoCloseChanges(c);
+            }
+            break;
+        }
+
+        if (isConfig(c)) {
+          projectCache.evict(project);
+          ProjectState ps = projectCache.get(project.getNameKey());
+          repoManager.setProjectDescription(project.getNameKey(), //
+              ps.getProject().getDescription());
+        }
+
+        if (!MagicBranch.isMagicBranch(c.getRefName())) {
+          // We only schedule direct refs updates for replication.
+          // Change refs are scheduled when they are created.
+          //
+          replication.scheduleUpdate(project.getNameKey(), c.getRefName());
+          Branch.NameKey destBranch = new Branch.NameKey(project.getNameKey(), c.getRefName());
+          hooks.doRefUpdatedHook(destBranch, c.getOldId(), c.getNewId(), currentUser.getAccount());
+        }
+        commandProgress.update(1);
       }
-      newProgress.end();
+    }
+    closeProgress.end();
+    commandProgress.end();
+    progress.end();
 
-      doReplaces();
-      replaceProgress.end();
-
-      for (final ReceiveCommand c : commands) {
-        if (c.getResult() == Result.OK) {
-          switch (c.getType()) {
-            case CREATE:
-              if (isHead(c)) {
-                autoCloseChanges(c);
-              }
-              break;
-
-            case UPDATE: // otherwise known as a fast-forward
-              tagCache.updateFastForward(project.getNameKey(),
-                  c.getRefName(),
-                  c.getOldId(),
-                  c.getNewId());
-              if (isHead(c)) {
-                autoCloseChanges(c);
-              }
-              break;
-
-            case UPDATE_NONFASTFORWARD:
-              if (isHead(c)) {
-                autoCloseChanges(c);
-              }
-              break;
-          }
-
-          if (isConfig(c)) {
-            projectCache.evict(project);
-            ProjectState ps = projectCache.get(project.getNameKey());
-            repoManager.setProjectDescription(project.getNameKey(), //
-                ps.getProject().getDescription());
-          }
-
-          if (!MagicBranch.isMagicBranch(c.getRefName())) {
-            // We only schedule direct refs updates for replication.
-            // Change refs are scheduled when they are created.
-            //
-            replication.scheduleUpdate(project.getNameKey(), c.getRefName());
-            Branch.NameKey destBranch = new Branch.NameKey(project.getNameKey(), c.getRefName());
-            hooks.doRefUpdatedHook(destBranch, c.getOldId(), c.getNewId(), currentUser.getAccount());
-          }
-          commandProgress.update(1);
+    if (!allNewChanges.isEmpty() && canonicalWebUrl != null) {
+      final String url = canonicalWebUrl;
+      addMessage("");
+      addMessage("New Changes:");
+      for (final Change c : allNewChanges) {
+        if (c.getStatus() == Change.Status.DRAFT) {
+          addMessage("  " + url + c.getChangeId() + " [DRAFT]");
+        }
+        else {
+          addMessage("  " + url + c.getChangeId());
         }
       }
-      closeProgress.end();
-      commandProgress.end();
-      progress.end();
-
-      if (!allNewChanges.isEmpty() && canonicalWebUrl != null) {
-        final String url = canonicalWebUrl;
-        addMessage("");
-        addMessage("New Changes:");
-        for (final Change c : allNewChanges) {
-          if (c.getStatus() == Change.Status.DRAFT) {
-            addMessage("  " + url + c.getChangeId() + " [DRAFT]");
-          }
-          else {
-            addMessage("  " + url + c.getChangeId());
-          }
-        }
-        addMessage("");
-      }
-    } finally {
-      sendMessages();
+      addMessage("");
     }
   }
 
@@ -2060,7 +2056,7 @@
     reject(cmd, "prohibited by Gerrit");
   }
 
-  void reject(final ReceiveCommand cmd, final String why) {
+  private void reject(final ReceiveCommand cmd, final String why) {
     cmd.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, why);
     commandProgress.update(1);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 92a6e30..619bc06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -296,7 +296,7 @@
       //
       for (StarredChange w : args.db.get().starredChanges().byChange(
           change.getId())) {
-        add(RecipientType.BCC, w.getAccountId());
+        super.add(RecipientType.BCC, w.getAccountId());
       }
     } catch (OrmException err) {
       // Just don't BCC everyone. Better to send a partial message to those
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
index 7a75996..9705b8a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
@@ -67,6 +67,7 @@
     add(RecipientType.TO, reviewers);
     add(RecipientType.CC, extraCC);
     rcptToAuthors(RecipientType.CC);
+    bccStarredBy();
   }
 
   @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 e6848ed..a2fa7fe 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
@@ -77,6 +77,7 @@
   private boolean includeComments;
   private boolean includeFiles;
   private boolean includeCommitMessage;
+  private boolean includeDependencies;
 
   private OutputStream outputStream = DisabledOutputStream.INSTANCE;
   private PrintWriter out;
@@ -128,6 +129,14 @@
     return includeFiles;
   }
 
+  public void setIncludeDependencies(boolean on) {
+    includeDependencies = on;
+  }
+
+  public boolean getIncludeDependencies() {
+    return includeDependencies;
+  }
+
   public void setIncludeCommitMessage(boolean on) {
     includeCommitMessage = on;
   }
@@ -242,6 +251,10 @@
             }
           }
 
+          if (includeDependencies) {
+            eventFactory.addDependencies(c, d.getChange());
+          }
+
           show(c);
         }
 
@@ -367,7 +380,20 @@
       out.print('\n');
     } else if (value instanceof Collection) {
       out.print('\n');
+      boolean firstElement = true;
       for (Object thing : ((Collection<?>) value)) {
+        // The name of the collection was initially printed at the beginning
+        // of this routine.  Beginning at the second sub-element, reprint
+        // the collection name so humans can separate individual elements
+        // with less strain and error.
+        //
+        if (firstElement) {
+          firstElement = false;
+        } else {
+          out.print(indent);
+          out.print(field);
+          out.print(":\n");
+        }
         if (isPrimitive(thing)) {
           out.print(' ');
           out.print(value);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
index d5e047b..9befc7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
@@ -36,9 +36,9 @@
 /** Propagator for Guice's built-in servlet scope. */
 public class GuiceRequestScopePropagator extends RequestScopePropagator {
 
-  private final Provider<String> urlProvider;
-  private final Provider<SocketAddress> remotePeerProvider;
-  private final Provider<CurrentUser> currentUserProvider;
+  private final String url;
+  private final SocketAddress peer;
+  private final CurrentUser user;
 
   @Inject
   GuiceRequestScopePropagator(
@@ -46,9 +46,9 @@
       @RemotePeer Provider<SocketAddress> remotePeerProvider,
       Provider<CurrentUser> currentUserProvider) {
     super(ServletScopes.REQUEST);
-    this.urlProvider = urlProvider;
-    this.remotePeerProvider = remotePeerProvider;
-    this.currentUserProvider = currentUserProvider;
+    this.url = urlProvider != null ? urlProvider.get() : null;
+    this.peer = remotePeerProvider.get();
+    this.user = currentUserProvider.get();
   }
 
   /**
@@ -61,17 +61,14 @@
     // Request scopes appear to use specific keys in their map, instead of only
     // providers. Add bindings for both the key to the instance directly and the
     // provider to the instance to be safe.
-    String url = urlProvider.get();
     seedMap.put(Key.get(typeOfProvider(String.class), CanonicalWebUrl.class),
         Providers.of(url));
     seedMap.put(Key.get(String.class, CanonicalWebUrl.class), url);
 
-    SocketAddress peer = remotePeerProvider.get();
     seedMap.put(Key.get(typeOfProvider(SocketAddress.class), RemotePeer.class),
         Providers.of(peer));
     seedMap.put(Key.get(SocketAddress.class, RemotePeer.class), peer);
 
-    CurrentUser user = currentUserProvider.get();
     seedMap.put(Key.get(typeOfProvider(CurrentUser.class)), Providers.of(user));
     seedMap.put(Key.get(CurrentUser.class), user);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
index 403701a..581ccc1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
@@ -74,6 +74,13 @@
   /**
    * Returns a new context object based on the passed in context that has no
    * request scoped objects initialized.
+   * <p>
+   * Note that some code paths expect request-scoped objects like
+   * {@code CurrentUser} to be constructible starting from just the context
+   * object returned by this method. For example, in the SSH scope, the context
+   * includes the {@code SshSession}, which is used by
+   * {@code SshCurrentUserProvider} to construct a new {@code CurrentUser} in
+   * the new thread.
    *
    * @param ctx the context to continue.
    * @return a new context.
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
index 548f373..212ffb1 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
@@ -49,7 +49,25 @@
 		# Solaris AWK is just too broken
 		AWK=/usr/xpg4/bin/awk
 	fi
+
+	# How this works:
+	# - parse the commit message as (textLine+ blankLine*)*
+	# - assume textLine+ to be a footer until proven otherwise
+	# - exception: the first block is not footer (as it is the title)
+	# - read textLine+ into a variable
+	# - then count blankLines
+	# - once the next textLine appears, print textLine+ blankLine* as these
+	#   aren't footer
+	# - in END, the last textLine+ block is available for footer parsing
 	$AWK '
+	BEGIN {
+		# while we start with the assumption that textLine+
+		# is a footer, the first block is not.
+		isFooter = 0
+		footerComment = 0
+		blankLines = 0
+	}
+
 	# Skip lines starting with "#" without any spaces before it.
 	/^#/ { next }
 
@@ -57,80 +75,78 @@
 	# up to the end of the file, assuming it is only patch data.
 	# If more than one line before the diff was empty, strip all but one.
 	/^diff --git a/ {
-		if (blankLines > 1) {
-			blankLines = 1
-		}
+		blankLines = 0
 		while (getline) { }
 		next
 	}
 
-	# Handle comments and continuations in tags ([foo: bar] etc)
-	(caught == 1) && /^[ []/ {
-		if (lines != "") {
-			lines = lines "\n"
-		}
-		lines = lines $0
+	# Count blank lines outside footer comments
+	/^$/ && (footerComment == 0) {
+		blankLines++
 		next
 	}
 
-	# Handle normal lines (ie. not starting with some tag like "Signed-off-by:").
-	# If normal text appears after tags were "caught", handle them as normal text, too.
-	# Also count blank lines in blankLines.
-	!/^[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\// {
-		if ($0 == "") {
-			blankLines++
-			next
-		} else {
-			for (i = 0; i < blankLines; i++) {
-				print ""
-			}
-			blankLines = 0
-		}
-		if (caught == 1) {
-			caught = 0
-			print lines
-			lines = ""
-		}
-		print $0
-		next
+	# Catch footer comment
+	/^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) {
+		footerComment = 1
 	}
 
-	# Handle tags.  They are "caught" and collected in the "lines" variable
+	/]$/ && (footerComment == 1) {
+		footerComment = 2
+	}
+
+	# We have a non-blank line after blank lines. Handle this.
+	(blankLines > 0) {
+		print lines
+		for (i = 0; i < blankLines; i++) {
+			print ""
+		}
+
+		lines = ""
+		blankLines = 0
+		isFooter = 1
+		footerComment = 0
+	}
+
+	# Detect that the current block is not the footer
+	(footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) {
+		isFooter = 0
+	}
+
 	{
-		caught = 1
+		# We need this information about the current last comment line
+		if (footerComment == 2) {
+			footerComment = 0
+		}
 		if (lines != "") {
 			lines = lines "\n";
 		}
 		lines = lines $0
 	}
 
-	# Tag handling:
-	# If last line before tags was not blank, there were no tags.
-	# In that case, print everything, plus a blank line, followed by Change-Id.
-	# Otherwise there were tags. Look for the right place to inject Change-Id,
-	# by considering CHANGE_ID_AFTER. Tags listed in it (case insensitive) come first,
+	# Footer handling:
+	# If the last block is considered a footer, splice in the Change-Id at the
+	# right place.
+	# Look for the right place to inject Change-Id by considering
+	# CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first,
 	# then Change-Id, then everything else (eg. Signed-off-by:).
+	#
+	# Otherwise just print the last block, a new line and the Change-Id as a
+	# block of its own.
 	END {
 		unprinted = 1
-		if (blankLines == 0) {
-			if (lines == "") {
-				 print ""
-			} else {
-				print lines "\n"
+		if (isFooter == 0) {
+			print lines "\n"
+			lines = ""
+		}
+		changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
+		numlines = split(lines, footer, "\n")
+		for (line = 1; line <= numlines; line++) {
+			if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
+				unprinted = 0
+				print "Change-Id: I'"$id"'"
 			}
-		} else {
-			for (i = 0; i < blankLines; i++) {
-				print ""
-			}
-			changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
-			numlines = split(lines, footer, "\n")
-			for (line = 1; line <= numlines; line++) {
-				if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
-					unprinted = 0
-					print "Change-Id: I'"$id"'"
-				}
-				print footer[line]
-			}
+			print footer[line]
 		}
 		if (unprinted) {
 			print "Change-Id: I'"$id"'"
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
index 0d84a93..17fe068 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/CommitMsgHookTest.java
@@ -388,6 +388,25 @@
             "git://example.com/ fixes this\n"));
   }
 
+  @Test
+  public void testWithFalseTags() throws Exception {
+    assertEquals("foo\n" + //
+	"\n" + //
+	"FakeLine:\n" + //
+	"  foo\n" + //
+	"  bar\n" + //
+	"\n" + //
+	"Change-Id: I67632a37fd2e08a35f766f52fc9a47f4ea868c55\n" + //
+	"RealTag: abc\n", //
+	call("foo\n" + //
+	    "\n" + //
+	    "FakeLine:\n" + //
+	    "  foo\n" + //
+	    "  bar\n" + //
+	    "\n" + //
+	    "RealTag: abc\n"));
+  }
+
   private void hookDoesNotModify(final String in) throws Exception {
     assertEquals(in, call(in));
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
index 9bca0e5..d9a1c3f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -66,6 +66,11 @@
     processor.setIncludeCommitMessage(on);
   }
 
+  @Option(name = "--dependencies", usage = "Include depends-on and needed-by information")
+  void setDependencies(boolean on) {
+    processor.setIncludeDependencies(on);
+  }
+
   @Argument(index = 0, required = true, multiValued = true, metaVar = "QUERY", usage = "Query to execute")
   private List<String> query;
 
diff --git a/pom.xml b/pom.xml
index 14db525..2520825 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,7 +46,7 @@
   </issueManagement>
 
   <properties>
-    <jgitVersion>1.3.0.201202151440-r.54-gd725ecb</jgitVersion>
+    <jgitVersion>1.3.0.201202151440-r.75-gff13648</jgitVersion>
     <gwtormVersion>1.4</gwtormVersion>
     <gwtjsonrpcVersion>1.2.5</gwtjsonrpcVersion>
     <gwtexpuiVersion>1.2.5</gwtexpuiVersion>