Store PatchSetApprovals in a git-notes-based database

Add a new ref namespace refs/changes/*/*/meta for storing the state of
a Gerrit change in Git. Information about the change is mostly stored
in commit messages, the entire history of which are read at load time
to populate information about the change. Eventually, information tied
to a particular patch set will be stored in a git note referring to
the patch set's commit.

This data format only allows fast access to data on a per-change
basis. Lookups by other keys (e.g. approvals by user) will depend on the
secondary index.

The commit message format is simple:

  Update patch set 1

  <optional cover message>

  Patch-Set: 1
  Label: Code-Review=+1

The subject line is machine-generated and callers should attempt to
make it descriptive, but this is not strictly required. The
cover message is user-provided (not implemented in this change). At
least the Patch-Set footer line is required for the time being; it is
likely we will want to enforce there always being at least one footer to
fend off user-provided cover messages that themselves contains
footers.

The author of the commit is the end-user performing the update, using
their full name and email address. The committer is the same user, but
includes the Gerrit account ID in the email address for
disambiguation. (Eventually, we may decide that the (name, email)
combination is enough to uniquely identify a user, but for now we
still require the account ID.)

On the code side, reading and writing changes from this ref is
implemented with two VersionedMetaData classes:

ChangeNotes implements the read operation. When a ChangeNotes is
loaded, it traverses the entire history of the ref and populates the
object with all relevant information. Traversing the entire history
should be a fast operation on most changes, which typically have
roughly tens of operations.

ChangeUpdate implements the write operation. Unlike the traditional
database, where operations are organized around read-modify-write
operations on various entities spread across tables, a ChangeUpdate
encapsulates a set of updates that can be applied atomically. Because
each commit has a single author, timestamp, and patch set, these are
logically restricted to operations performed by a single user on a
single patch set. This corresponds better to the typical usage of the
database, where a single end-user is performing an operation on a
single change via a REST endpoint.

ChangeNotes and ChangeUpdate are separate VersionedMetaData subclasses
for a few reasons, mostly technical. First, it should not be necessary
in all cases to slurp all change data via ChangeNotes before preparing
a ChangeUpdate. Second, due to subtleties like LabelType sorting, it
would be tricky to make ChangeNotes mutable in such a way that the
result of reading it after one or more mutations would be the same as
if that change were read freshly from the git data. Keeping
ChangeNotes immutable and ChangeUpdate separate makes it less tempting
to assume this behavior.

This change implements only reading/writing for
ChangeNotes/ChangeUpdate; they are not yet used in any read/write code
paths in the server.

Change-Id: Id28978a92f3b197c8b5be6e2db0c1e3923f2211e
This commit is contained in:
Dave Borowitz
2013-12-05 12:22:40 -08:00
parent 4827706dbe
commit 23dc4d1bdc
6 changed files with 772 additions and 1 deletions

View File

@@ -213,7 +213,7 @@ public abstract class VersionedMetaData {
doSave(config, commit);
final ObjectId res = newTree.writeTree(inserter);
if (res.equals(srcTree)) {
if (res.equals(srcTree) && !update.allowEmpty()) {
// If there are no changes to the content, don't create the commit.
return;
}