Merge branch 'stable-2.7'

* stable-2.7:
  Mark ALREADY_MERGED changes as merged in the DB

Change-Id: I360c0593948786fe95418f7001b22b58a1eaee92
diff --git a/.buckconfig b/.buckconfig
new file mode 100644
index 0000000..bcefa2a
--- /dev/null
+++ b/.buckconfig
@@ -0,0 +1,16 @@
+[alias]
+  api = //:api
+  api_deploy = //:api_deploy
+  api_install = //:api_install
+  download = //:download
+  download_sources = //:download_sources
+  gerrit = //:gerrit
+  eclipse = //tools/eclipse:eclipse
+  eclipse_project = //tools/eclipse:eclipse_project
+  release = //:release
+
+[buildfile]
+  includes = //tools/DEFS
+
+[java]
+  src_roots = java, resources
diff --git a/.buckversion b/.buckversion
new file mode 100644
index 0000000..8c4a95b
--- /dev/null
+++ b/.buckversion
@@ -0,0 +1 @@
+a3aadacd7c1ccd819420a73975a08ba67110decb
diff --git a/.gitignore b/.gitignore
index c87c26f..1b4c29c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,17 @@
 /.classpath
 /.project
-/.settings
-/.settings/org.eclipse.jdt.core.prefs
 /.settings/org.maven.ide.eclipse.prefs
+/.settings/org.eclipse.m2e.core.prefs
+/.settings/org.eclipse.ltk.core.refactoring.prefs
 /test_site
 /.idea
 /gerrit-parent.iml
 *.sublime-*
+/gerrit-package-plugins
+/.buckconfig.local
+/.buckd
+/buck-cache
+/buck-out
+/local.properties
+*.pyc
+/gwt-unitCache
diff --git a/.gitmodules b/.gitmodules
index 0f7fdab..e45868b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -9,3 +9,7 @@
 [submodule "plugins/commit-message-length-validator"]
 	path = plugins/commit-message-length-validator
 	url = https://gerrit.googlesource.com/plugins/commit-message-length-validator
+
+[submodule "plugins/helloworld"]
+	path = plugins/helloworld
+	url = https://gerrit.googlesource.com/plugins/helloworld
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..be43141
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?>
+
+<pydev_project>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6.5</pydev_property>
+</pydev_project>
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..29abf99
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
+encoding/<project>=UTF-8
diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..8667cfd
--- /dev/null
+++ b/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Tue Sep 02 16:59:24 PDT 2008
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..dbc83d5
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,346 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=16
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=true
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=2
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..d4218a5
--- /dev/null
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,61 @@
+#Wed Jul 29 11:31:38 PDT 2009
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_Google Format
+formatter_settings_version=11
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=true
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=false
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/BUCK b/BUCK
new file mode 100644
index 0000000..98a85a1
--- /dev/null
+++ b/BUCK
@@ -0,0 +1,81 @@
+include_defs('//tools/build.defs')
+
+gerrit_war(name = 'gerrit')
+gerrit_war(name = 'chrome',   ui = 'ui_chrome')
+gerrit_war(name = 'firefox',  ui = 'ui_firefox')
+gerrit_war(name = 'withdocs', context = DOCS)
+gerrit_war(name = 'release',  context = DOCS + ['//plugins:core.zip'])
+
+API_DEPS = [
+  ':extension-api',
+  ':extension-api-src',
+  ':plugin-api',
+  ':plugin-api-src',
+]
+
+genrule(
+  name = 'api',
+  cmd = '',
+  srcs = [],
+  deps = API_DEPS,
+  out = '__fake.api__',
+)
+
+maven_install(deps = API_DEPS)
+maven_deploy(deps = API_DEPS)
+
+java_binary(name = 'extension-api', deps = [':extension-lib'])
+java_library(
+  name = 'extension-lib',
+  deps = [
+    '//gerrit-extension-api:api',
+    '//lib/guice:guice',
+    '//lib/guice:guice-servlet',
+    '//lib:servlet-api-3_0',
+  ],
+  export_deps = True,
+  visibility = ['PUBLIC'],
+)
+genrule(
+  name = 'extension-api-src',
+  cmd = 'ln -s $DEPS $OUT',
+  srcs = [],
+  deps = ['//gerrit-extension-api:api-src'],
+  out = 'extension-api-src.jar',
+)
+
+PLUGIN_API = [
+  '//gerrit-server:server',
+  '//gerrit-sshd:sshd',
+  '//gerrit-httpd:httpd',
+]
+
+java_binary(name = 'plugin-api', deps = [':plugin-lib'])
+java_library(
+  name = 'plugin-lib',
+  deps = PLUGIN_API,
+  export_deps = True,
+  visibility = ['PUBLIC'],
+)
+java_binary(
+  name = 'plugin-api-src',
+  deps = [
+    '//gerrit-extension-api:api-src',
+  ] + [d + '-src' for d in PLUGIN_API],
+)
+
+genrule(
+  name = 'download',
+  cmd = '${//tools:download_all}',
+  srcs = [],
+  deps = ['//tools:download_all'],
+  out = '__fake.download__',
+)
+
+genrule(
+  name = 'download_sources',
+  cmd = '${//tools:download_all} --src',
+  srcs = [],
+  deps = ['//tools:download_all'],
+  out = '__fake.download__',
+)
diff --git a/Documentation/BUCK b/Documentation/BUCK
new file mode 100644
index 0000000..15b7293
--- /dev/null
+++ b/Documentation/BUCK
@@ -0,0 +1,62 @@
+include_defs('//Documentation/asciidoc.defs')
+
+MAIN = ['//gerrit-pgm:pgm', '//gerrit-gwtui:ui_module']
+SRCS = glob(['*.txt'], excludes = ['licenses.txt'])
+HTML = [txt[0:-4] + '.html' for txt in SRCS]
+
+genrule(
+  name = 'html',
+  cmd = 'cd $TMP;' +
+    'mkdir -p Documentation/images;' +
+    'for s in $SRCS;do ln -s $s Documentation;done;' +
+    'mv Documentation/*.{jpg,png} Documentation/images;' +
+    'rm Documentation/licenses.txt;' +
+    'ln -s $SRCDIR/licenses.txt LICENSES.txt;' +
+    'zip -qr $OUT *',
+  srcs = [genfile(d) for d in HTML] +
+    glob([
+      'images/*.jpg',
+      'images/*.png',
+    ]) + [
+    genfile('licenses.html'),
+    genfile('licenses.txt'),
+  ],
+  deps = [':' + d for d in HTML] + [
+    ':licenses.html',
+    ':licenses.txt',
+  ],
+  out = 'html.zip',
+  visibility = ['PUBLIC'],
+)
+
+genasciidoc(
+  name = 'generate_html',
+  srcs = SRCS + [genfile('licenses.txt')],
+  outs = HTML + ['licenses.html'],
+  deps = [':config', ':licenses.txt'],
+  attributes = ['toc', 'newline="\\n"'],
+  backend = 'xhtml11',
+  conf_file = genfile('asciidoc.conf'),
+)
+
+genrule(
+  name = 'licenses.txt',
+  cmd = '${:gen_licenses} >$OUT',
+  srcs = [],
+  deps = [':gen_licenses'] + MAIN,
+  out = 'licenses.txt',
+)
+
+genrule(
+  name = 'config',
+  cmd = 'cp $SRCS $OUT &&' +
+    'echo "[attributes]" >>$OUT &&' +
+    'echo "revision=`git describe HEAD`" >>$OUT',
+  srcs = ['asciidoc.conf'],
+  out = 'asciidoc.conf',
+)
+
+python_binary(
+  name = 'gen_licenses',
+  main = 'gen_licenses.py',
+)
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index ab32d78..9ff0156 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -348,7 +348,7 @@
 
 This is where the Gerrit configuration of each project is residing.  This
 branch contains several files of importance: +project.config+, +groups+ and
-+rules.pl+.  Torgether they control access and behaviour during the change
++rules.pl+.  Torgether they control access and behavior during the change
 review process.
 
 
@@ -374,7 +374,6 @@
 These are references with added functionality to them compared to a regular
 git push operation.
 
-
 refs/for/<branch ref>
 ^^^^^^^^^^^^^^^^^^^^^
 
@@ -410,10 +409,6 @@
 Gerrit has several permission categories that can be granted to groups
 within projects, enabling functionality for that group's members.
 
-With the release of the Gerrit 2.2.x series, the web GUI for ACL
-configuration was rewritten from scratch.  Use this
-<<conversion_table,table>> to better understand the access rights
-conversions from the Gerrit 2.1.x to the Gerrit 2.2.x series.
 
 
 [[category_abandon]]
@@ -424,8 +419,9 @@
 to projects in Gerrit. It can give permission to abandon a specific
 change to a given ref.
 
-This also grants the permission to restore a change if the change
-can be uploaded.
+This also grants the permission to restore a change if the user also
+has link:#category_push[push permission] on the change's destination
+ref.
 
 
 [[category_create]]
@@ -752,7 +748,8 @@
 
 For every configured label `My-Name` in the project, there is a
 corresponding permission `label-My-Name` with a range corresponding to
-the defined values.
+the defined values. There is also a corresponding `labelAs-My-Name`
+permission that enables editing another user's label.
 
 Gerrit comes pre-configured with a default 'Code-Review' label that can
 be granted to groups within projects, enabling functionality for that
@@ -1118,43 +1115,6 @@
     label-Release-Process = -1..+1 group Release Engineers
 ====
 
-
-[[conversion_table]]
-Conversion table from 2.1.x series to 2.2.x series
---------------------------------------------------
-
-[options="header"]
-|=================================================================================
-|Gerrit 2.1.x                 |Gerrit 2.2.x
-|Code review                  |link:config-labels.html#label_Code-Review[Label: Code-Review]
-|Verify                       |link:config-labels.html#label_Verified[Label: Verify]
-|Forge Identity +1            |Forge <<category_forge_author,author>> identity
-|Forge Identity +2            |Forge <<category_forge_committer,committer>> & <<category_forge_author,author>> identity
-|Forge Identity +3            |Forge <<category_forge_server,server>> & <<category_forge_committer,committer>> & <<category_forge_author,author>> identity
-|Owner                        |<<category_owner,Owner>>
-|Push branch +1               |<<category_push_direct,Push>>
-|Push branch +2               |<<category_create,Create reference>> & <<category_push_direct,Push>>
-|Push branch +3               |<<category_push_direct,Push>> (with force) & <<category_create,Create reference>>
-|Push tag +1 & Push Branch +2 |No support to limit to push signed tag
-|Push tag +2 & Push Branch +2 |<<category_push_annotated,Push annotated tag>>
-|Push Branch +2 (refs/tags/*) |<<category_create,Create reference>> (refs/tags/...)
-|Push Branch +3 (refs/tags/*) |<<category_push_direct,Push>> (with force on refs/tags/...)
-|Read +1                      |<<category_read,Read>>
-|Read +2                      |<<category_read,Read>> & <<category_push_review,Push>> (refs/for/refs/...)
-|Read +3                      |<<category_read,Read>> & <<category_push_review,Push>> (refs/for/refs/...) & <<category_push_merge,Push Merge Commit>>
-|Submit                       |<<category_submit,Submit>>
-|=================================================================================
-
-
-[NOTE]
-In Gerrit 2.2.x, the way to set permissions for upload has changed entirely.
-To upload a change for review is no longer a separate permission type,
-instead you grant ordinary push permissions to the actual
-receiving reference. In practice this means that you set push permissions
-on `refs/for/refs/heads/<branch>` rather than permissions to upload changes
-on `refs/heads/<branch>`.
-
-
 [[global_capabilities]]
 Global Capabilities
 -------------------
@@ -1176,6 +1136,13 @@
 Below you find a list of capabilities available:
 
 
+[[capability_accessDatabase]]
+Access Database
+~~~~~~~~~~~~~~~
+
+Allow users to access the database using the `gsql` command.
+
+
 [[capability_administrateServer]]
 Administrate Server
 ~~~~~~~~~~~~~~~~~~~
@@ -1238,6 +1205,14 @@
 you need the <<capability_viewCaches,view caches capability>>.
 
 
+[[capability_generateHttpPassword]]
+Generate HTTP Password
+~~~~~~~~~~~~~~~~~~~~~~
+
+Allow the user to generate HTTP passwords for other users.  Typically this would
+be assigned to a non-interactive users group.
+
+
 [[capability_kill]]
 Kill Task
 ~~~~~~~~~
@@ -1293,11 +1268,25 @@
 command, but also to the web UI results pagination size.
 
 
-[[capability_accessDatabase]]
-Access Database
-~~~~~~~~~~~~~~~
+[[capability_runAs]]
+Run As
+~~~~~~
 
-Allow users to access the database using the `gsql` command.
+Allow users to impersonate any other user with the `X-Gerrit-RunAs`
+HTTP header on REST API calls, or the link:cmd-suexec.html[suexec]
+SSH command.
+
+When impersonating an administrator the Administrate Server capability
+is not honored.  This security feature tries to prevent a role with
+Run As capability from modifying the access controls in All-Projects,
+however modification may still be possible if the impersonated user
+has permission to push or submit changes on `refs/meta/config`.  Run
+As also blocks using most capabilities including Create User, Run
+Garbage Collection, etc., unless the capability is also explicitly
+granted to a group the administrator is a member of.
+
+Administrators do not automatically inherit this capability; it must
+be explicitly granted.
 
 
 [[capability_runGC]]
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
new file mode 100644
index 0000000..389e0ca
--- /dev/null
+++ b/Documentation/asciidoc.defs
@@ -0,0 +1,54 @@
+# 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.
+
+def genasciidoc(
+    name,
+    srcs = [],
+    outs = [],
+    deps = [],
+    attributes = [],
+    backend = None,
+    conf_file = None,
+    visibility = []):
+  cmd = ['asciidoc', '-o', '$OUT']
+  if backend:
+    cmd.extend(['-b', backend])
+  for attribute in attributes:
+    cmd.extend(['-a', attribute])
+  if conf_file:
+    cmd.append('-f')
+  cmd.append('$SRCS')
+
+  for p in zip(srcs, outs):
+    s, o = p
+    if conf_file:
+      src_list = [conf_file, s]
+    else:
+      src_list = [s]
+    genrule(
+      name = o,
+      cmd = ' '.join(cmd),
+      srcs = src_list,
+      deps = deps,
+      out = o,
+      visibility = visibility,
+    )
+  genrule(
+    name = name,
+    cmd = ':>$OUT',
+    srcs = [],
+    deps = [':' + o for o in outs],
+    out = name + '__done',
+    visibility = visibility,
+  )
diff --git a/Documentation/cmd-cherry-pick.txt b/Documentation/cmd-cherry-pick.txt
index d051a9a..15a8524 100644
--- a/Documentation/cmd-cherry-pick.txt
+++ b/Documentation/cmd-cherry-pick.txt
@@ -39,7 +39,7 @@
 ====
   $ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
 
-  $ curl -o ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
+  $ curl -Lo ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
 ====
 
 GERRIT
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index d0e56fd..666fe91 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -21,6 +21,7 @@
   [--require-change-id | --id]
   [[--branch <REF> | -b <REF>] ...]
   [--empty-commit]
+  [--max-object-size-limit <N>]
   { <NAME> | --name <NAME> }
 
 DESCRIPTION
@@ -144,6 +145,15 @@
 	Creates an initial empty commit for the Git repository of the
 	project that is newly created.
 
+--max-object-size-limit::
+	Define maximum Git object size for this project. Pushes containing an
+	object larger than this limit will be rejected. This can be used to
+	further limit the global
+  link:config-gerrit.html#receive.maxObjectSizeLimit[receive.maxObjectSizeLimit]
+	and cannot be used to increase that globally set limit.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
 
 EXAMPLES
 --------
diff --git a/Documentation/cmd-hook-commit-msg.txt b/Documentation/cmd-hook-commit-msg.txt
index bd602c1..d2f9648 100644
--- a/Documentation/cmd-hook-commit-msg.txt
+++ b/Documentation/cmd-hook-commit-msg.txt
@@ -61,7 +61,7 @@
 ====
   $ scp -p -P 29418 <your username>@<your Gerrit review server>:hooks/commit-msg <local path to your git>/.git/hooks/
 
-  $ curl -o <local path to your git>/.git/hooks/commit-msg <your Gerrit http URL>/tools/hooks/commit-msg
+  $ curl -Lo <local path to your git>/.git/hooks/commit-msg <your Gerrit http URL>/tools/hooks/commit-msg
 ====
 
 A specific example of this might look something like this:
@@ -70,7 +70,7 @@
 ====
   $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg ~/duhproject/.git/hooks/
 
-  $ curl -o ~/duhproject/.git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
+  $ curl -Lo ~/duhproject/.git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 ====
 
 Make sure the hook file is executable:
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 780231c..4f6c39d 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -12,8 +12,8 @@
   $ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
   $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
 
-  $ curl -o ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
-  $ curl -o .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
+  $ curl -Lo ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
+  $ curl -Lo .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 
 For more details on how to determine the correct SSH port number,
 see link:user-upload.html#test_ssh[Testing Your SSH Connection].
@@ -60,6 +60,9 @@
 link:cmd-ls-groups.html[gerrit ls-groups]::
 	List groups visible to the caller.
 
+link:cmd-ls-members.html[gerrit ls-members]::
+	List the membership of a group visible to the caller.
+
 link:cmd-ls-projects.html[gerrit ls-projects]::
 	List projects visible to the caller.
 
@@ -120,6 +123,9 @@
 link:cmd-gsql.html[gerrit gsql]::
 	Administrative interface to active database.
 
+link:cmd-set-members.html[gerrit set-members]::
+	Set group members.
+
 link:cmd-set-project-parent.html[gerrit set-project-parent]::
 	Change the project permissions are inherited from.
 
diff --git a/Documentation/cmd-ls-members.txt b/Documentation/cmd-ls-members.txt
new file mode 100644
index 0000000..9814ff2
--- /dev/null
+++ b/Documentation/cmd-ls-members.txt
@@ -0,0 +1,64 @@
+gerrit ls-members
+================
+
+NAME
+----
+gerrit ls-members - Show members of a given group
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit ls-members GROUPNAME'
+  [--recursive]
+
+DESCRIPTION
+-----------
+Displays the members of the given group, one per line, so long as the given
+group is visible to the user. The users' id, username, full name and email are
+shown tab-separated.
+
+ACCESS
+------
+Any user who has configured an SSH key.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts. Output is either an error
+message or a heading followed by zero or more lines, one for each member of the
+group. If any field is not set, or if the field is the user's full name and the
+name is empty, "n/a" is emitted as the field value.
+
+All non-printable characters (ASCII value 31 or less) are escaped
+according to the conventions used in languages like C, Python, and Perl,
+employing standard sequences like `\n` and `\t`, and `\xNN` for all
+others. In shell scripts, the `printf` command can be used to unescape
+the output.
+
+OPTIONS
+-------
+--recursive::
+	If a member of the group is itself a group, the sub-group's
+	members are included in the list. Otherwise members of any sub-group
+	are not shown and no indication is given that a sub-group is present
+
+EXAMPLES
+--------
+
+List members of the Administrators group:
+=====
+	$ ssh -p 29418 review.example.com gerrit ls-members Administrators
+	id      username  full name    email
+	100000  jim     Jim Bob somebody@example.com
+	100001  johnny  John Smith      n/a
+	100002  mrnoname        n/a     someoneelse@example.com
+=====
+
+List members of a non-existent group:
+=====
+	$ ssh -p 29418 review.example.com gerrit ls-members BadlySpelledGroup
+	Group not found or not visible
+=====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index 65c21db..70213da 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -11,7 +11,6 @@
 'ssh' -p <port> <host> 'gerrit review'
   [--project <PROJECT> | -p <PROJECT>]
   [--message <MESSAGE> | -m <MESSAGE>]
-  [--force-message]
   [--submit | -s]
   [--abandon | --restore]
   [--publish]
@@ -51,22 +50,6 @@
 	Optional cover letter to include as part of the message
 	sent to reviewers when the approval states are updated.
 
---force-message::
-	Option which allows Gerrit to publish the --message, even
-	when the labels could not be applied due to the change being
-	closed.
-+
-Used by some scripts/CI-systems, where the results (or links
-to the result) are posted as a message after completion of a
-build (often together with a label-change, indicating the success
-of the build).
-+
-If the message is posted successfully, the command will return
-successfully, even if the label could not be changed.
-+
-This option will not force the message to be posted if the command
-fails because the user is not permitted to change the label.
-
 --help::
 -h::
 	Display site-specific usage information, including the
diff --git a/Documentation/cmd-set-members.txt b/Documentation/cmd-set-members.txt
new file mode 100644
index 0000000..7524893
--- /dev/null
+++ b/Documentation/cmd-set-members.txt
@@ -0,0 +1,82 @@
+gerrit set-members
+==================
+
+NAME
+----
+gerrit set-members - Set group members
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit set-members'
+  [--add USER ...]
+  [--remove USER ...]
+  [--include GROUP ...]
+  [--exclude GROUP ...]
+  [--]
+  <GROUP> ...
+
+DESCRIPTION
+-----------
+Set the group members for the specified groups.
+
+OPTIONS
+-------
+<GROUP>::
+	Required; name of the group for which the members should be set.
+	The members for multiple groups can be set at once by specifying
+	multiple groups.
+
+--add::
+-a::
+	A user that should be added to the specified groups. Multiple
+	users can be added at once by using this option multiple times.
+
+--remove::
+-r::
+	Remove this user from the specified groups. Multiple users can be
+	removed at once by using this option multiple times.
+
+--include::
+-i::
+	A group that should be included to the specified groups. Multiple
+	groups can be included at once by using this option multiple
+	times.
+
+--exclude::
+-e::
+	Exclude this group from the specified groups. Multiple groups can
+	be excluded at once by using this option multiple times.
+
+The `set-members` command is processing the options in the following
+order: `--remove`, `--exclude`, `--add`, `--include`
+
+ACCESS
+------
+Any user who has configured an SSH key.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+EXAMPLES
+--------
+
+Add alice and bob, but remove eve from the groups my-committers and
+my-verifiers.
+=====
+	$ ssh -p 29418 review.example.com gerrit set-members \
+	  -a alice@example.com -a bob@example.com \
+	  -r eve@example.com my-committers my-verifiers
+=====
+
+Include the group my-friends into the group my-committers, but
+exclude the included group my-testers from the group my-committers.
+=====
+	$ ssh -p 29418 review.example.com gerrit set-members \
+	  -i my-friends -e my-testers my-committers
+=====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-set-project.txt b/Documentation/cmd-set-project.txt
index a0af910..7ff534b 100644
--- a/Documentation/cmd-set-project.txt
+++ b/Documentation/cmd-set-project.txt
@@ -16,6 +16,7 @@
   [--content-merge <true|false|inherit>]
   [--change-id <true|false|inherit>]
   [--project-state <STATE> | --ps <STATE>]
+  [--max-object-size-limit <N>]
   <NAME>
 
 DESCRIPTION
@@ -93,6 +94,15 @@
 is granted, but all modification operations are disabled.
 * HIDDEN: the project is not visible for those who are not owners
 
+--max-object-size-limit::
+	Define maximum Git object size for this project. Pushes containing an
+	object larger than this limit will be rejected. This can be used to
+	further limit the global
+  link:config-gerrit.html#receive.maxObjectSizeLimit[receive.maxObjectSizeLimit]
+	and cannot be used to increase that globally set limit.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
 EXAMPLES
 --------
 Change project `example` to be hidden, require change id, don't use content merge
@@ -105,4 +115,4 @@
 
 GERRIT
 ------
-Part of link:index.html[Gerrit Code Review]
\ No newline at end of file
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index 126b2a0..d426508 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -25,6 +25,10 @@
 	operating system, and other details about the environment
 	that Gerrit Code Review is running in.
 
+--width::
+-w::
+	Width of the output table.
+
 ACCESS
 ------
 Caller must be a member of the privileged 'Administrators' group,
diff --git a/Documentation/cmd-show-connections.txt b/Documentation/cmd-show-connections.txt
index 8404a97..ab9fadf 100644
--- a/Documentation/cmd-show-connections.txt
+++ b/Documentation/cmd-show-connections.txt
@@ -32,6 +32,11 @@
 -n::
 	Show client hostnames as IP addresses instead of DNS hostname.
 
+--wide::
+-w::
+	Do not format the output to the terminal width (default of
+	80 columns).
+
 DISPLAY
 -------
 
diff --git a/Documentation/cmd-show-queue.txt b/Documentation/cmd-show-queue.txt
index f99e342..4ab3097 100644
--- a/Documentation/cmd-show-queue.txt
+++ b/Documentation/cmd-show-queue.txt
@@ -18,7 +18,7 @@
 Gerrit contains an internal scheduler, similar to cron, that it
 uses to queue and dispatch both short and long term activity.
 
-Tasks that are completed or cancelled exit the queue very quickly
+Tasks that are completed or canceled exit the queue very quickly
 once they enter this state, but it can be possible to observe tasks
 in these states.
 
@@ -37,6 +37,13 @@
 ---------
 Intended for interactive use only.
 
+OPTIONS
+-------
+--wide::
+-w::
+	Do not format the output to the terminal width (default of
+	80 columns).
+
 DISPLAY
 -------
 
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 6da0ef0..512c801 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -14,7 +14,7 @@
 -----------
 
 Provides a portal into the major events occurring on the server,
-outputing activity data in real-time to the client.  Events are
+outputting activity data in real-time to the client.  Events are
 filtered by the caller's access permissions, ensuring the caller
 only receives events for changes they can view on the web, or in
 the project repository.
@@ -152,6 +152,15 @@
 
 reviewer:: link:json.html#account[account attribute]
 
+Topic Changed
+^^^^^^^^^^^^^
+type:: "topic-changed"
+
+change:: link:json.html#change[change attribute]
+
+changer:: link:json.html#account[account attribute]
+
+oldTopic:: Topic name before it was changed.
 
 SEE ALSO
 --------
diff --git a/Documentation/cmd-suexec.txt b/Documentation/cmd-suexec.txt
index baffd53..78fc361 100644
--- a/Documentation/cmd-suexec.txt
+++ b/Documentation/cmd-suexec.txt
@@ -19,10 +19,14 @@
 
 DESCRIPTION
 -----------
-The suexec command can only be invoked by the magic user `Gerrit
-Code Review` and permits executing any other command as any other
+The suexec command permits executing any other command as any other
 registered user account.
 
+suexec can only be invoked by the magic user `Gerrit Code Review`,
+or any user granted granted the link:access-control.html#capability_runAs[Run As]
+capability. The run as capability is permitted to be used only if
+link:config-gerrit.html[auth.enableRunAs] is true.
+
 OPTIONS
 -------
 
@@ -39,7 +43,8 @@
 ACCESS
 ------
 Caller must be the magic user Gerrit Code Review using the SSH
-daemon's host key or a key on this daemon's peer host key ring.
+daemon's host key, or a key on this daemon's peer host key ring,
+or a user granted the Run As capability.
 
 SCRIPTING
 ---------
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 3591c77..5c9bf6a 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -390,6 +390,18 @@
 +
 By default this is set to false.
 
+[[auth.enableRunAs]]auth.enableRunAs::
++
+If true HTTP REST APIs will accept the `X-Gerrit-RunAs` HTTP request
+header from any users granted the link:access-control.html#capability_runAs[Run As]
+capability. The header and capability permit the authenticated user
+to impersonate another account.
++
+If false the feature is disabled and cannot be re-enabled without
+editing gerrit.config and restarting the server.
++
+Default is true.
+
 [[cache]]Section cache
 ~~~~~~~~~~~~~~~~~~~~~~
 
@@ -497,7 +509,7 @@
 cache `"adv_bases"`::
 +
 Used only for push over smart HTTP when branch level access controls
-are enabled.  The cache entry contains all commits that are avaliable
+are enabled.  The cache entry contains all commits that are available
 for the client to use as potential delta bases.  Push over smart HTTP
 requires two HTTP requests, and this cache tries to carry state from
 the first request into the second to ensure it can complete.
@@ -671,7 +683,7 @@
 Boolean to enable or disable the computation of intraline differences
 when populating a diff cache entry.  This flag is provided primarily
 as a backdoor to disable the intraline difference feature if
-necessary.  To maintain backwards compatability with prior versions,
+necessary.  To maintain backwards compatibility with prior versions,
 this setting will fallback to `cache.diff.intraline` if not set in the
 configuration.
 +
@@ -923,7 +935,7 @@
 Largest object size, in bytes, that JGit will allocate as a
 contiguous byte array.  Any file revision larger than this threshold
 will have to be streamed, typically requiring the use of temporary
-files under '$GIT_DIR/objects' to implement psuedo-random access
+files under '$GIT_DIR/objects' to implement pseudo-random access
 during delta decompression.
 +
 Servers with very high traffic should set this to be larger than
@@ -948,7 +960,7 @@
 can be made by the JVM native code.
 +
 In server applications (such as Gerrit) that need to access many
-pack files, setting this to true risks artifically running out
+pack files, setting this to true risks artificially running out
 of virtual address space, as the garbage collector cannot reclaim
 unused mapped spaces fast enough.
 +
@@ -1235,6 +1247,19 @@
 by the system administrator, and might not even be running on the
 same host as Gerrit.
 
+[[gerrit.installCommitMsgHookCommand]]gerrit.installCommitMsgHookCommand::
++
+Optional command to install the `commit-msg` hook. Typically of the
+form:
+----
+fetch-cmd some://url/to/commit-msg .git/hooks/commit-msg ; chmod +x .git/hooks/commit-msg
+----
++
+By default unset; falls back to using scp from the canonical SSH host,
+or curl from the canonical HTTP URL for the server.  Only necessary if a
+proxy or other server/network configuration prevents clients from
+fetching from the default location.
+
 [[gerrit.gitHttpUrl]]gerrit.gitHttpUrl::
 +
 Optional base URL for repositories available over the HTTP
@@ -1416,6 +1441,11 @@
 Optional filename for the reviewer added hook, if not specified then
 `reviewer-added` will be used.
 
+[[hooks.topicChangedHook]]hooks.topicChangedHook::
++
+Optional filename for the topic changed hook, if not specified then
+`topic-changed` will be used.
+
 [[hooks.claSignedHook]]hooks.claSignedHook::
 +
 Optional filename for the CLA signed hook, if not specified then
@@ -1582,7 +1612,7 @@
 +
 Minimum number of spare threads to keep in the worker thread pool.
 This number must be at least 1 larger than httpd.acceptorThreads
-multipled by the number of httpd.listenUrls configured.
+multiplied by the number of httpd.listenUrls configured.
 +
 By default, 5, suitable for most lower-volume traffic sites.
 
@@ -1623,6 +1653,38 @@
 +
 By default, 5 minutes.
 
+[[httpd.filterClass]]httpd.filterClass::
++
+Class that implements the javax.servlet.Filter interface
+for filtering any HTTP related traffic going through the Gerrit
+HTTP protocol.
+Class is loaded and configured in the Gerrit Jetty container
+and run in front of all Gerrit URL handlers, allowing the filter
+to inspect, modify, allow or reject each request.
+It needs to be provided as JAR library
+under $GERRIT_SITE/lib as it is resolved using the default Gerrit class
+loader and cannot be dynamically loaded by a plugin.
++
+Failing to load the Filter class would result in a Gerrit start-up
+failure, as this class is supposed to provide mandatory filtering
+in front of Gerrit HTTP protocol.
++
+Typical usage is in conjunction with the auth.type=HTTP as replacement
+of an Apache HTTP proxy layer as security enforcement on top of Gerrit
+by returning a trusted username as HTTP Header.
++
+Example of using a security library secure.jar under $GERRIT_SITE/lib
+that provides a org.anyorg.MySecureFilter Servlet Filter that enforces
+a trusted username in the `TRUSTED_USER` HTTP Header:
+
+----
+[auth]
+	type = HTTP
+	httpHeader = TRUSTED_USER
+
+[http]
+	filterClass = org.anyorg.MySecureFilter
+----
 
 [[ldap]]Section ldap
 ~~~~~~~~~~~~~~~~~~~~
@@ -1905,7 +1967,7 @@
 Note the `renewTGT` property to make sure the TGT does not expire,
 and `useTicketCache` to use the TGT supplied by the operating system. As
 the whole point of using GSSAPI is to have passwordless authentication
-to the LDAP service, this option does not aquire a new TGT on its own.
+to the LDAP service, this option does not acquire a new TGT on its own.
 
 On Windows servers the registry key `HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters`
 must have the DWORD value `allowtgtsessionkey` set to 1 and the account must not
@@ -2042,6 +2104,10 @@
 Gerrit administrators can use this setting to prevent developers
 from pushing objects which are too large to Gerrit.
 +
+This setting can also be set in the `project.config` in order to further
+reduce the global setting. The project specific setting is only honored
+when it further reduces the global limit.
++
 Default is zero.
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
@@ -2062,7 +2128,7 @@
 set update.
 +
 Defaults to 1, using only the main receive thread. This feature is for
-databases with very high latency that can benfit from concurrent
+databases with very high latency that can benefit from concurrent
 operations when multiple changes are impacted at once.
 
 [[receive.timeout]]receive.timeout::
@@ -2073,7 +2139,7 @@
 be specified using standard time unit abbreviations ('ms', 'sec',
 'min', etc.).
 +
-Default is 2 minutes. If no unit is specified, millisconds
+Default is 2 minutes. If no unit is specified, milliseconds
 is assumed.
 
 
@@ -2128,7 +2194,7 @@
 * `USER`
 +
 Gerrit will set the From header to use the current user's
-Full Name and Preferred Email.  This may cause messsages to be
+Full Name and Preferred Email.  This may cause messages to be
 classified as spam if the user's domain has SPF or DKIM enabled
 and <<sendemail.smtpServer,sendemail.smtpServer>> is not a trusted
 relay for that domain.
@@ -2262,28 +2328,6 @@
 and text results for changes. If false, the URL is disabled and
 returns 404 to clients. Default is true, enabling `/query`.
 
-[[site.upgradeSchemaOnStartup]]site.upgradeSchemaOnStartup::
-+
-Control whether schema upgrade should be done on Gerrit startup. The following
-values are supported:
-+
-* `OFF`
-+
-No automatic schema upgrade on startup.
-+
-* `AUTO`
-+
-Perform schema migration on startup, if necessary.  If, as a result of
-schema migration, there would be any unused database objects they will
-be dropped automatically.
-+
-* `AUTO_NO_PRUNE`
-+
-Like `AUTO` but unused database objects will not be pruned.
-
-+
-The default is `OFF`.
-
 [[ssh-alias]] Section ssh-alias
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -2375,7 +2419,7 @@
 +
 If the number of threads requested for non-interactive users is larger
 than the total number of threads allocated in sshd.threads, then the
-value of sshd.threads is increased to accomodate the requested value.
+value of sshd.threads is increased to accommodate the requested value.
 +
 By default, 0.
 
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index e5edda8..7ba15b8 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -116,7 +116,7 @@
 # logo to use
 $logo = "git-logo.png";
 
-# the ‘favicon’
+# the favicon
 $favicon = "git-favicon.png";
 ----
 
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 1236077..e232c0c 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -90,7 +90,7 @@
 Called whenever a change has been abandoned.
 
 ====
-  change-abandoned --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --abandoner <abandoner> --reason <reason>
+  change-abandoned --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --abandoner <abandoner> --commit <sha1> --reason <reason>
 ====
 
 change-restored
@@ -99,7 +99,7 @@
 Called whenever a change has been restored.
 
 ====
-  change-restored --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --restorer <restorer> --reason <reason>
+  change-restored --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --restorer <restorer> --commit <sha1> --reason <reason>
 ====
 
 ref-updated
@@ -120,6 +120,15 @@
   reviewer-added --change <change id> --change-url <change url> --project <project name> --branch <branch> --reviewer <reviewer>
 ====
 
+topic-changed
+~~~~~~~~~~~~~
+
+Called whenever a change's topic is changed from the Web UI or via the REST API.
+
+====
+  topic-changed --change <change id> --changer <changer> --old-topic <old topic> --new-topic <new topic>
+====
+
 cla-signed
 ~~~~~~~~~~
 
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index 8de8e59..3b8bffa 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -65,6 +65,13 @@
 related to a user editing the commit message through the Gerrit UI.  It is a
 `ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
 
+Footer.vm
+~~~~~~~~~
+
+The `Footer.vm` template will determine the contents of the footer text
+appended to the end of all outgoing emails after the ChangeFooter and
+CommentFooter.
+
 Merged.vm
 ~~~~~~~~~
 
@@ -83,15 +90,10 @@
 ~~~~~~~~~~~~
 
 The `NewChange.vm` template will determine the contents of the email related
-to a user submitting a new change for review. It is a `ChangeEmail`: see
-`ChangeSubject.vm` and `ChangeFooter.vm`.
-
-RebasedPatchSet.vm
-~~~~~~~~~~~~~~~~~~
-
-The `RebasedPatchSet.vm` template will determine the contents of the email
-related to a user rebasing a patchset for a change through the Gerrit UI.
-It is a `ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
+to a user submitting a new change for review. This includes changes created
+by actions made by the user in the Web UI such as cherry picking a commit or
+reverting a change.  It is a `ChangeEmail`: see `ChangeSubject.vm` and
+`ChangeFooter.vm`.
 
 RegisterNewEmail.vm
 ~~~~~~~~~~~~~~~~~~~
@@ -103,7 +105,9 @@
 ~~~~~~~~~~~~~~~~~~
 
 The `ReplacePatchSet.vm` template will determine the contents of the email
-related to a user submitting a new patchset for a change.  It is a
+related to a user submitting a new patchset for a change.  This includes
+patchsets created by actions made by the user in the Web UI such as editing
+the commit message, cherry picking a commit, or rebasing a change.  It is a
 `ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
 
 Restored.vm
diff --git a/Documentation/config-validation.txt b/Documentation/config-validation.txt
index ab5d04a..fd8c0bd 100644
--- a/Documentation/config-validation.txt
+++ b/Documentation/config-validation.txt
@@ -2,14 +2,18 @@
 ======================================
 
 Gerrit supports link:dev-plugins.html[plugin-based] validation of
-uploaded commits.
+commits.
 
-This allows plugins to perform additional validation checks against
-uploaded commits, and send back a warning or error message to the git
-client.
+Plugins implementing the `CommitValidationListener` interface can
+perform additional validation checks against new commits.
 
-To make use of this feature, a plugin must implement the `CommitValidationListener`
-interface.
+If the commit fails the validation, the plugin can either provide a
+message that will be sent back to the git client, or throw an exception
+which will cause the commit to be rejected.
+
+Validation applies to both commits uploaded via `git push`, and new
+commits generated via Gerrit's Web UI features such as the rebase, revert
+and cherry-pick buttons.
 
 Out of the box, Gerrit includes a plugin that checks the length of the
 subject and body lines of commit messages on uploaded commits.
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
new file mode 100644
index 0000000..5aca9a4
--- /dev/null
+++ b/Documentation/dev-buck.txt
@@ -0,0 +1,312 @@
+Gerrit Code Review - Building with Buck
+=======================================
+
+
+Installation
+------------
+
+There is currently no binary distribution of Buck, so it has to be manually
+built and installed.  Apache Ant is required.  Currently only Linux and Mac
+OS are supported.
+
+Clone the git and build it:
+
+----
+  git clone https://gerrit.googlesource.com/buck
+  cd buck
+  ant
+----
+
+Make sure you have a `bin/` directory in your home directory and that
+it is included in your path:
+
+----
+  mkdir ~/bin
+  PATH=~/bin:$PATH
+----
+
+Add a symbolic link in `~/bin` to the buck executable:
+
+----
+  ln -s `pwd`/bin/buck ~/bin/
+----
+
+Verify that `buck` is accessible:
+
+----
+  which buck
+----
+
+
+[[eclipse]]
+Eclipse Integration
+-------------------
+
+
+Generating the Eclipse Project
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Create the Eclipse project by building the `eclipse` target:
+
+----
+  buck build eclipse
+----
+
+In Eclipse, choose 'Import existing project' and select the `gerrit` project
+from the current working directory.
+
+Expand the `gerrit` project, right-click on the `buck-out` folder, select
+'Properties', and then under 'Attributes' check 'Derived'.
+
+Note that if you make any changes in the project configuration that get
+saved to the `.project` file, for example adding Resource Filters on a
+folder, they will be overwritten the next time you run `buck build eclipse`.
+
+
+Refreshing the Classpath
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Normally `buck build eclipse` does everything necessary to generate a working Eclipse
+environment, but if the code doesn't compile and an updated classpath is needed, the
+Eclipse project can be refreshed and missing dependency JARs can be downloaded by
+building the `eclipse_project` and `download` targets:
+
+----
+  buck build eclipse_project download
+----
+
+
+Attaching Sources
+~~~~~~~~~~~~~~~~~
+
+To save time and bandwidth source JARs are only downloaded by the buck
+build where necessary to compile Java source into JavaScript using the
+GWT compiler.  Additional sources may be obtained, allowing Eclipse to
+show documentation or dive into the implementation of a library JAR:
+
+----
+  buck build download_sources
+----
+
+
+[[build]]
+Building on the Command Line
+----------------------------
+
+
+Gerrit Development WAR File
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To build the Gerrit web application:
+
+----
+  buck build gerrit
+----
+
+The output executable WAR will be placed in:
+
+----
+  buck-out/gen/gerrit.war
+----
+
+
+Extension and Plugin API JAR Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To build the extension and plugin API JAR files:
+
+----
+  buck build api
+----
+
+The output JAR files will be placed in:
+
+----
+  buck-out/gen/{extension,plugin}-api.jar
+----
+
+Install {extension,plugin}-api to the local maven repository:
+
+----
+  buck build api_install
+----
+
+Deploy {extension,plugin}-api to the remote maven repository
+
+----
+  buck build api_deploy
+----
+
+The type of the repo is induced from the Gerrit version name, i.e.
+* 2.8-SNAPSHOT: shapshot repo
+* 2.8: release repo
+
+Plugins
+~~~~~~~
+
+To build all core plugins:
+
+----
+  buck build plugins:core
+----
+
+The output JAR files for individual plugins will be placed in:
+
+----
+  buck-out/gen/plugins/<name>/<name>.jar
+----
+
+The JAR files will also be packaged in:
+
+----
+  buck-out/gen/plugins/core.zip
+----
+
+To build a specific plugin:
+
+----
+  buck build plugins/<name>
+----
+
+The output JAR file will be be placed in:
+
+----
+  buck-out/gen/plugins/<name>/<name>.jar
+----
+
+Note that when building an individual plugin, the `core.zip` package
+is not regenerated.
+
+
+[[tests]]
+Running Unit Tests
+------------------
+
+To run all tests including acceptance tests:
+
+----
+  buck test --all
+----
+
+To exclude slow tests:
+
+----
+  buck test --all -e slow
+----
+
+
+Dependencies
+------------
+
+Dependency JARs are normally downloaded automatically, but Buck can inspect
+its graph and download any missing JAR files.  This is useful to enable
+subsequent builds to run without network access.
+
+Force a download of dependency JARs by building the `download` target:
+
+----
+  buck build download
+----
+
+When downloading from behind a proxy (which is common in some corporate
+environments), it might be necessary to explicitly specify the proxy that
+is then used by `curl`:
+
+----
+  export http_proxy=http://<proxy_user_id>:<proxy_password>@<proxy_server>:<proxy_port>
+----
+
+Redirection to local mirrors of Maven Central and the Gerrit storage
+bucket is supported by defining specific properties in
+`local.properties`, a file that is not tracked by Git:
+
+----
+  echo download.GERRIT = http://nexus.my-company.com/ >>local.properties
+  echo download.MAVEN_CENTRAL = http://nexus.my-company.com/ >>local.properties
+----
+
+The `local.properties` file may be placed in the root of the gerrit repository
+being built, or in `~/.gerritcodereview/`.  The file in the root of the gerrit
+repository has precedence.
+
+
+Caching Build Results
+~~~~~~~~~~~~~~~~~~~~~
+
+Build results can be locally cached, saving rebuild time when
+switching between Git branches. Buck's documentation covers
+caching in link:http://facebook.github.io/buck/concept/buckconfig.html[buckconfig].
+The trivial case using a local directory is:
+
+----
+  cat >.buckconfig.local <<EOF
+  [cache]
+    mode = dir
+    dir = buck-cache
+  EOF
+----
+
+
+Build Process Switch Exit Criteria
+----------------------------------
+
+The switch to Buck is an experimental process. Buck will become the
+primary build for Gerrit only when the following conditions are met.
+
+1. Windows support.
++
+Facebook has an intern who will be working on this (summer 2013).
+
+2. Bootstrap and stable version support.
++
+From a fresh Gerrit clone on a machine without Buck (but with some
+reasonable subset of Buck's dependencies, e.g. Python 2.7), a new
+Gerrit developer should be able to set up and start building with
+Buck by running approximately one command. There should also be some
+idea of a "stable" version of Buck, even if we just tie our build
+to specific known-good SHAs. Binary distributions are another plus,
+which I believe the Buck team has planned.
+
+3. Shawn's Buck fork merged upstream.
++
+Shawn has a link:https://gerrit.googlesource.com/buck/+log/github-master..master[fork of Buck]
+with some patches necessary to build Gerrit and run its unit tests.
+These patches (or their equivalents) must be in the upstream Buck tree.
+
+4. Fix all incidental issues.
++
+Things come up that don't work. Martin just ran out of file
+descriptors, which sounds like an upstream bug.
++
+There should be a consensus that new bugs like this in upstream
+Buck are not constantly being introduced.
+
+5. Support development of custom plugins.
++
+There are three different alternatives for custom plugins:
++
+The first is to build with BUCK in tree; your plugin builds against
+whatever version of Gerrit you have the sources checked out for. This
+is the simplest method on master right now. The BUCK definition is
+only a few lines of code and the rest "just works". But you are
+working from a Gerrit source tree, which is maybe not the ideal way to
+develop.
++
+Another is to continue to use Maven. We just have to package the
+archetypes to support creating a new plugin, but existing plugins can
+develop against the API JAR(s) if they are installed into a Maven
+repository. tools/deploy_api.sh is how we did this for release
+versions of Gerrit. Something similar probably still be used with BUCK
+to publish the precompiled JARs. (Note: this is partially done with the
+target: `buck build api_install`; after issuing this command the new API
+version can be consumed by Maven driven custom plugins).
++
+The third option is to support a BUCK based plugin build outside of
+the Gerrit tree. This is harder because there is functionality
+developed as macros in the Gerrit tree that plugins would want to use
+(e.g. gerrit_plugin rule).
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 84cb1e0..edc072d 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -94,13 +94,22 @@
 ====
 
 The Change-Id is, as usual, created by a local git hook.  To install it, simply
-copy one from the checkout and make it executable:
+copy it from the checkout and make it executable:
 
 ====
   cp ./gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg .git/hooks/
   chmod +x .git/hooks/commit-msg
 ====
 
+If you are working on core plugins, you will also need to install the
+same hook in the submodules:
+
+====
+  export hook=$(pwd)/.git/hooks/commit-msg
+  git submodule foreach 'cp -p "$hook" "$(git rev-parse --git-dir)/hooks/"'
+====
+
+
 To set up git's remote for easy pushing, run the following:
 
 ====
@@ -127,7 +136,7 @@
     in the formatting guidelines.  This is especially true within the
     same file.
   * Review your change in Gerrit to see if it highlights
-    mistakingly deleted/added spaces on lines, trailing spaces.
+    mistakenly deleted/added spaces on lines, trailing spaces.
   * Line length should be 80 or less, unless the code reads
     better with something slightly longer.  Shorter lines not only
     help reviewers who may use a tablet to review the code, but future
@@ -271,6 +280,10 @@
 Developers concerned with stable branches are encouraged to backport or push
 patchsets to these branches, even if no new release is planned.
 
+Fixes that are known to be needed for a particular release should be pushed
+for review on that release's stable branch.  It will then be included in
+the master branch when the stable branch is merged back.
+
 
 GERRIT
 ------
diff --git a/Documentation/dev-design.txt b/Documentation/dev-design.txt
index 2e43b96..3cb58b1 100644
--- a/Documentation/dev-design.txt
+++ b/Documentation/dev-design.txt
@@ -66,7 +66,7 @@
 
 Gerrit 2.x is a complete rewrite of the Gerrit fork, completely
 changing the implementation from Python on Google App Engine, to Java
-on a J2EE servlet container and a SQL database.
+on a J2EE servlet container and an SQL database.
 
 * link:http://video.google.com/videoplay?docid=-8502904076440714866[Mondrian Code Review On The Web]
 * link:http://code.google.com/p/rietveld/[Rietveld - Code Review for Subversion]
@@ -445,7 +445,7 @@
 -------
 
 Gerrit targets for sub-250 ms per page request, mostly by using
-very compact JSON payloads bewteen client and server.  However, as
+very compact JSON payloads between client and server.  However, as
 most of the serving stack (network, hardware, metadata
 database) is out of control of the Gerrit developers, no real
 guarantees can be made about latency.
@@ -474,7 +474,7 @@
 Out of the box, Gerrit will handle the "Default Maximum". Site
 administrators may reconfigure their servers by editing gerrit.config
 to run closer to the estimated maximum if sufficient memory is made
-avaliable to the JVM and the relevant cache.*.memoryLimit variables
+available to the JVM and the relevant cache.*.memoryLimit variables
 are increased from their defaults.
 
 Discussion
@@ -671,7 +671,7 @@
 
 PostgreSQL and MySQL can be configured to replicate their data to
 other systems, where they are applied to a warm-standby backup in
-real time.  Gerrit instances which care about reduduncy will setup
+real time.  Gerrit instances which care about redundancy will setup
 this feature of PostgreSQL or MySQL to ensure the warm-standby is
 reasonably current should the master go offline.
 
@@ -699,7 +699,7 @@
 Changes submitted and merged into a branch also update the
 Git reflog.  These logs are available only to the Gerrit site
 administrator, and they are not replicated through the automatic
-replication noted earlier.  These logs are primarly recorded for an
+replication noted earlier.  These logs are primarily recorded for an
 "oh s**t" moment where the administrator has to rewind data.  In most
 installations they are a waste of disk space.  Future versions of
 JGit may allow disabling these logs, and Gerrit may take advantage
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 019c78f..d2fc8f0 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -8,19 +8,6 @@
 runtime debugging environment.
 
 
-[[maven]]
-Maven Plugin
-------------
-
-Install the Maven Integration plugins.
-
-In Eclipse version 3.7 (Indigo) and later, these are available in the
-default update site and can be found under the 'Collaboration' category.
-
-For older versions the update site must be manually added; the link can
-be found on the http://www.eclipse.org/m2e/download/[m2eclipse download page].
-
-
 [[Formatting]]
 Code Formatter Settings
 -----------------------
@@ -32,23 +19,12 @@
 settings prefer when formatting source code.
 
 
-Import Projects
----------------
-
-Import the projects into Eclipse by going to File -> Import... -> Maven ->
-Existing Maven Projects and selecting the directory containing pom.xml.
-
-Some of the source code is generated with ANTLR sources.  To build
-these files, right click on the imported projects, Maven -> Update
-Project Configuration.  This will resolve compile errors identified
-after import.
-
-
 Site Initialization
 -------------------
 
-link:dev-readme.html#build[Build] once on the command line and
-then follow link:dev-readme.html#init[Site Initialization] in the
+Build once on the command line with
+link:dev-buck.html#build[Buck] and then follow
+link:dev-readme.html#init[Site Initialization] in the
 Developer Setup guide to configure a local site for testing.
 
 
@@ -58,10 +34,10 @@
 Running the Daemon
 ~~~~~~~~~~~~~~~~~~
 
-Duplicate the existing `pgm_daemon` launch configuration:
+Duplicate the existing launch configuration:
 
 * Run -> Debug Configurations ...
-* Java Application -> `pgm_daemon`
+* Java Application -> `buck_daemon_ui_*`
 * Right click, Duplicate
 
 * Modify the name to be unique.
@@ -73,25 +49,16 @@
 
 * Switch to Common tab.
 * Change Save as to be Local file.
+* Close the Debug Configurations dialog and save the changes when prompted.
 
 
-[[hosted-mode]]
 Running Hosted Mode
 ~~~~~~~~~~~~~~~~~~~
 
-To debug the GWT code executing in the web browser, two additional Git
-repositories need to be cloned.
-
-* https://gerrit.googlesource.com/gwtjsonrpc
-* https://gerrit.googlesource.com/gwtorm
-
-In Eclipse, import the pom.xml file in the root directory of each of
-these cloned gits via General -> Maven Projects.
-
-Duplicate the existing `gwtui_dbg` launch configuration:
+Duplicate the existing launch configuration:
 
 * Run -> Debug Configurations ...
-* Java Application -> `gwtui_dbg`
+* Java Application -> `buck_gwt_debug`
 * Right click, Duplicate
 
 * Modify the name to be unique.
@@ -103,23 +70,22 @@
 
 * Switch to Common tab.
 * Change Save as to be Local file.
+* Close the Debug Configurations dialog and save the changes when prompted.
 
 
 [[known-problems]]
 Known problems
 --------------
 
-* When running Gerrit under the Eclipse debugger, code that attempts
-to load Prolog code may erroneously raise ClassNotFoundException,
-claiming that classes in the `Gerrit` package can't be found. The
-error can often be resolved by rebuilding Gerrit with `mvn package`
-and restarting the debug session.
-
 * OpenID authentication won't work in hosted mode, so you need to change
 the link:config-gerrit.html#auth.type[auth.type] configuration parameter
 to `DEVELOPMENT_BECOME_ANY_ACCOUNT` to disable OpenID and allow you to
 impersonate whatever account you otherwise would've used.
 
+* Error "Cannot create ReviewDb" occurs if the test site is already running.
+Stop the test site with `gerrit.sh stop` before attempting to run hosted mode
+debugging.
+
 * Gerrit site doesn't appear, only directory listing is shown. Web toolkit
 developer browser plugin is missing. If there is no warning, that browser
 plugin is missing with the suggestion to install it, you can install the
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 12838fe..051f9c2 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -64,7 +64,7 @@
 the plugin is developed. If the plugin is developed for a released
 Gerrit version (no `SNAPSHOT` version) then the URL for the
 `gerrit-api-repository` in the `pom.xml` needs to be changed to
-`https://gerrit-api.commondatastorage.googleapis.com/release/`.
+`https://gerrit-api.storage.googleapis.com/release/`.
 
 [[API]]
 API
@@ -193,7 +193,7 @@
 ====
 
 MyInitStep needs to follow the standard Gerrit InitStep syntax
-and behaviour: writing to the console using the injected ConsoleUI
+and behavior: writing to the console using the injected ConsoleUI
 and accessing / changing configuration settings using Section.Factory.
 
 In addition to the standard Gerrit init injections, plugins receive
@@ -217,26 +217,26 @@
 objects injected at startup.
 
 ====
-public class MyInitStep implements InitStep {
-  private final ConsoleUI ui;
-  private final Section.Factory sections;
-  private final String pluginName;
+  public class MyInitStep implements InitStep {
+    private final ConsoleUI ui;
+    private final Section.Factory sections;
+    private final String pluginName;
 
-  @Inject
-  public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
-    this.ui = ui;
-    this.sections = sections;
-    this.pluginName = pluginName;
+    @Inject
+    public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
+      this.ui = ui;
+      this.sections = sections;
+      this.pluginName = pluginName;
+    }
+
+    @Override
+    public void run() throws Exception {
+      ui.header("\nMy plugin");
+
+      Section mySection = getSection("myplugin", null);
+      mySection.string("Link name", "linkname", "MyLink");
+    }
   }
-
-  @Override
-  public void run() throws Exception {
-    ui.header("\nMy plugin");
-
-    Section mySection = getSection("myplugin", null);
-    mySection.string("Link name", "linkname", "MyLink");
-  }
-}
 ====
 
 [[classpath]]
@@ -384,11 +384,11 @@
 `/Documentation/*` or `/static/*`, the core Gerrit server will
 automatically export these resources over HTTP from the plugin JAR.
 
-Static resources under `static/` directory in the JAR will be
+Static resources under the `static/` directory in the JAR will be
 available as `/plugins/helloworld/static/resource`. This prefix is
 configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
 
-Documentation files under `Documentation/` directory in the JAR
+Documentation files under the `Documentation/` directory in the JAR
 will be available as `/plugins/helloworld/Documentation/resource`. This
 prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
 attribute.
@@ -444,9 +444,15 @@
 of the file, minus the `*.html` extension, as the link text. Any
 hyphens in the file name will be replaced with spaces.
 
+If a discovered file is named `about.md` or `about.html`, its
+content will be inserted in an 'About' section at the top of the
+auto-generated index page.  If both `about.md` and `about.html`
+exist, only the first discovered file will be used.
+
 If a discovered file name beings with `cmd-` it will be clustered
-into a 'Commands' section of the generated index page. All other
-files are clustered under a 'Documentation' section.
+into a 'Commands' section of the generated index page.
+
+All other files are clustered under a 'Documentation' section.
 
 Some optional information from the manifest is extracted and
 displayed as part of the index page, if present in the manifest:
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 67b4aad..ced8648 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -1,12 +1,13 @@
 Gerrit Code Review - Developer Setup
 ====================================
 
-Apache Maven is needed to compile the code, and a SQL database
-to house the review metadata.  H2 is recommended for development
+Facebook Buck is needed to compile the code, and an SQL database to
+house the review metadata.  H2 is recommended for development
 databases, as it requires no external server process.
 
-Get the Source
---------------
+
+Getting the Source
+------------------
 
 Create a new client workspace:
 
@@ -19,46 +20,27 @@
 the core plugins, which are included as git submodules, are also
 cloned.
 
+
+Compiling
+---------
+
+For details on how to build the source code with Buck, refer to:
+link:dev-buck.html#build[Building on the command line with Buck].
+
+
 Configuring Eclipse
 -------------------
 
 To use the Eclipse IDE for development, please see
-link:dev-eclipse.html[Eclipse Setup] for more details on how to
-configure the workspace with the Maven build scripts.
+link:dev-eclipse.html[Eclipse Setup].
 
+For details on how to configure the Eclipse workspace with Buck,
+refer to: link:dev-buck.html#eclipse[Eclipse integration with Buck].
 
-[[build]]
-Building
---------
-
-From the command line:
-
-----
-  mvn clean package
-----
-
-By default the build will run tests and build the documentation.
-
-To build without tests:
-
-----
-  mvn clean package -DskipTests
-----
-
-To build without documentation:
-
-----
-  mvn clean package -Dgerrit.documentation.skip
-----
-
-Output executable WAR will be placed in:
-
-----
-  gerrit-war/target/gerrit-*.war
-----
 
 Mac OS X
-~~~~~~~~
+--------
+
 On Mac OS X ensure "Java For Mac OS X 10.5 Upate 4" (or later) has
 been installed, and that `JAVA_HOME` is set to
 "/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home".
@@ -75,7 +57,7 @@
 testing site for development use:
 
 ----
-  java -jar gerrit-war/target/gerrit-*.war init -d ../test_site
+  java -jar buck-out/gen/gerrit.war init -d ../test_site
 ----
 
 Accept defaults by pressing Enter until 'init' completes, or add
@@ -97,7 +79,8 @@
 Testing
 -------
 
-[[run-acceptance-tests]]
+
+[[tests]]
 Running the Acceptance Tests
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -108,15 +91,10 @@
 started on that site. When the test has finished the Gerrit daemon is
 shutdown.
 
-Since the acceptance tests are too expensive to run every time
-Gerrit is built, they are only executed during the Maven verify phase
-if the Maven profile `acceptance` is enabled.
+For instructions on running the integration tests with Buck,
+please refer to:
+link:dev-buck.html#tests[Running integration tests with Buck].
 
-To execute the acceptance tests run:
-
-----
-  mvn clean verify -Pacceptance
-----
 
 Running the Daemon
 ~~~~~~~~~~~~~~~~~~
@@ -125,7 +103,7 @@
 copying to the test site:
 
 ----
-  java -jar gerrit-war/target/gerrit-*.war daemon -d ../test_site
+  java -jar buck-out/gen/gerrit.war daemon -d ../test_site
 ----
 
 
@@ -136,7 +114,7 @@
 command line.  If the daemon is not currently running:
 
 ----
-  java -jar gerrit-war/target/gerrit-*.war gsql -d ../test_site
+  java -jar buck-out/gen/gerrit.war gsql -d ../test_site
 ----
 
 Or, if it is running and the database is in use, connect over SSH
@@ -213,11 +191,6 @@
 
 * http://code.google.com/webtoolkit/download.html[Download]
 
-Apache Maven:
-
-* http://maven.apache.org/download.html[Download]
-* http://maven.apache.org/run-maven/index.html[Running]
-
 Apache SSHD:
 
 * http://mina.apache.org/sshd/[SSHD]
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
index ffdb6ea..9c98fff 100644
--- a/Documentation/dev-release-deploy-config.txt
+++ b/Documentation/dev-release-deploy-config.txt
@@ -73,7 +73,7 @@
     <repository>
       <id>gerrit-maven-repository</id>
       <name>Gerrit Maven Repository</name>
-      <url>s3://gerrit-maven@commondatastorage.googleapis.com</url>
+      <url>gs://gerrit-maven</url>
       <uniqueVersion>true</uniqueVersion>
     </repository>
   </distributionManagement>
@@ -86,9 +86,9 @@
   <build>
     <extensions>
       <extension>
-        <groupId>net.anzix.aws</groupId>
-        <artifactId>s3-maven-wagon</artifactId>
-        <version>3.2</version>
+        <groupId>com.googlesource.gerrit</groupId>
+        <artifactId>gs-maven-wagon</artifactId>
+        <version>3.3</version>
       </extension>
     </extensions>
   </build>
@@ -107,7 +107,7 @@
     <repository>
       <id>gerrit-plugins-repository</id>
       <name>Gerrit Plugins Repository</name>
-      <url>s3://gerrit-plugins@commondatastorage.googleapis.com</url>
+      <url>gs://gerrit-plugins</url>
       <uniqueVersion>true</uniqueVersion>
     </repository>
   </distributionManagement>
@@ -120,9 +120,9 @@
   <build>
     <extensions>
       <extension>
-        <groupId>net.anzix.aws</groupId>
-        <artifactId>s3-maven-wagon</artifactId>
-        <version>3.2</version>
+        <groupId>com.googlesource.gerrit</groupId>
+        <artifactId>gs-maven-wagon</artifactId>
+        <version>3.3</version>
       </extension>
     </extensions>
   </build>
diff --git a/Documentation/dev-release-subproject.txt b/Documentation/dev-release-subproject.txt
index 5e3770d..956bd29 100644
--- a/Documentation/dev-release-subproject.txt
+++ b/Documentation/dev-release-subproject.txt
@@ -13,9 +13,9 @@
 `gerrit-api-repository` in the `pom.xml` is correct.
 +
 If `Gerrit-ApiVersion` references a released Gerrit version it must be
-`https://gerrit-api.commondatastorage.googleapis.com/release/`, if
+`https://gerrit-api.stoarge.googleapis.com/release/`, if
 `Gerrit-ApiVersion` references a snapshot Gerrit version it must be
-`https://gerrit-api.commondatastorage.googleapis.com/snapshot/`.
+`https://gerrit-api.storage.googleapis.com/snapshot/`.
 
 * Build the latest snapshot and install it into the local Maven
 repository:
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index cd7cd34..c06d16a 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -157,7 +157,7 @@
 link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
 configuration needed for deployment]
 
-* Push the Jars to `commondatastorage.googleapis.com`:
+* Push the Jars to `storage.googleapis.com`:
 +
 ----
   ./tools/deploy_api.sh
diff --git a/Documentation/dev-rest-api.txt b/Documentation/dev-rest-api.txt
new file mode 100644
index 0000000..0175a62
--- /dev/null
+++ b/Documentation/dev-rest-api.txt
@@ -0,0 +1,89 @@
+Gerrit Code Review - REST API Developers' Notes
+===============================================
+
+This document is about developing the REST API.  For details of the
+actual APIs available in Gerrit, please see the
+link:rest-api.html[REST API interface reference].
+
+
+Testing REST API Functionality
+------------------------------
+
+
+Basic Testing
+~~~~~~~~~~~~~
+
+Basic testing of REST API functionality can be done with `curl`:
+
+----
+  curl http://localhost:8080/path/to/api/
+----
+
+By default, `curl` sends `GET` requests.  To test APIs with `PUT`, `POST`,
+or `DELETE`, an additional argument is required:
+
+----
+ curl -X PUT http://localhost:8080/path/to/api/
+ curl -X POST http://localhost:8080/path/to/api/
+ curl -X DELETE http://localhost:8080/path/to/api/
+----
+
+
+Sending Data in the Request
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some REST APIs accept data in the request body of `PUT` and `POST` requests.
+
+Test data can be included from a local file:
+
+----
+  curl -X PUT -d@testdata.txt --header "Content-Type: application/json" http://localhost:8080/path/to/api/
+----
+
+Note that the `-d` option will remove the newlines from the content of the
+local file. If the content should be sent as-is then use the `--data-binary`
+option instead:
+
+----
+  curl -X PUT --data-binary @testdata.txt --header "Content-Type: text/plain" http://localhost:8080/path/to/api/
+----
+
+
+Authentication
+~~~~~~~~~~~~~~
+
+To test APIs that require authentication, the username and password must be specified on
+the command line:
+
+----
+ curl --digest --user username:password http://localhost:8080/a/path/to/api/
+----
+
+This makes it easy to switch users for testing of permissions.
+
+It is also possible to test with a username and password from the `.netrc`
+file (on Windows, `_netrc`):
+
+----
+ curl --digest -n http://localhost:8080/a/path/to/api/
+----
+
+In both cases, the password should be the user's link:user-upload.html#http[HTTP password].
+
+
+Verifying Header Content
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+To verify the headers returned from a REST API call, use `curl` in verbose mode:
+
+----
+  curl -v -n --digest -X DELETE http://localhost:8080/a/path/to/api/
+----
+
+The headers on both the request and the response will be printed.
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
diff --git a/Documentation/error-missing-changeid.txt b/Documentation/error-missing-changeid.txt
index b13f3b4..edbc63b 100644
--- a/Documentation/error-missing-changeid.txt
+++ b/Documentation/error-missing-changeid.txt
@@ -6,11 +6,12 @@
 message if the commit message of the pushed commit does not contain
 a Change-Id in the footer (the last paragraph).
 
-This error may happen for two reasons:
+This error may happen for different reasons:
 
 . missing Change-Id in the commit message
 . Change-Id is contained in the commit message but not in the last
   paragraph
+. Change-Id is the only line in the commit message
 
 You can see the commit messages for existing commits in the history
 by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
@@ -51,6 +52,20 @@
 Change-ID into the last paragraph. How to update the commit message
 is explained link:error-push-fails-due-to-commit-message.html[here].
 
+Change-Id is the only line in the commit message
+------------------------------------------------
+
+Gerrit does not parse the subject of a commit message for the
+Change-Id even if this is the only and last paragraph of the commit
+message.
+
+If the Change-Id is the only line in the commit message you must update
+the commit message and insert a subject as the first line in the commit
+message. The Change-Id must be in the last paragraph of the commit
+message, i.e. separated from the subject by a blank line. How to update
+the commit message is explained
+link:error-push-fails-due-to-commit-message.html[here].
+
 
 GERRIT
 ------
diff --git a/Documentation/gen_licenses.py b/Documentation/gen_licenses.py
new file mode 100755
index 0000000..fb03526
--- /dev/null
+++ b/Documentation/gen_licenses.py
@@ -0,0 +1,131 @@
+#!/usr/bin/python
+# 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.
+#
+# TODO(sop): Be more detailed: version, link to Maven Central
+
+from __future__ import print_function
+
+from collections import defaultdict, deque
+import re
+from shutil import copyfileobj
+from subprocess import Popen, PIPE
+from sys import stdout
+
+MAIN = ['//gerrit-pgm:pgm', '//gerrit-gwtui:ui_module']
+
+def parse_graph():
+  graph = defaultdict(list)
+  p = Popen(
+    ['buck', 'audit', 'classpath', '--dot'] + MAIN,
+    stdout = PIPE)
+  for line in p.stdout:
+    m = re.search(r'"(//.*?)" -> "(//.*?)";', line)
+    if not m:
+      continue
+    target, dep = m.group(1), m.group(2)
+    if not target.endswith('__compile'):
+      graph[target].append(dep)
+  r = p.wait()
+  if r != 0:
+    exit(r)
+  return graph
+
+graph = parse_graph()
+licenses = defaultdict(set)
+
+queue = deque(MAIN)
+while queue:
+  target = queue.popleft()
+  for dep in graph[target]:
+    if not dep.startswith('//lib:LICENSE-'):
+      continue
+    licenses[dep].add(target)
+  queue.extend(graph[target])
+used = sorted(licenses.keys())
+
+print("""\
+Gerrit Code Review - Licenses
+=============================
+
+Gerrit open source software is licensed under the <<Apache2_0,Apache
+License 2.0>>.  Executable distributions also include other software
+components that are provided under additional licenses.
+
+[[cryptography]]
+Cryptography Notice
+-------------------
+
+This distribution includes cryptographic software.  The country
+in which you currently reside may have restrictions on the import,
+possession, use, and/or re-export to another country, of encryption
+software.  BEFORE using any encryption software, please check
+your country's laws, regulations and policies concerning the
+import, possession, or use, and re-export of encryption software,
+to see if this is permitted.  See the
+link:http://www.wassenaar.org/[Wassenaar Arrangement]
+for more information.
+
+The U.S. Government Department of Commerce, Bureau of Industry
+and Security (BIS), has classified this software as Export
+Commodity Control Number (ECCN) 5D002.C.1, which includes
+information security software using or performing cryptographic
+functions with asymmetric algorithms.  The form and manner of
+this distribution makes it eligible for export under the License
+Exception ENC Technology Software Unrestricted (TSU) exception
+(see the BIS Export Administration Regulations, Section 740.13)
+for both object code and source code.
+
+Gerrit includes an SSH daemon (Apache SSHD), to support authenticated
+uploads of changes directly from `git push` command line clients.
+
+Gerrit includes an SSH client (JSch), to support authenticated
+replication of changes to remote systems, such as for automatic
+updates of mirror servers, or realtime backups.
+
+For either feature to function, Gerrit requires the
+link:http://java.sun.com/javase/technologies/security/[Java Cryptography extensions]
+and/or the
+link:http://www.bouncycastle.org/java.html[Bouncy Castle Crypto API]
+to be installed by the end-user.
+
+Licenses
+--------
+""")
+
+for n in used:
+  libs = sorted(licenses[n])
+  name = n[len('//lib:LICENSE-'):]
+  print()
+  print('[[%s]]' % name.replace('.', '_'))
+  print(name)
+  print('~' * len(name))
+  print()
+  for d in libs:
+    if d.startswith('//lib:') or d.startswith('//lib/'):
+      p = d[len('//lib:'):]
+    else:
+      p = d[d.index(':')+1:].lower()
+    print('* ' + p)
+  print()
+  print('----')
+  with open(n[2:].replace(':', '/')) as fd:
+    copyfileobj(fd, stdout)
+  print('----')
+
+print("""
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+""")
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 88b50fa..e028d37 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -1,72 +1,82 @@
 Gerrit Code Review for Git
 ==========================
 
-Getting Started
----------------
+Index
+-----
 
-* link:intro-quick.html[A Quick Introduction To Gerrit]
+. General info
+.. link:licenses.html[Licenses and Notices]
+. Installing
+.. link:intro-quick.html[A Quick Introduction To Gerrit]
+.. link:install.html[Installation Guide]
+. Tutorial
+.. Get started
+... External link: link:http://source.android.com/submit-patches/workflow[Default Android Workflow]
+.. Web
+... Registering a new Gerrit account
+... link:user-search.html[Searching Changes]
+... link:user-notify.html[Subscribing to Email Notifications]
+.. Ssh
+... ssh connection details
+... link:cmd-index.html[Command Line Tools]
+.. Git
+... git connection details
+... Commands, scenarios
+.... link:user-upload.html[Uploading Changes]
+.... link:error-messages.html[Error Messages]
+... Changes
+.... link:user-changeid.html[Change-Id Lines]
+.... link:user-signedoffby.html[Signed-off-by Lines]
+... Patch sets
+. Project management
+.. link:project-setup.html[Project Setup]
+.. link:access-control.html[Access Controls]
+... link:config-labels.html[Review Labels]
+.. Multi-project management
+... Submodules
+... Repo
+.. Prolog rules
+... link:prolog-cookbook.html[Prolog Cookbook]
+... link:prolog-change-facts.html[Prolog Facts for Gerrit Changes]
+.. link:user-submodules.html[Subscribing to Git Submodules]
+.. Project sunset
+. Customization and integration
+.. link:user-dashboards.html[Dashboards]
+.. link:rest-api.html[REST API]
+.. link:config-gitweb.html[Gitweb Integration]
+.. link:config-themes.html[Themes]
+.. link:config-sso.html[Single Sign-On Systems]
+.. link:config-hooks.html[Hooks]
+.. link:config-mail.html[Mail Templates]
+.. link:config-cla.html[Contributor Agreements]
+. Server administration
+.. link:config-gerrit.html[System Settings]
+.. Backup
+.. Performance tuning
+... link:cmd-index.html[Command Line Tools]
+... Reading show-caches efficiently
+... How to read stats from the JVM
+.. High availability
+.. Replication
+.. Plugins
+.. link:dev-design.html[System Design]
+.. link:config-contact.html[User Contact Information]
+.. link:config-reverseproxy.html[Reverse Proxy]
+.. link:pgm-index.html[Server Side Administrative Tools]
+. Developer
+.. link:dev-readme.html[Developer Setup]
+.. link:dev-eclipse.html[Eclipse Setup]
+.. link:dev-contributing.html[Contributing to Gerrit]
+.. Documentation formatting guide for contributions
+.. link:dev-design.html[System Design]
+.. link:i18n-readme.html[i18n Support]
+.. Plugin development
+... link:dev-plugins.html[Developing Plugins]
+... link:config-validation.html[Commit Validation]
+. Maintainer
+.. link:dev-release.html[Developer Release]
+.. link:dev-release-subproject.html[Developer Subproject Release]
 
-End User Guide
---------------
-
-* External link: link:http://source.android.com/submit-patches/workflow[Default Android Workflow]
-* link:user-search.html[Searching Changes]
-* link:cmd-index.html[Command Line Tools]
-* link:user-upload.html[Uploading Changes]
-* link:user-changeid.html[Change-Id Lines]
-* link:user-signedoffby.html[Signed-off-by Lines]
-* link:error-messages.html[Error Messages]
-* link:user-notify.html[Subscribing to Email Notifications]
-
-Project Owner and Power User Guide
-----------------------------------
-
-* link:access-control.html[Access Controls]
-* link:rest-api.html[REST API]
-* link:user-dashboards.html[Dashboards]
-* link:user-submodules.html[Subscribing to Git Submodules]
-* link:prolog-cookbook.html[Prolog Cookbook]
-* link:prolog-change-facts.html[Prolog Facts for Gerrit Changes]
-* link:config-labels.html[Review Labels]
-
-Admin User Guide
-----------------
-
-* link:pgm-index.html[Server Side Administrative Tools]
-
-Installation
-~~~~~~~~~~~~
-
-* link:licenses.html[Licenses and Notices]
-* link:install.html[Installation Guide]
-* link:install-quick.html[Quick Installation in 10 Minutes]
-* link:project-setup.html[Project Setup]
-
-Configuration
-~~~~~~~~~~~~~
-
-* link:config-gerrit.html[System Settings]
-* link:config-contact.html[User Contact Information]
-* link:config-gitweb.html[Gitweb Integration]
-* link:config-themes.html[Themes]
-* link:config-sso.html[Single Sign-On Systems]
-* link:config-reverseproxy.html[Reverse Proxy]
-* link:config-hooks.html[Hooks]
-* link:config-mail.html[Mail Templates]
-* link:config-cla.html[Contributor Agreements]
-
-Gerrit Developer Documentation
-------------------------------
-
-* link:dev-readme.html[Developer Setup]
-* link:dev-eclipse.html[Eclipse Setup]
-* link:dev-contributing.html[Contributing to Gerrit]
-* link:dev-plugins.html[Developing Plugins]
-* link:config-validation.html[Commit Validation]
-* link:dev-design.html[System Design]
-* link:i18n-readme.html[i18n Support]
-* link:dev-release.html[Developer Release]
-* link:dev-release-subproject.html[Developer Subproject Release]
 
 Resources
 ---------
diff --git a/Documentation/install-j2ee.txt b/Documentation/install-j2ee.txt
index 4927041..f6853c8 100644
--- a/Documentation/install-j2ee.txt
+++ b/Documentation/install-j2ee.txt
@@ -21,7 +21,7 @@
   link:install.html#init[site initialization] tasks described
   in the standard installation documentation.
 
-* Stop the embedded deamon that was automatically started by 'init':
+* Stop the embedded daemon that was automatically started by 'init':
 +
 ----
   review_site/bin/gerrit.sh stop
diff --git a/Documentation/install.txt b/Documentation/install.txt
index a18a506..8137f53 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -9,7 +9,7 @@
 
 * JDK, minimum version 1.6 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
 
-You'll also need a SQL database to house the review metadata. You have the
+You'll also need an SQL database to house the review metadata. You have the
 choice of either using the embedded H2 or to host your own MySQL or PostgreSQL.
 
 
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 3893e3f..2836e5b 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -115,11 +115,13 @@
 createdOn:: Time in seconds since the UNIX epoch when this patchset
 was created.
 
+isDraft:: Whether or not the patch set is a draft patch set.
+
 approvals:: The <<approval,approval attribute>> granted.
 
 comments:: All comments for this patchset in <<patchsetcomment,patchsetComment attributes>>.
 
-files:: All changed files in this patchset in <<patch,patch attributes>>.
+files:: All changed files in this patchset in <<file,file attributes>>.
 
 sizeInsertions:: Size information of insertions of this patchset.
 
@@ -235,9 +237,9 @@
 
 message:: The comment text.
 
-[[patch]]
-patch
------
+[[file]]
+file
+----
 Information about a patch on a file.
 
 file:: The name of the file.  If the file is renamed, the new name.
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
deleted file mode 100644
index fbdd9cd..0000000
--- a/Documentation/licenses.txt
+++ /dev/null
@@ -1,1189 +0,0 @@
-Gerrit Code Review - Licenses
-=============================
-
-Gerrit itself is licensed under the <<apache2,Apache License 2.0>>.
-Gerrit's executable distributions also include many other software
-components that are covered by additional licenses.
-
-Included Components
--------------------
-
-[options="header"]
-|======================================================================
-|Included Package           | License
-|Gerrit Code Review         | <<apache2,Apache License 2.0>>
-|gwtjsonrpc                 | <<apache2,Apache License 2.0>>
-|gwtorm                     | <<apache2,Apache License 2.0>>
-|Google Gson                | <<apache2,Apache License 2.0>>
-|Google Web Toolkit         | <<apache2,Apache License 2.0>>
-|Guice                      | <<apache2,Apache License 2.0>>
-|Guava Libraries            | <<apache2,Apache License 2.0>>
-|Apache Commons Codec       | <<apache2,Apache License 2.0>>
-|Apache Commons DBCP        | <<apache2,Apache License 2.0>>
-|Apache Commons Http Client | <<apache2,Apache License 2.0>>
-|Apache Commons Lang        | <<apache2,Apache License 2.0>>
-|Apache Commons Logging     | <<apache2,Apache License 2.0>>
-|Apache Commons Net         | <<apache2,Apache License 2.0>>
-|Apache Commons Pool        | <<apache2,Apache License 2.0>>
-|Apache Log4J               | <<apache2,Apache License 2.0>>
-|Apache MINA                | <<apache2,Apache License 2.0>>
-|Apache Tomcat Servlet API  | <<apache2,Apache License 2.0>>
-|Apache SSHD                | <<apache2,Apache License 2.0>>, see also <<sshd,NOTICE>>
-|Apache Velocity            | <<apache2,Apache License 2.0>>
-|Apache Xerces              | <<apache2,Apache License 2.0>>
-|OpenID4Java                | <<apache2,Apache License 2.0>>
-|Neko HTML                  | <<apache2,Apache License 2.0>>
-|mime-util                  | <<apache2,Apache License 2.0>>
-|Jetty                      | <<apache2,Apache License 2.0>>, or link:http://www.eclipse.org/legal/epl-v10.html[EPL]
-|Prolog Cafe                | <<prolog_cafe,EPL or GPL>>
-|Google Code Prettify       | <<apache2,Apache License 2.0>>
-|JavaEWAH                   | <<apache2,Apache License 2.0>>
-|JGit                       | <<jgit,New-Style BSD>>
-|JSch                       | <<sshd,New-Style BSD>>
-|PostgreSQL JDBC Driver     | <<postgresql,New-Style BSD>>
-|H2 Database                | <<h2,EPL or modified MPL>>
-|ObjectWeb ASM              | <<asm,New-Style BSD>>
-|ANTLR                      | <<antlr,New-Style BSD>>
-|args4j                     | <<args4j,MIT License>>
-|SLF4J                      | <<slf4j,MIT License>>
-|Clippy                     | <<clippy,MIT License>>
-|juniversalchardet          | <<mpl1_1,MPL 1.1>>
-|AOP Alliance               | Public Domain
-|JSR 305                    | <<jsr305,New-Style BSD>>
-|dk.brics.automaton         | <<automaton,New-Style BSD>>
-|Java Concurrency in Practice Annotations | <<jcip,Create Commons Attribution License>>
-|pegdown                    | <<apache2,Apache License 2.0>>
-|======================================================================
-
-Cryptography Notice
--------------------
-
-This distribution includes cryptographic software.  The country
-in which you currently reside may have restrictions on the import,
-possession, use, and/or re-export to another country, of encryption
-software.  BEFORE using any encryption software, please check
-your country's laws, regulations and policies concerning the
-import, possession, or use, and re-export of encryption software,
-to see if this is permitted.  See the
-link:http://www.wassenaar.org/[Wassenaar Arrangement]
-for more information.
-
-The U.S. Government Department of Commerce, Bureau of Industry
-and Security (BIS), has classified this software as Export
-Commodity Control Number (ECCN) 5D002.C.1, which includes
-information security software using or performing cryptographic
-functions with asymmetric algorithms.  The form and manner of
-this distribution makes it eligible for export under the License
-Exception ENC Technology Software Unrestricted (TSU) exception
-(see the BIS Export Administration Regulations, Section 740.13)
-for both object code and source code.
-
-Gerrit includes an SSH daemon (Apache SSHD), to support authenticated
-uploads of changes directly from `git push` command line clients.
-
-Gerrit includes an SSH client (JSch), to support authenticated
-replication of changes to remote systems, such as for automatic
-updates of mirror servers, or realtime backups.
-
-For either feature to function, Gerrit requires the
-link:http://java.sun.com/javase/technologies/security/[Java Cryptography extensions]
-and/or the
-link:http://www.bouncycastle.org/java.html[Bouncy Castle Crypto API]
-to be installed by the end-user.
-
-Licenses
---------
-
-[[apache2]]
-Apache License 2.0
-~~~~~~~~~~~~~~~~~~
-
-----
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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.
-----
-
-[[sshd]]
-Apache SSHD - Notice
-~~~~~~~~~~~~~~~~~~~~
-
-* link:http://svn.apache.org/viewvc/mina/sshd/trunk/NOTICE.txt?view=markup[Original]
-
-----
-   =========================================================================
-   ==  NOTICE file for use with the Apache License, Version 2.0,          ==
-   ==  in this case for the SSHD distribution.                            ==
-   =========================================================================
-
-   This product contains software developped by JCraft,Inc. and subject to
-   the following license:
-
-Copyright (c) 2002,2003,2004,2005,2006,2007,2008 Atsuhiko Yamanaka, JCraft,Inc.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-  1. Redistributions of source code must retain the above copyright notice,
-     this list of conditions and the following disclaimer.
-
-  2. Redistributions in binary form must reproduce the above copyright
-     notice, this list of conditions and the following disclaimer in
-     the documentation and/or other materials provided with the distribution.
-
-  3. The names of the authors may not be used to endorse or promote products
-     derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
-INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
-INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
-EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
- --------------------------------------------------------------------------------
-
-Copyright (c) 2000 - 2006 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in the
-Software without restriction, including without limitation the rights to use, copy,
-modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
-and to permit persons to whom the Software is furnished to do so, subject to the
-following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
-PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----
-
-[[postgresql]]
-PostgreSQL JDBC Driver - New Style BSD
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* link:http://jdbc.postgresql.org/license.html[Original]
-
-----
-Copyright (c) 1997-2008, PostgreSQL Global Development Group
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice,
-   this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright notice,
-   this list of conditions and the following disclaimer in the documentation
-   and/or other materials provided with the distribution.
-3. Neither the name of the PostgreSQL Global Development Group nor the names
-   of its contributors may be used to endorse or promote products derived
-   from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-----
-
-[[prolog_cafe]]
-Prolog Cafe - EPL or GPL
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Originally developed by Mutsunori BANBARA and Naoyuki TAMURA at the
-Kobe University, JAPAN. Gerrit Code Review uses a fork derived from
-the 1.2.5 release, and offers the corresponding source code at
-link:https://gerrit.googlesource.com/prolog-cafe[].
-
-Prolog Cafe is dual licensed and available under either the
-link:http://opensource.org/licenses/eclipse-1.0.php[Eclipse Public License],
-or the
-link:http://www.gnu.org/licenses/gpl-2.0.html[GPL version 2.0 (or later)].
-
-In the context of Gerrit Code Review, Prolog Cafe is consumed
-under the EPL.
-
-[[h2]]
-H2 Database - EPL or modified MPL
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* link:http://www.h2database.com/html/license.html[Complete Terms]
-
-H2 is dual licensed and available under a modified version of the
-MPL 1.1 (Mozilla Public License) or under the (unmodified) EPL 1.0
-(link:http://opensource.org/licenses/eclipse-1.0.php[Eclipse Public License]).
-
-[[asm]]
-ObjectWeb ASM - New Style BSD
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* link:http://svn.forge.objectweb.org/cgi-bin/viewcvs.cgi/asm/trunk/asm/LICENSE.txt[Original]
-
-----
- ASM: a very small and fast Java bytecode manipulation framework
- Copyright (c) 2000-2005 INRIA, France Telecom
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
-
- 1. Redistributions of source code must retain the above copyright
-    notice, this list of conditions and the following disclaimer.
- 2. Redistributions in binary form must reproduce the above copyright
-    notice, this list of conditions and the following disclaimer in the
-    documentation and/or other materials provided with the distribution.
- 3. Neither the name of the copyright holders nor the names of its
-    contributors may be used to endorse or promote products derived from
-    this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- THE POSSIBILITY OF SUCH DAMAGE.
-----
-
-[[antlr]]
-ANTLR - New Style BSD
-~~~~~~~~~~~~~~~~~~~~~
-
-* link:http://www.antlr.org/license.html[Original]
-
-----
-Copyright (c) 2003-2008, Terence Parr
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above
-      copyright notice, this list of conditions and the following
-      disclaimer in the documentation and/or other materials provided
-      with the distribution.
-    * Neither the name of the author nor the names of its
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-----
-
-[[jgit]]
-JGit - New Style BSD
-~~~~~~~~~~~~~~~~~~~~
-
-* link:http://repo.or.cz/w/egit.git?a=blob;f=org.spearce.jgit/LICENSE;hb=HEAD[Original]
-
-----
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Git Development Community nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----
-
-[[jsr305]]
-JSR 305 Reference Implementation - New Style BSD
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* link:http://code.google.com/p/jsr-305/source/browse/trunk/ri/LICENSE[Original 1]
-* link:http://code.google.com/p/findbugs/source/browse/trunk/findbugs/LICENSE-jsr305.txt[Original 2]
-
-----
-Copyright (c) 2007-2009, JSR305 expert group
-All rights reserved.
-
-http://www.opensource.org/licenses/bsd-license.php
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimer in the documentation
-      and/or other materials provided with the distribution.
-    * Neither the name of the JSR305 expert group nor the names of its
-      contributors may be used to endorse or promote products derived from
-      this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-----
-
-[[automaton]]
-dk.brics.automaton - New Style BSD
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* link:http://www.brics.dk/automaton/index.html
-
-----
-Copyright (c) 2007-2009, dk.brics.automaton
-All rights reserved.
-
-http://www.opensource.org/licenses/bsd-license.php
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimer in the documentation
-      and/or other materials provided with the distribution.
-    * Neither the name of the JSR305 expert group nor the names of its
-      contributors may be used to endorse or promote products derived from
-      this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-----
-
-[[args4j]]
-args4j - MIT License
-~~~~~~~~~~~~~~~~~~~~
-
-* link:https://args4j.dev.java.net/[Home]
-* link:http://www.opensource.org/licenses/mit-license.php[Canonical MIT License]
-
-[[slf4j]]
-SLF4J - MIT License
-~~~~~~~~~~~~~~~~~~~
-
-* link:http://www.slf4j.org/license.html[Original]
-
-----
- Copyright (c) 2004-2008 QOS.ch
- All rights reserved.
-
- Permission is hereby granted, free  of charge, to any person obtaining
- a  copy  of this  software  and  associated  documentation files  (the
- "Software"), to  deal in  the Software without  restriction, including
- without limitation  the rights to  use, copy, modify,  merge, publish,
- distribute,  sublicense, and/or sell  copies of  the Software,  and to
- permit persons to whom the Software  is furnished to do so, subject to
- the following conditions:
-
- The  above  copyright  notice  and  this permission  notice  shall  be
- included in all copies or substantial portions of the Software.
-
- THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
- EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
- MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----
-
-[[clippy]]
-Clippy - MIT License
-~~~~~~~~~~~~~~~~~~~~
-
-* link:http://github.com/mojombo/clippy/tree/master[Site]
-
-----
-(The MIT License)
-
-Copyright (c) 2008 Tom Preston-Werner
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-'Software'), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
- 
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
- 
-THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----
-
-[[jcip]]
-Java Concurrency in Practice Annotations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* link:http://jcip.net/[book website]
-* link:http://jcip.net/jcip-annotations-src.jar[sources]
-* link:http://creativecommons.org/licenses/by/2.5/[license]
-
-----
-Copyright (c) 2005 Brian Goetz and Tim Peierls
-Released under the Creative Commons Attribution License
-  (http://creativecommons.org/licenses/by/2.5)
-Official home: http://www.jcip.net
-
-Any republication or derived work distributed in source code form
-must include this copyright and license notice.
-----
-
-[[mpl1_1]]
-Mozilla Public License 1.1
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* link:http://www.mozilla.org/MPL/MPL-1.1.html[Mozilla Public License (Original)]
-* link:http://www.mozilla.org/MPL/MPL-1.1-annotated.html[Mozilla Public License (Annotated)]
-
-----
-                          MOZILLA PUBLIC LICENSE
-                                Version 1.1
-
-                              ---------------
-
-1. Definitions.
-
-     1.0.1. "Commercial Use" means distribution or otherwise making the
-     Covered Code available to a third party.
-
-     1.1. "Contributor" means each entity that creates or contributes to
-     the creation of Modifications.
-
-     1.2. "Contributor Version" means the combination of the Original
-     Code, prior Modifications used by a Contributor, and the Modifications
-     made by that particular Contributor.
-
-     1.3. "Covered Code" means the Original Code or Modifications or the
-     combination of the Original Code and Modifications, in each case
-     including portions thereof.
-
-     1.4. "Electronic Distribution Mechanism" means a mechanism generally
-     accepted in the software development community for the electronic
-     transfer of data.
-
-     1.5. "Executable" means Covered Code in any form other than Source
-     Code.
-
-     1.6. "Initial Developer" means the individual or entity identified
-     as the Initial Developer in the Source Code notice required by Exhibit
-     A.
-
-     1.7. "Larger Work" means a work which combines Covered Code or
-     portions thereof with code not governed by the terms of this License.
-
-     1.8. "License" means this document.
-
-     1.8.1. "Licensable" means having the right to grant, to the maximum
-     extent possible, whether at the time of the initial grant or
-     subsequently acquired, any and all of the rights conveyed herein.
-
-     1.9. "Modifications" means any addition to or deletion from the
-     substance or structure of either the Original Code or any previous
-     Modifications. When Covered Code is released as a series of files, a
-     Modification is:
-          A. Any addition to or deletion from the contents of a file
-          containing Original Code or previous Modifications.
-
-          B. Any new file that contains any part of the Original Code or
-          previous Modifications.
-
-     1.10. "Original Code" means Source Code of computer software code
-     which is described in the Source Code notice required by Exhibit A as
-     Original Code, and which, at the time of its release under this
-     License is not already Covered Code governed by this License.
-
-     1.10.1. "Patent Claims" means any patent claim(s), now owned or
-     hereafter acquired, including without limitation,  method, process,
-     and apparatus claims, in any patent Licensable by grantor.
-
-     1.11. "Source Code" means the preferred form of the Covered Code for
-     making modifications to it, including all modules it contains, plus
-     any associated interface definition files, scripts used to control
-     compilation and installation of an Executable, or source code
-     differential comparisons against either the Original Code or another
-     well known, available Covered Code of the Contributor's choice. The
-     Source Code can be in a compressed or archival form, provided the
-     appropriate decompression or de-archiving software is widely available
-     for no charge.
-
-     1.12. "You" (or "Your")  means an individual or a legal entity
-     exercising rights under, and complying with all of the terms of, this
-     License or a future version of this License issued under Section 6.1.
-     For legal entities, "You" includes any entity which controls, is
-     controlled by, or is under common control with You. For purposes of
-     this definition, "control" means (a) the power, direct or indirect,
-     to cause the direction or management of such entity, whether by
-     contract or otherwise, or (b) ownership of more than fifty percent
-     (50%) of the outstanding shares or beneficial ownership of such
-     entity.
-
-2. Source Code License.
-
-     2.1. The Initial Developer Grant.
-     The Initial Developer hereby grants You a world-wide, royalty-free,
-     non-exclusive license, subject to third party intellectual property
-     claims:
-          (a)  under intellectual property rights (other than patent or
-          trademark) Licensable by Initial Developer to use, reproduce,
-          modify, display, perform, sublicense and distribute the Original
-          Code (or portions thereof) with or without Modifications, and/or
-          as part of a Larger Work; and
-
-          (b) under Patents Claims infringed by the making, using or
-          selling of Original Code, to make, have made, use, practice,
-          sell, and offer for sale, and/or otherwise dispose of the
-          Original Code (or portions thereof).
-
-          (c) the licenses granted in this Section 2.1(a) and (b) are
-          effective on the date Initial Developer first distributes
-          Original Code under the terms of this License.
-
-          (d) Notwithstanding Section 2.1(b) above, no patent license is
-          granted: 1) for code that You delete from the Original Code; 2)
-          separate from the Original Code;  or 3) for infringements caused
-          by: i) the modification of the Original Code or ii) the
-          combination of the Original Code with other software or devices.
-
-     2.2. Contributor Grant.
-     Subject to third party intellectual property claims, each Contributor
-     hereby grants You a world-wide, royalty-free, non-exclusive license
-
-          (a)  under intellectual property rights (other than patent or
-          trademark) Licensable by Contributor, to use, reproduce, modify,
-          display, perform, sublicense and distribute the Modifications
-          created by such Contributor (or portions thereof) either on an
-          unmodified basis, with other Modifications, as Covered Code
-          and/or as part of a Larger Work; and
-
-          (b) under Patent Claims infringed by the making, using, or
-          selling of  Modifications made by that Contributor either alone
-          and/or in combination with its Contributor Version (or portions
-          of such combination), to make, use, sell, offer for sale, have
-          made, and/or otherwise dispose of: 1) Modifications made by that
-          Contributor (or portions thereof); and 2) the combination of
-          Modifications made by that Contributor with its Contributor
-          Version (or portions of such combination).
-
-          (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
-          effective on the date Contributor first makes Commercial Use of
-          the Covered Code.
-
-          (d)    Notwithstanding Section 2.2(b) above, no patent license is
-          granted: 1) for any code that Contributor has deleted from the
-          Contributor Version; 2)  separate from the Contributor Version;
-          3)  for infringements caused by: i) third party modifications of
-          Contributor Version or ii)  the combination of Modifications made
-          by that Contributor with other software  (except as part of the
-          Contributor Version) or other devices; or 4) under Patent Claims
-          infringed by Covered Code in the absence of Modifications made by
-          that Contributor.
-
-3. Distribution Obligations.
-
-     3.1. Application of License.
-     The Modifications which You create or to which You contribute are
-     governed by the terms of this License, including without limitation
-     Section 2.2. The Source Code version of Covered Code may be
-     distributed only under the terms of this License or a future version
-     of this License released under Section 6.1, and You must include a
-     copy of this License with every copy of the Source Code You
-     distribute. You may not offer or impose any terms on any Source Code
-     version that alters or restricts the applicable version of this
-     License or the recipients' rights hereunder. However, You may include
-     an additional document offering the additional rights described in
-     Section 3.5.
-
-     3.2. Availability of Source Code.
-     Any Modification which You create or to which You contribute must be
-     made available in Source Code form under the terms of this License
-     either on the same media as an Executable version or via an accepted
-     Electronic Distribution Mechanism to anyone to whom you made an
-     Executable version available; and if made available via Electronic
-     Distribution Mechanism, must remain available for at least twelve (12)
-     months after the date it initially became available, or at least six
-     (6) months after a subsequent version of that particular Modification
-     has been made available to such recipients. You are responsible for
-     ensuring that the Source Code version remains available even if the
-     Electronic Distribution Mechanism is maintained by a third party.
-
-     3.3. Description of Modifications.
-     You must cause all Covered Code to which You contribute to contain a
-     file documenting the changes You made to create that Covered Code and
-     the date of any change. You must include a prominent statement that
-     the Modification is derived, directly or indirectly, from Original
-     Code provided by the Initial Developer and including the name of the
-     Initial Developer in (a) the Source Code, and (b) in any notice in an
-     Executable version or related documentation in which You describe the
-     origin or ownership of the Covered Code.
-
-     3.4. Intellectual Property Matters
-          (a) Third Party Claims.
-          If Contributor has knowledge that a license under a third party's
-          intellectual property rights is required to exercise the rights
-          granted by such Contributor under Sections 2.1 or 2.2,
-          Contributor must include a text file with the Source Code
-          distribution titled "LEGAL" which describes the claim and the
-          party making the claim in sufficient detail that a recipient will
-          know whom to contact. If Contributor obtains such knowledge after
-          the Modification is made available as described in Section 3.2,
-          Contributor shall promptly modify the LEGAL file in all copies
-          Contributor makes available thereafter and shall take other steps
-          (such as notifying appropriate mailing lists or newsgroups)
-          reasonably calculated to inform those who received the Covered
-          Code that new knowledge has been obtained.
-
-          (b) Contributor APIs.
-          If Contributor's Modifications include an application programming
-          interface and Contributor has knowledge of patent licenses which
-          are reasonably necessary to implement that API, Contributor must
-          also include this information in the LEGAL file.
-
-               (c)    Representations.
-          Contributor represents that, except as disclosed pursuant to
-          Section 3.4(a) above, Contributor believes that Contributor's
-          Modifications are Contributor's original creation(s) and/or
-          Contributor has sufficient rights to grant the rights conveyed by
-          this License.
-
-     3.5. Required Notices.
-     You must duplicate the notice in Exhibit A in each file of the Source
-     Code.  If it is not possible to put such notice in a particular Source
-     Code file due to its structure, then You must include such notice in a
-     location (such as a relevant directory) where a user would be likely
-     to look for such a notice.  If You created one or more Modification(s)
-     You may add your name as a Contributor to the notice described in
-     Exhibit A.  You must also duplicate this License in any documentation
-     for the Source Code where You describe recipients' rights or ownership
-     rights relating to Covered Code.  You may choose to offer, and to
-     charge a fee for, warranty, support, indemnity or liability
-     obligations to one or more recipients of Covered Code. However, You
-     may do so only on Your own behalf, and not on behalf of the Initial
-     Developer or any Contributor. You must make it absolutely clear than
-     any such warranty, support, indemnity or liability obligation is
-     offered by You alone, and You hereby agree to indemnify the Initial
-     Developer and every Contributor for any liability incurred by the
-     Initial Developer or such Contributor as a result of warranty,
-     support, indemnity or liability terms You offer.
-
-     3.6. Distribution of Executable Versions.
-     You may distribute Covered Code in Executable form only if the
-     requirements of Section 3.1-3.5 have been met for that Covered Code,
-     and if You include a notice stating that the Source Code version of
-     the Covered Code is available under the terms of this License,
-     including a description of how and where You have fulfilled the
-     obligations of Section 3.2. The notice must be conspicuously included
-     in any notice in an Executable version, related documentation or
-     collateral in which You describe recipients' rights relating to the
-     Covered Code. You may distribute the Executable version of Covered
-     Code or ownership rights under a license of Your choice, which may
-     contain terms different from this License, provided that You are in
-     compliance with the terms of this License and that the license for the
-     Executable version does not attempt to limit or alter the recipient's
-     rights in the Source Code version from the rights set forth in this
-     License. If You distribute the Executable version under a different
-     license You must make it absolutely clear that any terms which differ
-     from this License are offered by You alone, not by the Initial
-     Developer or any Contributor. You hereby agree to indemnify the
-     Initial Developer and every Contributor for any liability incurred by
-     the Initial Developer or such Contributor as a result of any such
-     terms You offer.
-
-     3.7. Larger Works.
-     You may create a Larger Work by combining Covered Code with other code
-     not governed by the terms of this License and distribute the Larger
-     Work as a single product. In such a case, You must make sure the
-     requirements of this License are fulfilled for the Covered Code.
-
-4. Inability to Comply Due to Statute or Regulation.
-
-     If it is impossible for You to comply with any of the terms of this
-     License with respect to some or all of the Covered Code due to
-     statute, judicial order, or regulation then You must: (a) comply with
-     the terms of this License to the maximum extent possible; and (b)
-     describe the limitations and the code they affect. Such description
-     must be included in the LEGAL file described in Section 3.4 and must
-     be included with all distributions of the Source Code. Except to the
-     extent prohibited by statute or regulation, such description must be
-     sufficiently detailed for a recipient of ordinary skill to be able to
-     understand it.
-
-5. Application of this License.
-
-     This License applies to code to which the Initial Developer has
-     attached the notice in Exhibit A and to related Covered Code.
-
-6. Versions of the License.
-
-     6.1. New Versions.
-     Netscape Communications Corporation ("Netscape") may publish revised
-     and/or new versions of the License from time to time. Each version
-     will be given a distinguishing version number.
-
-     6.2. Effect of New Versions.
-     Once Covered Code has been published under a particular version of the
-     License, You may always continue to use it under the terms of that
-     version. You may also choose to use such Covered Code under the terms
-     of any subsequent version of the License published by Netscape. No one
-     other than Netscape has the right to modify the terms applicable to
-     Covered Code created under this License.
-
-     6.3. Derivative Works.
-     If You create or use a modified version of this License (which you may
-     only do in order to apply it to code which is not already Covered Code
-     governed by this License), You must (a) rename Your license so that
-     the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
-     "MPL", "NPL" or any confusingly similar phrase do not appear in your
-     license (except to note that your license differs from this License)
-     and (b) otherwise make it clear that Your version of the license
-     contains terms which differ from the Mozilla Public License and
-     Netscape Public License. (Filling in the name of the Initial
-     Developer, Original Code or Contributor in the notice described in
-     Exhibit A shall not of themselves be deemed to be modifications of
-     this License.)
-
-7. DISCLAIMER OF WARRANTY.
-
-     COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
-     WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
-     WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
-     DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
-     THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
-     IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
-     YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
-     COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
-     OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
-     ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
-
-8. TERMINATION.
-
-     8.1.  This License and the rights granted hereunder will terminate
-     automatically if You fail to comply with terms herein and fail to cure
-     such breach within 30 days of becoming aware of the breach. All
-     sublicenses to the Covered Code which are properly granted shall
-     survive any termination of this License. Provisions which, by their
-     nature, must remain in effect beyond the termination of this License
-     shall survive.
-
-     8.2.  If You initiate litigation by asserting a patent infringement
-     claim (excluding declatory judgment actions) against Initial Developer
-     or a Contributor (the Initial Developer or Contributor against whom
-     You file such action is referred to as "Participant")  alleging that:
-
-     (a)  such Participant's Contributor Version directly or indirectly
-     infringes any patent, then any and all rights granted by such
-     Participant to You under Sections 2.1 and/or 2.2 of this License
-     shall, upon 60 days notice from Participant terminate prospectively,
-     unless if within 60 days after receipt of notice You either: (i)
-     agree in writing to pay Participant a mutually agreeable reasonable
-     royalty for Your past and future use of Modifications made by such
-     Participant, or (ii) withdraw Your litigation claim with respect to
-     the Contributor Version against such Participant.  If within 60 days
-     of notice, a reasonable royalty and payment arrangement are not
-     mutually agreed upon in writing by the parties or the litigation claim
-     is not withdrawn, the rights granted by Participant to You under
-     Sections 2.1 and/or 2.2 automatically terminate at the expiration of
-     the 60 day notice period specified above.
-
-     (b)  any software, hardware, or device, other than such Participant's
-     Contributor Version, directly or indirectly infringes any patent, then
-     any rights granted to You by such Participant under Sections 2.1(b)
-     and 2.2(b) are revoked effective as of the date You first made, used,
-     sold, distributed, or had made, Modifications made by that
-     Participant.
-
-     8.3.  If You assert a patent infringement claim against Participant
-     alleging that such Participant's Contributor Version directly or
-     indirectly infringes any patent where such claim is resolved (such as
-     by license or settlement) prior to the initiation of patent
-     infringement litigation, then the reasonable value of the licenses
-     granted by such Participant under Sections 2.1 or 2.2 shall be taken
-     into account in determining the amount or value of any payment or
-     license.
-
-     8.4.  In the event of termination under Sections 8.1 or 8.2 above,
-     all end user license agreements (excluding distributors and resellers)
-     which have been validly granted by You or any distributor hereunder
-     prior to termination shall survive termination.
-
-9. LIMITATION OF LIABILITY.
-
-     UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
-     (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
-     DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
-     OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
-     ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
-     CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
-     WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
-     COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
-     INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
-     LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
-     RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
-     PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
-     EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
-     THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
-
-10. U.S. GOVERNMENT END USERS.
-
-     The Covered Code is a "commercial item," as that term is defined in
-     48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
-     software" and "commercial computer software documentation," as such
-     terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
-     C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
-     all U.S. Government End Users acquire Covered Code with only those
-     rights set forth herein.
-
-11. MISCELLANEOUS.
-
-     This License represents the complete agreement concerning subject
-     matter hereof. If any provision of this License is held to be
-     unenforceable, such provision shall be reformed only to the extent
-     necessary to make it enforceable. This License shall be governed by
-     California law provisions (except to the extent applicable law, if
-     any, provides otherwise), excluding its conflict-of-law provisions.
-     With respect to disputes in which at least one party is a citizen of,
-     or an entity chartered or registered to do business in the United
-     States of America, any litigation relating to this License shall be
-     subject to the jurisdiction of the Federal Courts of the Northern
-     District of California, with venue lying in Santa Clara County,
-     California, with the losing party responsible for costs, including
-     without limitation, court costs and reasonable attorneys' fees and
-     expenses. The application of the United Nations Convention on
-     Contracts for the International Sale of Goods is expressly excluded.
-     Any law or regulation which provides that the language of a contract
-     shall be construed against the drafter shall not apply to this
-     License.
-
-12. RESPONSIBILITY FOR CLAIMS.
-
-     As between Initial Developer and the Contributors, each party is
-     responsible for claims and damages arising, directly or indirectly,
-     out of its utilization of rights under this License and You agree to
-     work with Initial Developer and Contributors to distribute such
-     responsibility on an equitable basis. Nothing herein is intended or
-     shall be deemed to constitute any admission of liability.
-
-13. MULTIPLE-LICENSED CODE.
-
-     Initial Developer may designate portions of the Covered Code as
-     "Multiple-Licensed".  "Multiple-Licensed" means that the Initial
-     Developer permits you to utilize portions of the Covered Code under
-     Your choice of the NPL or the alternative licenses, if any, specified
-     by the Initial Developer in the file described in Exhibit A.
-
-EXHIBIT A -Mozilla Public License.
-
-     ``The contents of this file are subject to the Mozilla Public License
-     Version 1.1 (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.mozilla.org/MPL/
-
-     Software distributed under the License is distributed on an "AS IS"
-     basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
-     License for the specific language governing rights and limitations
-     under the License.
-
-     The Original Code is ______________________________________.
-
-     The Initial Developer of the Original Code is ________________________.
-     Portions created by ______________________ are Copyright (C) ______
-     _______________________. All Rights Reserved.
-
-     Contributor(s): ______________________________________.
-
-     Alternatively, the contents of this file may be used under the terms
-     of the _____ license (the  "[___] License"), in which case the
-     provisions of [______] License are applicable instead of those
-     above.  If you wish to allow use of your version of this file only
-     under the terms of the [____] License and not to allow others to use
-     your version of this file under the MPL, indicate your decision by
-     deleting  the provisions above and replace  them with the notice and
-     other provisions required by the [___] License.  If you do not delete
-     the provisions above, a recipient may use your version of this file
-     under either the MPL or the [___] License."
-
-     [NOTE: The text of this Exhibit A may differ slightly from the text of
-     the notices in the Source Code files of the Original Code. You should
-     use the text of this Exhibit A rather than the text found in the
-     Original Code Source Code for Your Modifications.]
-----
-
-GERRIT
-------
-Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/man/Makefile b/Documentation/man/Makefile
index 75e6533..945f215 100644
--- a/Documentation/man/Makefile
+++ b/Documentation/man/Makefile
@@ -34,6 +34,7 @@
 cmd-rename-group.txt   \
 cmd-review.txt         \
 cmd-set-account.txt    \
+cmd-set-members.txt    \
 cmd-set-project-parent.txt \
 cmd-set-project.txt    \
 cmd-set-reviewers.txt  \
diff --git a/Documentation/pgm-daemon.txt b/Documentation/pgm-daemon.txt
index 3ffcb40..ce88ab7 100644
--- a/Documentation/pgm-daemon.txt
+++ b/Documentation/pgm-daemon.txt
@@ -15,6 +15,7 @@
 	[\--console-log]
 	[\--slave]
 	[\--headless]
+	[\--init]
 
 DESCRIPTION
 -----------
@@ -63,6 +64,10 @@
 	Don't start the default Gerrit UI. May be useful when Gerrit is
 	run with an alternative UI.
 
+\--init::
+	Run init before starting the daemon. This will create a new site or
+	upgrade an existing site.
+
 CONTEXT
 -------
 This command can only be run on a server which has direct
diff --git a/Documentation/pgm-prolog-shell.txt b/Documentation/pgm-prolog-shell.txt
index f3fa2d8..3189e90 100644
--- a/Documentation/pgm-prolog-shell.txt
+++ b/Documentation/pgm-prolog-shell.txt
@@ -20,7 +20,7 @@
 -s::
 	Dynamically load the Prolog source code at startup,
 	as though the user had entered `['FILE.pl'].` into
-	the interepter once it was running. This option may
+	the interpreter once it was running. This option may
 	be supplied more than once to load multiple files.
 
 EXAMPLES
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index 7912cf2..80f92a3 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -182,7 +182,7 @@
 <2> label `'Verified'` is rejected. Change is not submittable.
 <3> label `'Author-is-John-Doe'` is needed for the change to become submittable.
     Note that this tells nothing about how this criteria will be met. It is up
-    to the implementor of the `submit_rule` to return `label('Author-is-John-Doe',
+    to the implementer of the `submit_rule` to return `label('Author-is-John-Doe',
     ok(_))` when this criteria is met.  Most likely, it will have to match
     against `gerrit:commit_author` in order to check if this criteria is met.
     This will become clear through the examples below.
@@ -645,7 +645,7 @@
 
 Reusing the default submit policy
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To get results of Gerrits default submit policy we use the
+To get results of Gerrit's default submit policy we use the
 `gerrit:default_submit` predicate.  The `gerrit:default_submit(X)` includes all
 categories from the database.  This means that if we write a submit rule like:
 
@@ -743,7 +743,7 @@
 The latter implementation is probably easier to understand and the code looks
 cleaner. Note, however, that the latter implementation will always return the
 two standard categories only (`Code-Review` and `Verified`) even if a new
-category has beeen inserted into the database. To include the new category
+category has been inserted into the database. To include the new category
 the `rules.pl` would need to be modified or a `submit_filter` in a parent
 project would have to care about including the new category in the result
 of this `submit_rule`.
diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt
new file mode 100644
index 0000000..6c786e4
--- /dev/null
+++ b/Documentation/rest-api-access.txt
@@ -0,0 +1,380 @@
+Gerrit Code Review - /access/ REST API
+======================================
+
+This page describes the access rights related REST endpoints.
+Please also take note of the general information on the
+link:rest-api.html[REST API].
+
+[[access-endpoints]]
+Access Rights Endpoints
+-----------------------
+
+[[list-access]]
+List Access Rights
+~~~~~~~~~~~~~~~~~~
+[verse]
+'GET /access/?project=link:rest-api-projects.html#project-name[\{project-name\}]'
+
+Lists the access rights for projects. The projects for which the access
+rights should be returned must be specified as `project` options. The
+`project` can be specified multiple times.
+
+As result a map is returned that maps the project name to
+link:#project-access-info[ProjectAccessInfo] entities.
+
+The entries in the map are sorted by project name.
+
+.Request
+----
+  GET /access/?project=MyProject&project=All-Projects HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "All-Projects": {
+      "revision": "edd453d18e08640e67a8c9a150cec998ed0ac9aa",
+      "local": {
+        "GLOBAL_CAPABILITIES": {
+          "permissions": {
+            "priority": {
+              "rules": {
+                "15bfcd8a6de1a69c50b30cedcdcc951c15703152": {
+                  "action": "BATCH"
+                }
+              }
+            },
+            "streamEvents": {
+              "rules": {
+                "15bfcd8a6de1a69c50b30cedcdcc951c15703152": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "administrateServer": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                }
+              }
+            }
+          }
+        },
+        "refs/meta/config": {
+          "permissions": {
+            "submit": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "label-Code-Review": {
+              "label": "Code-Review",
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW",
+                  "min": -2,
+                  "max": 2
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW",
+                  "min": -2,
+                  "max": 2
+                }
+              }
+            },
+            "read": {
+              "exclusive": true,
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "push": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            }
+          }
+        },
+        "refs/for/refs/*": {
+          "permissions": {
+            "pushMerge": {
+              "rules": {
+                "global:Registered-Users": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "push": {
+              "rules": {
+                "global:Registered-Users": {
+                  "action": "ALLOW"
+                }
+              }
+            }
+          }
+        },
+        "refs/tags/*": {
+          "permissions": {
+            "pushSignedTag": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "pushTag": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            }
+          }
+        },
+        "refs/heads/*": {
+          "permissions": {
+            "forgeCommitter": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "forgeAuthor": {
+              "rules": {
+                "global:Registered-Users": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "submit": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "editTopicName": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW",
+                  "force": true
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW",
+                  "force": true
+                }
+              }
+            },
+            "label-Code-Review": {
+              "label": "Code-Review",
+              "rules": {
+                "global:Registered-Users": {
+                  "action": "ALLOW",
+                  "min": -1,
+                  "max": 1
+                },
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW",
+                  "min": -2,
+                  "max": 2
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW",
+                  "min": -2,
+                  "max": 2
+                }
+              }
+            },
+            "create": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            },
+            "push": {
+              "rules": {
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                },
+                "global:Project-Owners": {
+                  "action": "ALLOW"
+                }
+              }
+            }
+          }
+        },
+        "refs/*": {
+          "permissions": {
+            "read": {
+              "rules": {
+                "global:Anonymous-Users": {
+                  "action": "ALLOW"
+                },
+                "53a4f647a89ea57992571187d8025f830625192a": {
+                  "action": "ALLOW"
+                }
+              }
+            }
+          }
+        }
+      },
+      "is_owner": true,
+      "owner_of": [
+        "GLOBAL_CAPABILITIES",
+        "refs/meta/config",
+        "refs/for/refs/*",
+        "refs/tags/*",
+        "refs/heads/*",
+        "refs/*"
+      ],
+      "can_upload": true,
+      "can_add": true,
+      "config_visible": true
+    },
+    "MyProject": {
+      "revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
+      "inherits_from": {
+        "kind": "gerritcodereview#project",
+        "id": "All-Projects",
+        "name": "All-Projects",
+        "description": "Access inherited by all other projects."
+      },
+      "local": {},
+      "is_owner": true,
+      "owner_of": [
+        "refs/*"
+      ],
+      "can_upload": true,
+      "can_add": true,
+      "config_visible": true
+    }
+  }
+----
+
+[[access-section-info]]
+AccessSectionInfo
+~~~~~~~~~~~~~~~~~
+The `AccessSectionInfo` describes the access rights that are assigned
+on a ref.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==================================
+|Field Name           ||Description
+|`permissions`        ||
+The permissions assigned on the ref of this access section as a map
+that maps the permission names to link:#permission-info[PermissionInfo]
+entities.
+|==================================
+
+[[permission-info]]
+PermissionInfo
+~~~~~~~~~~~~~~
+The `PermissionInfo` entity contains information about an assigned
+permission.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==================================
+|Field Name     ||Description
+|`label`        |optional|
+The name of the label. Not set if it's not a label permission.
+|`exclusive`    |not set if `false`|
+Whether this permission is assigned exclusively.
+|`rules`        ||
+The rules assigned for this permission as a map that maps the UUIDs of
+the groups for which the permission are assigned to
+link:#permission-info[PermissionRuleInfo] entities.
+|==================================
+
+[[permission-rule-info]]
+PermissionRuleInfo
+~~~~~~~~~~~~~~~~~~
+The `PermissionRuleInfo` entity contains information about a permission
+rule that is assigned to group.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==================================
+|Field Name     ||Description
+|`action`       ||
+The action of this rule. For normal permissions this can be `ALLOW`,
+`DENY` or `BLOCK`. Special values for global capabilities are
+`INTERACTIVE` and `BATCH`.
+|`force`        |not set if `false`|
+Whether the force flag is set.
+|`min`          |
+not set if range if empty (from `0` to `0`) or not set|
+The min value of the permission range.
+|`max`          |
+not set if range if empty (from `0` to `0`) or not set|
+The max value of the permission range.
+|==================================
+
+[[project-access-info]]
+ProjectAccessInfo
+~~~~~~~~~~~~~~~~~
+The `ProjectAccessInfo` entity contains information about the access
+rights for a project.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==================================
+|Field Name           ||Description
+|`revision`           ||
+The revision of the `refs/meta/config` branch from which the access
+rights were loaded.
+|`inherits_from`      |not set for the `All-Project` project|
+The parent project from which permissions are inherited as a
+link:rest-api-projects.html#project-info[ProjectInfo] entity.
+|`local`              ||
+The local access rights of the project as a map that maps the refs to
+link:#access-section-info[AccessSectionInfo] entities.
+|`is_owner`           |not set if `false`|
+Whether the calling user owns this project.
+|`owner_of`           ||The list of refs owned by the calling user.
+|`can_upload`         |not set if `false`|
+Whether the calling user can upload to any ref.
+|`can_add`            |not set if `false`|
+Whether the calling user can add any ref.
+|`config_visible`     |not set if `false`|
+Whether the calling user can see the `refs/meta/config` branch of the
+project.
+|==================================
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index f17a741..2cff3eb 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -31,10 +31,565 @@
   {
     "_account_id": 1000096,
     "name": "John Doe",
+    "email": "john.doe@example.com",
+    "username": "john"
+  }
+----
+
+[[create-account]]
+Create Account
+~~~~~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#username[\{username\}]'
+
+Creates a new account.
+
+In the request body additional data for the account can be provided as
+link:#account-input[AccountInput].
+
+.Request
+----
+  PUT /accounts/john HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "name": "John Doe",
+    "email": "john.doe@example.com",
+    "ssh_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw==",
+    "http_password": "19D9aIn7zePb",
+    "groups": [
+      "MyProject-Owners"
+    ]
+  }
+----
+
+As response a detailed link:#account-info[AccountInfo] entity is
+returned that describes the created account.
+
+.Response
+----
+  HTTP/1.1 201 Created
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "_account_id": 1000195,
+    "name": "John Doe",
     "email": "john.doe@example.com"
   }
 ----
 
+[[get-account-name]]
+Get Account Name
+~~~~~~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/name'
+
+Retrieves the full name of an account.
+
+.Request
+----
+  GET /accounts/self/name HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  "John Doe"
+----
+
+If the account does not have a name an empty string is returned.
+
+[[set-account-name]]
+Set Account Name
+~~~~~~~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#account-id[\{account-id\}]/name'
+
+Sets the full name of an account.
+
+The new account name must be provided in the request body inside
+a link:#account-name-input[AccountNameInput] entity.
+
+.Request
+----
+  PUT /accounts/self/name HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "name": "John F. Doe"
+  }
+----
+
+As response the new account name is returned.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  "John F. Doe"
+----
+
+If the name was deleted the response is "`204 No Content`".
+
+Some realms may not allow to modify the account name. In this case the
+request is rejected with "`405 Method Not Allowed`".
+
+[[delete-account-name]]
+Delete Account Name
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /accounts/link:#account-id[\{account-id\}]/name'
+
+Deletes the name of an account.
+
+.Request
+----
+  DELETE /accounts/self/name HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+[[get-username]]
+Get Username
+~~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/username'
+
+Retrieves the username of an account.
+
+.Request
+----
+  GET /accounts/self/username HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  "john.doe"
+----
+
+If the account does not have a username the response is `404 Not Found`.
+
+[[get-active]]
+Get Active
+~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/active'
+
+Checks if an account is active.
+
+.Request
+----
+  GET /accounts/john.doe@example.com/active HTTP/1.0
+----
+
+As response `200 OK` is returned for an active account and
+`404 Not Found` is returned for an inactive account.
+
+.Response
+----
+  HTTP/1.1 200 OK
+----
+
+[[set-active]]
+Set Active
+~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#account-id[\{account-id\}]/active'
+
+Sets the account state to active.
+
+.Request
+----
+  PUT /accounts/john.doe@example.com/active HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 201 Created
+----
+
+If the account was already active the response is `200 OK`.
+
+[[delete-active]]
+Delete Active
+~~~~~~~~~~~~~
+[verse]
+'DELETE /accounts/link:#account-id[\{account-id\}]/active'
+
+Sets the account state to inactive.
+
+.Request
+----
+  DELETE /accounts/john.doe@example.com/active HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+If the account was already inactive the response is `404 Not Found`.
+
+[[get-http-password]]
+Get HTTP Password
+~~~~~~~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/password.http'
+
+Retrieves the HTTP password of an account.
+
+.Request
+----
+  GET /accounts/john.doe@example.com/password.http HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  "ETxgpih8xrNs"
+----
+
+If the account does not have an HTTP password the response is `404 Not Found`.
+
+[[set-http-password]]
+Set/Generate HTTP Password
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#account-id[\{account-id\}]/password.http'
+
+Sets/Generates the HTTP password of an account.
+
+The options for setting/generating the HTTP password must be provided
+in the request body inside a link:#http-password-input[
+HttpPasswordInput] entity.
+
+.Request
+----
+  PUT /accounts/self/password.http HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "generate": true
+  }
+----
+
+As response the new HTTP password is returned.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  "ETxgpih8xrNs"
+----
+
+If the HTTP password was deleted the response is "`204 No Content`".
+
+[[delete-http-password]]
+Delete HTTP Password
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /accounts/link:#account-id[\{account-id\}]/password.http'
+
+Deletes the HTTP password of an account.
+
+.Request
+----
+  DELETE /accounts/self/password.http HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+[[list-account-emails]]
+List Account Emails
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/emails'
+
+Returns the email addresses that are configured for the specified user.
+
+.Request
+----
+  GET /accounts/self/emails HTTP/1.0
+----
+
+As response the email addresses of the user are returned as a list of
+link:#email-info[EmailInfo] entities.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    {
+      "email": "john.doe@example.com",
+      "preferred": true
+    },
+    {
+      "email": "j.doe@example.com"
+    }
+  ]
+----
+
+[[get-account-email]]
+Get Account Email
+~~~~~~~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]'
+
+Retrieves an email address of a user.
+
+.Request
+----
+  GET /accounts/self/emails/john.doe@example.com HTTP/1.0
+----
+
+As response an link:#email-info[EmailInfo] entity is returned that
+describes the email address.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "email": "john.doe@example.com",
+    "preferred": true
+  }
+----
+
+[[create-account-email]]
+Create Account Email
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]'
+
+Registers a new email address for the user. A verification email is
+sent with a link that needs to be visited to confirm the email address,
+unless `DEVELOPMENT_BECOME_ANY_ACCOUNT` is used as authentication type.
+For the development mode email addresses are directly added without
+confirmation. A Gerrit administrator may add an email address without
+confirmation by setting `no_confirmation` in the
+link:#email-input[EmailInput].
+
+In the request body additional data for the email address can be
+provided as link:#email-input[EmailInput].
+
+.Request
+----
+  PUT /accounts/self/emails/john.doe@example.com HTTP/1.0
+----
+
+As response the new email address is returned as
+link:#email-info[EmailInfo] entity.
+
+.Response
+----
+  HTTP/1.1 201 Created
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "email": "john.doe@example.com",
+    "pending_confirmation": true
+  }
+----
+
+[[delete-account-email]]
+Delete Account Email
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]'
+
+Deletes an email address of an account.
+
+.Request
+----
+  DELETE /accounts/self/emails/john.doe@example.com HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+[[set-preferred-email]]
+Set Preferred Email
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]/preferred'
+
+Sets an email address as preferred email address for an account.
+
+.Request
+----
+  PUT /accounts/self/emails/john.doe@example.com/preferred HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 201 Created
+----
+
+If the email address was already the preferred email address of the
+account the response is "`200 OK`".
+
+[[list-ssh-keys]]
+List SSH Keys
+~~~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/sshkeys'
+
+Returns the SSH keys of an account.
+
+.Request
+----
+  GET /accounts/self/sshkeys HTTP/1.0
+----
+
+As response the SSH keys of the account are returned as a list of
+link:#ssh-key-info[SshKeyInfo] entities.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    {
+      "seq": 1,
+      "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d john.doe@example.com",
+      "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d",
+      "algorithm": "ssh-rsa",
+      "comment": "john.doe@example.com",
+      "valid": true
+    }
+  ]
+----
+
+[[get-ssh-key]]
+Get SSH Key
+~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/sshkeys/link:#ssh-key-id[\{ssh-key-id\}]'
+
+Retrieves an SSH key of a user.
+
+.Request
+----
+  GET /accounts/self/sshkeys/1 HTTP/1.0
+----
+
+As response an link:#ssh-key-info[SshKeyInfo] entity is returned that
+describes the SSH key.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "seq": 1,
+    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d john.doe@example.com",
+    "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d",
+    "algorithm": "ssh-rsa",
+    "comment": "john.doe@example.com",
+    "valid": true
+  }
+----
+
+[[add-ssh-key]]
+Add SSH Key
+~~~~~~~~~~~
+[verse]
+'POST /accounts/link:#account-id[\{account-id\}]/sshkeys'
+
+Adds an SSH key for a user.
+
+The SSH public key must be provided as raw content in the request body.
+
+.Request
+----
+  POST /accounts/self/sshkeys HTTP/1.0
+  Content-Type: plain/text
+
+  AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d
+----
+
+As response an link:#ssh-key-info[SshKeyInfo] entity is returned that
+describes the new SSH key.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "seq": 2,
+    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d john.doe@example.com",
+    "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d",
+    "algorithm": "ssh-rsa",
+    "comment": "john.doe@example.com",
+    "valid": true
+  }
+----
+
+[[delete-ssh-key]]
+Delete SSH Key
+~~~~~~~~~~~~~~
+[verse]
+'DELETE /accounts/link:#account-id[\{account-id\}]/sshkeys/link:#ssh-key-id[\{ssh-key-id\}]'
+
+Deletes an SSH key of a user.
+
+.Request
+----
+  DELETE /accounts/self/sshkeys/2 HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
 [[list-account-capabilities]]
 List Account Capabilities
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -391,6 +946,22 @@
 Identifier of a global capability. Valid values are all field names of
 the link:#capability-info[CapabilityInfo] entity.
 
+[[email-id]]
+\{email-id\}
+~~~~~~~~~~~~~~
+An email address, or `preferred` for the preferred email address of the
+user.
+
+[[username]]
+\{username\}
+~~~~~~~~~~~~
+The user name.
+
+[[ssh-key-id]]
+\{ssh-key-id\}
+~~~~~~~~~~~~
+The sequence number of the SSH key.
+
 
 [[json-entities]]
 JSON Entities
@@ -410,8 +981,43 @@
 |`email`       |optional|
 The email address the user prefers to be contacted through. +
 Only set if detailed account information is requested.
+|`username`    |optional|The username of the user. +
+Only set if detailed account information is requested.
 |===========================
 
+[[account-input]]
+AccountInput
+~~~~~~~~~~~~
+The `AccountInput` entity contains information for the creation of
+a new account.
+
+[options="header",width="50%",cols="1,^2,4"]
+|============================
+|Field Name     ||Description
+|`username`     |optional|
+The user name. If provided, must match the user name from the URL.
+|`name`         |optional|The full name of the user.
+|`email`        |optional|The email address of the user.
+|`ssh_key`      |optional|The public SSH key of the user.
+|`http_password`|optional|The HTTP password of the user.
+|`groups`       |optional|
+A list of link:rest-api-groups.html#group-id[group IDs] that identify
+the groups to which the user should be added.
+|============================
+
+[[account-name-input]]
+AccountNameInput
+~~~~~~~~~~~~~~~~
+The `AccountNameInput` entity contains information for setting a name
+for an account.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=============================
+|Field Name ||Description
+|`name`     |optional|The new full name of the account. +
+If not set or if set to an empty string, the account name is deleted.
+|=============================
+
 [[capability-info]]
 CapabilityInfo
 ~~~~~~~~~~~~~~
@@ -551,6 +1157,65 @@
 Number of spaces that should be used to display one tab.
 |=====================================
 
+[[email-info]]
+EmailInfo
+~~~~~~~~~
+The `EmailInfo` entity contains information about an email address of a
+user.
+
+[options="header",width="50%",cols="1,^1,5"]
+|========================
+|Field Name ||Description
+|`email`    ||The email address.
+|`preferred`|not set if `false`|
+Whether this is the preferred email address of the user.
+|`pending_confirmation`|not set if `false`|
+Set true if the user must confirm control of the email address
+by following a verification link before Gerrit will permit use of
+this address.
+|========================
+
+[[email-input]]
+EmailInput
+~~~~~~~~~~
+The `EmailInput` entity contains information for registering a new
+email address.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==============================
+|Field Name       ||Description
+|`email`          ||
+The email address. If provided, must match the email address from the
+URL.
+|`preferred`      |`false` if not set|
+Whether the new email address should become the preferred email address
+of the user (only supported if `no_confirmation` is set or if the
+authentication type is `DEVELOPMENT_BECOME_ANY_ACCOUNT`).
+|`no_confirmation`|`false` if not set|
+Whether the email address should be added without confirmation. In this
+case no verification email is sent to the user. +
+Only Gerrit administrators are allowed to add email addresses without
+confirmation.
+|==============================
+
+[[http-password-input]]
+HttpPasswordInput
+~~~~~~~~~~~~~~~~~
+The `HttpPasswordInput` entity contains information for setting/generating
+an HTTP password.
+
+[options="header",width="50%",cols="1,^1,5"]
+|============================
+|Field Name     ||Description
+|`generate`     |`false` if not set|
+Whether a new HTTP password should be generated
+|`http_password`|optional|
+The new HTTP password. Only Gerrit administrators may set the HTTP
+password directly. +
+If empty or not set and `generate` is false or not set, the HTTP
+password is deleted.
+|============================
+
 [[query-limit-info]]
 QueryLimitInfo
 ~~~~~~~~~~~~~~
@@ -564,6 +1229,23 @@
 |`max`               |Upper limit.
 |================================
 
+[[ssh-key-info]]
+SshKeyInfo
+~~~~~~~~~~
+The `SshKeyInfo` entity contains information about an SSH key of a
+user.
+
+[options="header",width="50%",cols="1,^1,5"]
+|=============================
+|Field Name      ||Description
+|`seq`           ||The sequence number of the SSH key.
+|`ssh_public_key`||The complete public SSH key.
+|`encoded_key`   ||The encoded key.
+|`algorithm`     ||The algorithm of the SSH key.
+|`comment`       |optional|The comment of the SSH key.
+|`valid`         ||Whether the SSH key is valid.
+|=============================
+
 
 GERRIT
 ------
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 09c7e11..f1b2e99 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -213,6 +213,13 @@
 * `MESSAGES`: include messages associated with the change.
 --
 
+[[actions]]
+--
+* `CURRENT_ACTIONS`: include information on available actions
+  for the change and its current revision. The caller must be
+  authenticated to obtain the available actions.
+--
+
 .Request
 ----
   GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES HTTP/1.0
@@ -364,6 +371,11 @@
 detailed labels], link:#detailed-accounts[detailed accounts], and
 link:#messages[messages].
 
+Additional fields can be obtained by adding `o` parameters, each
+option requires more database lookups and slows down the query
+response time to the client so they are generally disabled by
+default. Fields are described in link:#list-changes[Query Changes].
+
 .Request
 ----
   GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/detail HTTP/1.0
@@ -710,6 +722,95 @@
   change is new
 ----
 
+[[rebase-change]]
+Rebase Change
+~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/rebase'
+
+Rebases a change.
+
+.Request
+----
+  POST /changes/myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2/rebase HTTP/1.0
+----
+
+As response a link:#change-info[ChangeInfo] entity is returned that
+describes the rebased change. Information about the current patch set
+is included.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "kind": "gerritcodereview#change",
+    "id": "myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2",
+    "project": "myProject",
+    "branch": "master",
+    "change_id": "I3ea943139cb62e86071996f2480e58bf3eeb9dd2",
+    "subject": "Implement Feature X",
+    "status": "NEW",
+    "created": "2013-02-01 09:59:32.126000000",
+    "updated": "2013-02-21 11:16:36.775000000",
+    "mergeable": false,
+    "_sortkey": "0024cf9a000012bf",
+    "_number": 4799,
+    "owner": {
+      "name": "John Doe"
+    },
+    "current_revision": "27cc4558b5a3d3387dd11ee2df7a117e7e581822",
+    "revisions": {
+      "27cc4558b5a3d3387dd11ee2df7a117e7e581822": {
+        "_number": 2,
+        "fetch": {
+          "http": {
+            "url": "http://gerrit:8080/myProject",
+            "ref": "refs/changes/99/4799/2"
+          }
+        },
+        "commit": {
+          "parents": [
+            {
+              "commit": "b4003890dadd406d80222bf1ad8aca09a4876b70",
+              "subject": "Implement Feature A"
+            }
+        ],
+        "author": {
+          "name": "John Doe",
+          "email": "john.doe@example.com",
+          "date": "2013-05-07 15:21:27.000000000",
+          "tz": 120
+        },
+        "committer": {
+          "name": "Gerrit Code Review",
+          "email": "gerrit-server@example.com",
+          "date": "2013-05-07 15:35:43.000000000",
+          "tz": 120
+        },
+        "subject": "Implement Feature X",
+        "message": "Implement Feature X\n\nAdded feature X."
+      }
+    }
+  }
+----
+
+If the change cannot be rebased, e.g. due to conflicts, the response is
+"`409 Conflict`" and the error message is contained in the response
+body.
+
+.Response
+----
+  HTTP/1.1 409 Conflict
+  Content-Disposition: attachment
+  Content-Type: text/plain;charset=UTF-8
+
+  The change could not be rebased due to a path conflict during merge.
+----
+
 [[revert-change]]
 Revert Change
 ~~~~~~~~~~~~~
@@ -1031,6 +1132,55 @@
 Revision Endpoints
 ------------------
 
+[[get-commit]]
+Get Commit
+~~~~~~~~~~
+[verse]
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/commit'
+
+Retrieves a parsed commit of a revision.
+
+.Request
+----
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/commit HTTP/1.0
+----
+
+As response a link:#commit-info[CommitInfo] entity is returned that
+describes the revision.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "kind": "gerritcodereview#commit",
+    "parents": [
+      {
+        "commit": "1eee2c9d8f352483781e772f35dc586a69ff5646",
+        "subject": "Migrate contributor agreements to All-Projects."
+      }
+    ],
+    "author": {
+      "name": "Shawn O. Pearce",
+      "email": "sop@google.com",
+      "date": "2012-04-24 18:08:08.000000000",
+      "tz": -420
+    },
+    "committer": {
+      "name": "Shawn O. Pearce",
+      "email": "sop@google.com",
+      "date": "2012-04-24 18:08:08.000000000",
+      "tz": -420
+    },
+    "subject": "Use an EventBus to manage star icons",
+    "message": "Use an EventBus to manage star icons\n\nImage widgets that need to ..."
+  }
+----
+
+
 [[get-review]]
 Get Review
 ~~~~~~~~~~
@@ -1218,6 +1368,95 @@
   }
 ----
 
+[[rebase-revision]]
+Rebase Revision
+~~~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/rebase'
+
+Rebases a revision.
+
+.Request
+----
+  POST /changes/myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/rebase HTTP/1.0
+----
+
+As response a link:#change-info[ChangeInfo] entity is returned that
+describes the rebased change. Information about the current patch set
+is included.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "kind": "gerritcodereview#change",
+    "id": "myProject~master~I3ea943139cb62e86071996f2480e58bf3eeb9dd2",
+    "project": "myProject",
+    "branch": "master",
+    "change_id": "I3ea943139cb62e86071996f2480e58bf3eeb9dd2",
+    "subject": "Implement Feature X",
+    "status": "NEW",
+    "created": "2013-02-01 09:59:32.126000000",
+    "updated": "2013-02-21 11:16:36.775000000",
+    "mergeable": false,
+    "_sortkey": "0024cf9a000012bf",
+    "_number": 4799,
+    "owner": {
+      "name": "John Doe"
+    },
+    "current_revision": "27cc4558b5a3d3387dd11ee2df7a117e7e581822",
+    "revisions": {
+      "27cc4558b5a3d3387dd11ee2df7a117e7e581822": {
+        "_number": 2,
+        "fetch": {
+          "http": {
+            "url": "http://gerrit:8080/myProject",
+            "ref": "refs/changes/99/4799/2"
+          }
+        },
+        "commit": {
+          "parents": [
+            {
+              "commit": "b4003890dadd406d80222bf1ad8aca09a4876b70",
+              "subject": "Implement Feature A"
+            }
+        ],
+        "author": {
+          "name": "John Doe",
+          "email": "john.doe@example.com",
+          "date": "2013-05-07 15:21:27.000000000",
+          "tz": 120
+        },
+        "committer": {
+          "name": "Gerrit Code Review",
+          "email": "gerrit-server@example.com",
+          "date": "2013-05-07 15:35:43.000000000",
+          "tz": 120
+        },
+        "subject": "Implement Feature X",
+        "message": "Implement Feature X\n\nAdded feature X."
+      }
+    }
+  }
+----
+
+If the revision cannot be rebased, e.g. due to conflicts, the response is
+"`409 Conflict`" and the error message is contained in the response
+body.
+
+.Response
+----
+  HTTP/1.1 409 Conflict
+  Content-Disposition: attachment
+  Content-Type: text/plain;charset=UTF-8
+
+  The change could not be rebased due to a path conflict during merge.
+----
+
 [[submit-revision]]
 Submit Revision
 ~~~~~~~~~~~~~~~
@@ -1268,6 +1507,32 @@
   "revision 674ac754f91e64a0efb8087e59a176484bd534d1 is not current revision"
 ----
 
+[[get-patch]]
+Get Patch
+~~~~~~~~~
+[verse]
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/patch'
+
+Gets the formatted patch for one revision.
+
+.Request
+----
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/patch HTTP/1.0
+----
+
+The formatted patch is returned as text encoded inside base64:
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: text/plain;charset=ISO-8859-1
+  X-FYI-Content-Encoding: base64
+  X-FYI-Content-Type: application/mbox
+
+  RnJvbSA3ZGFkY2MxNTNmZGVhMTdhYTg0ZmYzMmE2ZTI0NWRiYjY...
+----
+
 [[get-submit-type]]
 Get Submit Type
 ~~~~~~~~~~~~~~~
@@ -1641,13 +1906,239 @@
   }
 ----
 
+[[list-files]]
+List Files
+~~~~~~~~~~
+[verse]
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/'
+
+Lists the files that were modified, added or deleted in a revision.
+
+.Request
+----
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/ HTTP/1.0
+----
+
+As result a map is returned that maps the file path to a list of
+link:#file-info[FileInfo] entries. The entries in the map are
+sorted by file path.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "/COMMIT_MSG": {
+      "status": "A",
+      "lines_inserted": 7
+    },
+    "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java": {
+      "lines_inserted": 5,
+      "lines_deleted": 3
+    }
+  }
+----
+
+[[get-content]]
+Get Content
+~~~~~~~~~~~
+[verse]
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/content'
+
+Gets the content of a file from a certain revision.
+
+.Request
+----
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/content HTTP/1.0
+----
+
+The content is returned as base64 encoded string.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: text/plain;charset=UTF-8
+
+  Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
+----
+
+[[get-diff]]
+Get Diff
+~~~~~~~~
+[verse]
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/diff'
+
+Gets the diff of a file from a certain revision.
+
+.Request
+----
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/diff HTTP/1.0
+----
+
+As response a link:#diff-info[DiffInfo] entity is returned that describes the diff.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]
+  {
+    "meta_a": {
+      "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "content_type": "text/x-java-source"
+    },
+    "meta_b": {
+      "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "content_type": "text/x-java-source"
+    },
+    "change_type": "MODIFIED",
+    "diff_header": [
+      "diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "index 59b7670..9faf81c 100644",
+      "--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java"
+    ],
+    "content": [
+      {
+        "ab": [
+          "// Copyright (C) 2010 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."
+        ]
+      },
+      {
+        "b": [
+          "//",
+          "// Add some more lines in the header."
+        ]
+      },
+      {
+        "ab": [
+          "",
+          "package com.google.gerrit.server.project;",
+          "",
+          "import com.google.common.collect.Maps;",
+          ...
+        ]
+      }
+      ...
+    ]
+  }
+----
+
+If the `intraline` parameter is specified, intraline differences are included in the diff.
+
+.Request
+----
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/b6b9c10649b9041884046119ab794374470a1b45/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/diff?intraline HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]
+  {
+    "meta_a": {
+      "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "content_type": "text/x-java-source"
+    },
+    "meta_b": {
+      "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "content_type": "text/x-java-source"
+    },
+    "change_type": "MODIFIED",
+    "diff_header": [
+      "diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "index 59b7670..9faf81c 100644",
+      "--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java"
+    ],
+    "content": [
+      ...
+      {
+        "a": [
+          "/** Manages access control for Git references (aka branches, tags). */"
+        ],
+        "b": [
+          "/** Manages access control for the Git references (aka branches, tags). */"
+        ],
+        "edit_a": [],
+        "edit_b": [
+          [
+            31,
+            4
+          ]
+        ]
+      }
+      ]
+    }
+----
+
+The `base` parameter can be specified to control the base patch set from which the diff should
+be generated.
+
+.Request
+----
+  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/b6b9c10649b9041884046119ab794374470a1b45/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/diff?base=2 HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]
+  {
+    "meta_a": {
+      "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "content_type": "text/x-java-source"
+    },
+    "meta_b": {
+      "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
+      "content_type": "text/x-java-source"
+    },
+    "change_type": "MODIFIED",
+    "content": [
+      {
+        "skip": 578
+      }
+    ]
+  }
+----
+
+The `ignore-whitespace` parameter can be specified to control how whitespace differences are
+reported in the result.  Valid values are `NONE`, `TRAILING`, `CHANGED` or `ALL`.
+
+The `context` parameter can be specified to control the number of lines of surrounding context
+in the diff.  Valid values are `ALL` or number of lines.
+
 [[set-reviewed]]
 Set Reviewed
 ~~~~~~~~~~~~
 [verse]
-'PUT /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#patch-id[\{patch-id\}]/reviewed'
+'PUT /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/reviewed'
 
-Marks a patch of a revision as reviewed by the calling user.
+Marks a file of a revision as reviewed by the calling user.
 
 .Request
 ----
@@ -1659,16 +2150,16 @@
   HTTP/1.1 201 Created
 ----
 
-If the patch was already marked as reviewed by the calling user the
+If the file was already marked as reviewed by the calling user the
 response is "`200 OK`".
 
 [[delete-reviewed]]
 Delete Reviewed
 ~~~~~~~~~~~~~~~
 [verse]
-'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#patch-id[\{patch-id\}]/reviewed'
+'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/reviewed'
 
-Deletes the reviewed flag of the calling user from a patch of a revision.
+Deletes the reviewed flag of the calling user from a file of a revision.
 
 .Request
 ----
@@ -1680,6 +2171,57 @@
   HTTP/1.1 204 No Content
 ----
 
+[[cherry-pick]]
+Cherry Pick Revision
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/cherrypick'
+
+Cherry picks a revision to a destination branch.
+
+The commit message and destination branch must be provided in the request body inside a
+link:#cherrypick-input[CherryPickInput] entity.
+
+.Request
+----
+  POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/cherrypick HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "message" : "Implementing Feature X",
+    "destination" : "release-branch"
+  }
+----
+
+As response a link:#change-info[ChangeInfo] entity is returned that
+describes the resulting cherry picked change.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "kind": "gerritcodereview#change",
+    "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9941",
+    "project": "myProject",
+    "branch": "release-branch",
+    "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9941",
+    "subject": "Implementing Feature X",
+    "status": "NEW",
+    "created": "2013-02-01 09:59:32.126000000",
+    "updated": "2013-02-21 11:16:36.775000000",
+    "reviewed": true,
+    "mergeable": true,
+    "_sortkey": "0023412400000f7d",
+    "_number": 3965,
+    "owner": {
+      "name": "John Doe"
+    }
+  }
+----
 
 [[ids]]
 IDs
@@ -1715,10 +2257,10 @@
 ~~~~~~~~~~~~
 UUID of a draft comment.
 
-[[patch-id]]
-\{patch-id\}
+[[file-id]]
+\{file-id\}
 ~~~~~~~~~~~~
-The file path of the patch.
+The path of the file.
 
 [[revision-id]]
 \{revision-id\}
@@ -1733,7 +2275,6 @@
   change ("674ac754"), at least 4 digits are required
 * a legacy numeric patch number ("1" for first patch set of the change)
 
-
 [[json-entities]]
 JSON Entities
 -------------
@@ -1751,6 +2292,33 @@
 change.
 |===========================
 
+[[action-info]]
+ActionInfo
+~~~~~~~~~~
+The `ActionInfo` entity describes a REST API call the client can
+make to manipulate a resource. These are frequently implemented by
+plugins and may be discovered at runtime.
+
+[options="header",width="50%",cols="1,^1,5"]
+|====================================
+|Field Name             ||Description
+|`method`               |optional|
+HTTP method to use with the action. Most actions use `POST`, `PUT`
+or `DELETE` to cause state changes.
+|`label`                |optional|
+Short title to display to a user describing the action. In the
+Gerrit web interface the label is used as the text on the button
+presented in the UI.
+|`title`                |optional|
+Longer text to display describing the action. In a web UI this
+should be the title attribute of the element, displaying when
+the user hovers the mouse.
+|`enabled`              |optional|
+If true the action is permitted at this time and the caller is
+likely allowed to execute it. This may change if state is updated
+at the server or permissions are modified. Not present if false.
+|====================================
+
 [[add-reviewer-result]]
 AddReviewerResult
 ~~~~~~~~~~~~~~~~~
@@ -1788,6 +2356,8 @@
 The vote that the user has given for the label. If present and zero, the
 user is permitted to vote on the label. If absent, the user is not
 permitted to vote on that label.
+|`date`        |optional|
+The time and date describing when the approval was made.
 |===========================
 
 [[change-info]]
@@ -1832,6 +2402,10 @@
 |`owner`              ||
 The owner of the change as an link:rest-api-accounts.html#account-info[
 AccountInfo] entity.
+|`actions`            |optional|
+Actions the caller might be able to perform on this revision. The
+information is a map of view name to link:#action-info[ActionInfo]
+entities.
 |`labels`             |optional|
 The labels of the change as a map that maps the label names to
 link:#label-info[LabelInfo] entries. +
@@ -1883,6 +2457,18 @@
 Which patchset (if any) generated this message.
 |==================================
 
+[[cherrypick-input]]
+CherryPickInput
+~~~~~~~~~~~~~~~
+The `CherryPickInput` entity contains information for cherry-picking a change to a new branch.
+
+[options="header",width="50%",cols="1,6"]
+|===========================
+|Field Name    |Description
+|`message`     |Commit message for the cherry-picked change
+|`destination` |Destination Branch
+|===========================
+
 [[comment-info]]
 CommentInfo
 ~~~~~~~~~~~
@@ -1963,7 +2549,8 @@
 |`commit`      |The commit ID.
 |`parent`      |
 The parent commits of this commit as a list of
-link:#commit-info[CommitInfo] entities.
+link:#commit-info[CommitInfo] entities. In parent
+only `commit` and `subject` fields are populated.
 |`author`      |The author of the commit as a
 link:#git-person-info[GitPersonInfo] entity.
 |`committer`   |The committer of the commit as a
@@ -1973,6 +2560,79 @@
 |`message`     |The commit message.
 |==========================
 
+[[diff-content]]
+DiffContent
+~~~~~~~~~~~
+The `DiffContent` entity contains information about the content differences
+in a file.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==========================
+|Field Name ||Description
+|`a`        |optional|Content only in the file on side A (deleted in B).
+|`b`        |optional|Content only in the file on side B (added in B).
+|`ab`       |optional|Content in the file on both sides (unchanged).
+|`edit_a`   |only present during a replace, i.e. both `a` and `b` are present|
+Text sections deleted from side A as a
+link:#diff-intraline-info[DiffIntralineInfo] entity.
+|`edit_b`   |only present during a replace, i.e. both `a` and `b` are present|
+Text sections inserted in side B as a
+link:#diff-intraline-info[DiffIntralineInfo] entity.
+|`skip`     |optional|count of lines skipped on both sides when the file is
+too large to include all common lines.
+|==========================
+
+[[diff-file-meta-info]]
+DiffFileMetaInfo
+~~~~~~~~~~~~~~~~
+The `DiffFileMetaInfo` entity contains meta information about a file diff.
+
+[options="header",width="50%",cols="1,6"]
+|==========================
+|Field Name    |Description
+|`name`        |The name of the file.
+|`content_type`|The content type of the file.
+|==========================
+
+[[diff-info]]
+DiffInfo
+~~~~~~~~
+The `DiffInfo` entity contains information about the diff of a file
+in a revision.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==========================
+|Field Name        ||Description
+|`meta_a`          |not present when the file is added|
+Meta information about the file on side A as a
+link:#diff-file-meta-info[DiffFileMetaInfo] entity.
+|`meta_b`          |not present when the file is deleted|
+Meta information about the file on side B as a
+link:#diff-file-meta-info[DiffFileMetaInfo] entity.
+|`change_type`     ||The type of change (`ADDED`, `MODIFIED`, `DELETED`, `RENAMED`
+`COPIED`, `REWRITE`).
+|`intraline_status`|only set when the `intraline` parameter was specified in the request|
+Intraline status (`OK`, `ERROR`, `TIMEOUT`).
+|`diff_header`     ||A list of strings representing the patch set diff header.
+|`content`         ||The content differences in the file as a list of
+link:#diff-content[DiffContent] entities.
+|==========================
+
+[[diff-intraline-info]]
+DiffIntralineInfo
+~~~~~~~~~~~~~~~~~
+The `DiffIntralineInfo` entity contains information about intraline edits in a
+file.
+
+The information consists of a list of `<skip length, mark length>` pairs, where
+the skip length is the number of characters between the end of the previous edit
+and the start of this edit, and the mark length is the number of edited characters
+following the skip. The start of the edits is from the beginning of the related
+diff content lines.
+
+Note that the implied newline character at the end of each line is included in
+the length calculation, and thus it is possible for the edits to span newlines.
+
 [[fetch-info]]
 FetchInfo
 ~~~~~~~~~
@@ -2030,40 +2690,62 @@
 [[label-info]]
 LabelInfo
 ~~~~~~~~~
-The `LabelInfo` entity contains information about a label on a change.
+The `LabelInfo` entity contains information about a label on a change, always
+corresponding to the current patch set.
 
+There are two options that control the contents of `LabelInfo`:
+link:#labels[`LABELS`] and link:#detailed-labels[`DETAILED_LABELS`].
+
+* For a quick summary of the state of labels, use `LABELS`.
+* For detailed information about labels, including exact numeric votes for all
+  users and the allowed range of votes for the current user, use `DETAILED_LABELS`.
+
+Common fields
+^^^^^^^^^^^^^
 [options="header",width="50%",cols="1,^1,5"]
 |===========================
 |Field Name    ||Description
-|`approved`    |optional|The user who approved this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`rejected`    |optional|The user who rejected this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`recommended` |optional|The user who recommended this label on the
-change as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`disliked`    |optional|The user who disliked this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`value`       |optional|The voting value of the user who
-recommended/disliked this label on the change if it is not
-"`+1`"/"`-1`". +
-Only set if link:#labels[labels] are requested.
 |`optional`    |not set if `false`|
 Whether the label is optional. Optional means the label may be set, but
 it's neither necessary for submission nor does it block submission if
 set.
+|===========================
+
+Fields set by `LABELS`
+^^^^^^^^^^^^^^^^^^^^^^
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name    ||Description
+|`approved`    |optional|One user who approved this label on the change
+(voted the maximum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`rejected`    |optional|One user who rejected this label on the change
+(voted the minimum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`recommended` |optional|One user who recommended this label on the
+change (voted positively, but not the maximum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`disliked`    |optional|One user who disliked this label on the change
+(voted negatively, but not the minimum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`value`       |optional|The voting value of the user who
+recommended/disliked this label on the change if it is not
+"`+1`"/"`-1`".
+|===========================
+
+Fields set by `DETAILED_LABELS`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name    ||Description
 |`all`         |optional|List of all approvals for this label as a list
-of link:#approval-info[ApprovalInfo] entities. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+of link:#approval-info[ApprovalInfo] entities.
 |`values`      |optional|A map of all values that are allowed for this
 label. The map maps the values ("`-2`", "`-1`", " `0`", "`+1`", "`+2`")
-to the value descriptions. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+to the value descriptions.
 |===========================
 
+
 [[restore-input]]
 RestoreInput
 ~~~~~~~~~~~~
@@ -2138,6 +2820,14 @@
 after the review is stored. +
 Allowed values are `NONE`, `OWNER`, `OWNER_REVIEWERS` and `ALL`. +
 If not set, the default is `ALL`.
+|`on_behalf_of`|optional|
+link:rest-api-accounts.html#account-id[\{account-id\}] the review
+should be posted on behalf of. To use this option the caller must
+have been granted `labelAs-NAME` permission for all keys of labels.
+|`wait_for_commit`|optional|
+Whether the request should wait for commit to the index to finish.
+If `false` (default) the request returns after the data is sent to
+the index, but searches may not immediately see the update.
 |============================
 
 [[reviewer-info]]
@@ -2196,11 +2886,15 @@
 Information about how to fetch this patch set. The fetch information is
 provided as a map that maps the protocol name ("`git`", "`http`",
 "`ssh`") to link:#fetch-info[FetchInfo] entities.
-|`commit`      ||The commit of the patch set as
+|`commit`      |optional|The commit of the patch set as
 link:#commit-info[CommitInfo] entity.
-|`files`       ||
+|`files`       |optional|
 The files of the patch set as a map that maps the file names to
 link:#file-info[FileInfo] entities.
+|`actions`     |optional|
+Actions the caller might be able to perform on this revision. The
+information is a map of view name to link:#action-info[ActionInfo]
+entities.
 |===========================
 
 [[rule-input]]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
new file mode 100644
index 0000000..6af6897
--- /dev/null
+++ b/Documentation/rest-api-config.txt
@@ -0,0 +1,159 @@
+Gerrit Code Review - /config/ REST API
+======================================
+
+This page describes the config related REST endpoints.
+Please also take note of the general information on the
+link:rest-api.html[REST API].
+
+[[config-endpoints]]
+Config Endpoints
+---------------
+
+[[get-version]]
+Get Version
+~~~~~~~~~~~
+[verse]
+'GET /config/server/version'
+
+Returns the version of the Gerrit server.
+
+.Request
+----
+  GET /config/server/version HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  "2.7"
+----
+
+[[list-capabilities]]
+List Capabilities
+~~~~~~~~~~~~~~~~~
+[verse]
+'GET /config/server/capabilities'
+
+Lists the capabilities that are available in the system. There are two
+kinds of capabilities: core and plugin-owned capabilities.
+
+As result a map of link:#capability-info[CapabilityInfo] entities is
+returned.
+
+The entries in the map are sorted by capability ID.
+
+.Request
+----
+  GET /config/server/capabilities/ HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "accessDatabase": {
+      "kind": "gerritcodereview#capability",
+      "id": "accessDatabase",
+      "name": "Access Database"
+    },
+    "administrateServer": {
+      "kind": "gerritcodereview#capability",
+      "id": "administrateServer",
+      "name": "Administrate Server"
+    },
+    "createAccount": {
+      "kind": "gerritcodereview#capability",
+      "id": "createAccount",
+      "name": "Create Account"
+    },
+    "createGroup": {
+      "kind": "gerritcodereview#capability",
+      "id": "createGroup",
+      "name": "Create Group"
+    },
+    "createProject": {
+      "kind": "gerritcodereview#capability",
+      "id": "createProject",
+      "name": "Create Project"
+    },
+    "emailReviewers": {
+      "kind": "gerritcodereview#capability",
+      "id": "emailReviewers",
+      "name": "Email Reviewers"
+    },
+    "flushCaches": {
+      "kind": "gerritcodereview#capability",
+      "id": "flushCaches",
+      "name": "Flush Caches"
+    },
+    "killTask": {
+      "kind": "gerritcodereview#capability",
+      "id": "killTask",
+      "name": "Kill Task"
+    },
+    "priority": {
+      "kind": "gerritcodereview#capability",
+      "id": "priority",
+      "name": "Priority"
+    },
+    "queryLimit": {
+      "kind": "gerritcodereview#capability",
+      "id": "queryLimit",
+      "name": "Query Limit"
+    },
+    "runGC": {
+      "kind": "gerritcodereview#capability",
+      "id": "runGC",
+      "name": "Run Garbage Collection"
+    },
+    "startReplication": {
+      "kind": "gerritcodereview#capability",
+      "id": "startReplication",
+     "name": "Start Replication"
+    },
+    "streamEvents": {
+      "kind": "gerritcodereview#capability",
+      "id": "streamEvents",
+      "name": "Stream Events"
+    },
+    "viewCaches": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewCaches",
+      "name": "View Caches"
+    },
+    "viewConnections": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewConnections",
+      "name": "View Connections"
+    },
+    "viewQueue": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewQueue",
+      "name": "View Queue"
+    }
+  }
+----
+
+[[capability-info]]
+CapabilityInfo
+~~~~~~~~~~~~~~
+The `CapabilityInfo` entity contains information about a capability.
+
+[options="header",width="50%",cols="1,5"]
+|=================================
+|Field Name           |Description
+|`kind`               |`gerritcodereview#capability`
+|`id`                 |capability ID
+|`name`               |capability name
+|=================================
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index e800d56..d8f021b 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -293,12 +293,14 @@
       {
         "_account_id": 1000097,
         "name": "Jane Roe",
-        "email": "jane.roe@example.com"
+        "email": "jane.roe@example.com",
+        "username": "jane"
       },
       {
         "_account_id": 1000096,
         "name": "John Doe",
         "email": "john.doe@example.com"
+        "username": "john"
       }
     ],
     "includes": []
@@ -620,12 +622,14 @@
     {
       "_account_id": 1000097,
       "name": "Jane Roe",
-      "email": "jane.roe@example.com"
+      "email": "jane.roe@example.com",
+      "username": "jane"
     },
     {
       "_account_id": 1000096,
       "name": "John Doe",
-      "email": "john.doe@example.com"
+      "email": "john.doe@example.com",
+      "username": "john"
     }
   ]
 ----
@@ -657,17 +661,20 @@
     {
       "_account_id": 1000097,
       "name": "Jane Roe",
-      "email": "jane.roe@example.com"
+      "email": "jane.roe@example.com",
+      "username": "jane"
     },
     {
       "_account_id": 1000096,
       "name": "John Doe",
-      "email": "john.doe@example.com"
+      "email": "john.doe@example.com",
+      "username": "john"
     },
     {
       "_account_id": 1000098,
       "name": "Richard Roe",
-      "email": "richard.roe@example.com"
+      "email": "richard.roe@example.com",
+      "username": "rroe"
     }
   ]
 ----
@@ -698,7 +705,8 @@
   {
     "_account_id": 1000096,
     "name": "John Doe",
-    "email": "john.doe@example.com"
+    "email": "john.doe@example.com",
+    "username": "john"
   }
 ----
 
@@ -728,7 +736,8 @@
   {
     "_account_id": 1000037,
     "name": "John Doe",
-    "email": "john.doe@example.com"
+    "email": "john.doe@example.com",
+    "username": "john"
   }
 ----
 
@@ -782,12 +791,14 @@
     {
       "_account_id": 1000057,
       "name": "Jane Roe",
-      "email": "jane.roe@example.com"
+      "email": "jane.roe@example.com",
+      "username": "jane"
     },
     {
       "_account_id": 1000037,
       "name": "John Doe",
-      "email": "john.doe@example.com"
+      "email": "john.doe@example.com",
+      "username": "john"
     }
   ]
 ----
@@ -923,7 +934,8 @@
 [verse]
 'PUT /groups/link:#group-id[\{group-id\}]/groups/link:#group-id[\{group-id\}]'
 
-Includes a group into a Gerrit internal group.
+Includes an internal or external group into a Gerrit internal group.
+External groups must be specified using the UUID.
 
 .Request
 ----
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 35caac4..0f01b58 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -499,6 +499,281 @@
   done.
 ----
 
+[[branch-endpoints]]
+Branch Endpoints
+----------------
+
+[[list-branches]]
+List Branches
+~~~~~~~~~~~~~
+[verse]
+'GET /projects/link:#project-name[\{project-name\}]/branches/'
+
+List the branches of a project.
+
+As result a list of link:#branch-info[BranchInfo] entries is
+returned.
+
+.Request
+----
+  GET /projects/work%2Fmy-project/branches/ HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    {
+      "ref": "HEAD",
+      "revision": "master"
+    },
+    {
+      "ref": "refs/meta/config",
+      "revision": "76016386a0d8ecc7b6be212424978bb45959d668"
+    },
+    {
+      "ref": "refs/heads/master",
+      "revision": "67ebf73496383c6777035e374d2d664009e2aa5c"
+    },
+    {
+      "ref": "refs/heads/stable",
+      "revision": "64ca533bd0eb5252d2fee83f63da67caae9b4674",
+      "can_delete": true
+    }
+  ]
+----
+
+[[get-branch]]
+Get Branch
+~~~~~~~~~~
+[verse]
+'GET /projects/link:#project-name[\{project-name\}]/branches/link:#branch-id[\{branch-id\}]'
+
+Retrieves a branch of a project.
+
+.Request
+----
+  GET /projects/work%2Fmy-project/branches/master HTTP/1.0
+----
+
+As response a link:#branch-info[BranchInfo] entity is returned that
+describes the branch.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "ref": "refs/heads/master",
+    "revision": "67ebf73496383c6777035e374d2d664009e2aa5c"
+  }
+----
+
+[[create-branch]]
+Create Branch
+~~~~~~~~~~~~~
+[verse]
+'PUT /projects/link:#project-name[\{project-name\}]/branches/link:#branch-id[\{branch-id\}]'
+
+Creates a new branch.
+
+In the request body additional data for the branch can be provided as
+link:#branch-input[BranchInput].
+
+.Request
+----
+  PUT /projects/MyProject/branches/stable HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "revision": "76016386a0d8ecc7b6be212424978bb45959d668"
+  }
+----
+
+As response a link:#branch-info[BranchInfo] entity is returned that
+describes the created branch.
+
+.Response
+----
+  HTTP/1.1 201 Created
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "ref": "refs/heads/stable",
+    "revision": "76016386a0d8ecc7b6be212424978bb45959d668",
+    "can_delete": true
+  }
+----
+
+[[delete-branch]]
+Delete Branch
+~~~~~~~~~~~~~
+[verse]
+'DELETE /projects/link:#project-name[\{project-name\}]/branches/link:#branch-id[\{branch-id\}]'
+
+Deletes a branch.
+
+.Request
+----
+  DELETE /projects/MyProject/branches/stable HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+[[child-project-endpoints]]
+Child Project Endpoints
+-----------------------
+
+[[list-child-projects]]
+List Child Projects
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'GET /projects/link:#project-name[\{project-name\}]/children/'
+
+List the direct child projects of a project.
+
+.Request
+----
+  GET /projects/Public-Plugins/children/ HTTP/1.0
+----
+
+As result a list of link:#project-info[ProjectInfo] entries is
+returned that describe the child projects.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    {
+      "kind": "gerritcodereview#project",
+      "id": "plugins%2Freplication",
+      "name": "plugins/replication",
+      "parent": "Public-Plugins",
+      "description": "Copies to other servers using the Git protocol"
+    },
+    {
+      "kind": "gerritcodereview#project",
+      "id": "plugins%2Freviewnotes",
+      "name": "plugins/reviewnotes",
+      "parent": "Public-Plugins",
+      "description": "Annotates merged commits using notes on refs/notes/review."
+    },
+    {
+      "kind": "gerritcodereview#project",
+      "id": "plugins%2Fsingleusergroup",
+      "name": "plugins/singleusergroup",
+      "parent": "Public-Plugins",
+      "description": "GroupBackend enabling users to be directly added to access rules"
+    }
+  ]
+----
+
+To resolve the child projects of a project recursively the parameter
+`recursive` can be set.
+
+Child projects that are not visible to the calling user are ignored and
+are not resolved further.
+
+.Request
+----
+  GET /projects/Public-Projects/children/?recursive HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    {
+      "kind": "gerritcodereview#project",
+      "id": "gerrit",
+      "name": "gerrit",
+      "parent": "Public-Projects",
+      "description": "Gerrit Code Review"
+    },
+    {
+      "kind": "gerritcodereview#project",
+      "id": "plugins%2Freplication",
+      "name": "plugins/replication",
+      "parent": "Public-Plugins",
+      "description": "Copies to other servers using the Git protocol"
+    },
+    {
+      "kind": "gerritcodereview#project",
+      "id": "plugins%2Freviewnotes",
+      "name": "plugins/reviewnotes",
+      "parent": "Public-Plugins",
+      "description": "Annotates merged commits using notes on refs/notes/review."
+    },
+    {
+      "kind": "gerritcodereview#project",
+      "id": "plugins%2Fsingleusergroup",
+      "name": "plugins/singleusergroup",
+      "parent": "Public-Plugins",
+      "description": "GroupBackend enabling users to be directly added to access rules"
+    },
+    {
+      "kind": "gerritcodereview#project",
+      "id": "Public-Plugins",
+      "name": "Public-Plugins",
+      "parent": "Public-Projects",
+      "description": "Parent project for plugins/*"
+    }
+  ]
+----
+
+[[get-child-project]]
+Get Child Project
+~~~~~~~~~~~~~~~~~
+[verse]
+'GET /projects/link:#project-name[\{project-name\}]/children/link:#project-name[\{project-name\}]'
+
+Retrieves a child project. If a non-direct child project should be
+retrieved the parameter `recursive` must be set.
+
+.Request
+----
+  GET /projects/Public-Plugins/children/plugins%2Freplication HTTP/1.0
+----
+
+As response a link:#project-info[ProjectInfo] entity is returned that
+describes the child project.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "kind": "gerritcodereview#project",
+    "id": "plugins%2Freplication",
+    "name": "plugins/replication",
+    "parent": "Public-Plugins",
+    "description": "Copies to other servers using the Git protocol"
+  }
+----
+
 [[dashboard-endpoints]]
 Dashboard Endpoints
 -------------------
@@ -727,6 +1002,12 @@
 IDs
 ---
 
+[[branch-id]]
+\{branch-id\}
+~~~~~~~~~~~~~
+The name of a branch or `HEAD`. The prefix `refs/heads/` can be
+omitted.
+
 [[dashboard-id]]
 \{dashboard-id\}
 ~~~~~~~~~~~~~~~~
@@ -745,6 +1026,38 @@
 JSON Entities
 -------------
 
+[[branch-info]]
+BranchInfo
+~~~~~~~~~~
+The `BranchInfo` entity contains information about a branch.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=========================
+|Field Name  ||Description
+|`ref`       ||The ref of the branch.
+|`revision`  ||The revision to which the branch points.
+|`can_delete`|`false` if not set|
+Whether the calling user can delete this branch.
+|=========================
+
+[[branch-input]]
+BranchInput
+~~~~~~~~~~~
+The `BranchInput` entity contains information for the creation of
+a new branch.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=======================
+|Field Name||Description
+|`ref`     |optional|
+The name of the branch. The prefix `refs/heads/` can be
+omitted. +
+If set, must match the branch ID in the URL.
+|`revision`|optional|
+The base revision of the new branch. +
+If not set, `HEAD` will be used as base revision.
+|=======================
+
 [[config-info]]
 ConfigInfo
 ~~~~~~~~~~
@@ -754,33 +1067,38 @@
 Fields marked with * are only visible to users who have read access to
 `refs/meta/config`.
 
-[options="header",width="50%",cols="1,6"]
-|======================================
-|Field Name                   |Description
-|`use_contributor_agreements*`|
+[options="header",width="50%",cols="1,^2,4"]
+|==========================================
+|Field Name                   ||Description
+|`use_contributor_agreements*`|optional|
 link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
 authors must complete a contributor agreement on the site before
 pushing any commits or changes to this project.
-|`use_content_merge*`|
+|`use_content_merge*`         |optional|
 link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
 Gerrit will try to perform a 3-way merge of text file content when a
 file has been modified by both the destination branch and the change
 being submitted. This option only takes effect if submit type is not
 FAST_FORWARD_ONLY.
-|`use_signed_off_by*`|
+|`use_signed_off_by*`         |optional|
 link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
 each change must contain a Signed-off-by line from either the author or
 the uploader in the commit message.
-|`require_change_id*`|
+|`require_change_id*`         |optional|
 link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether a
 valid link:user-changeid.html[Change-Id] footer in any commit uploaded
 for review is required. This does not apply to commits pushed directly
 to a branch or tag.
-|`commentlinks`|
-Comment link configuration for the project. Has the same format as the
-link:config-gerrit.html#_a_id_commentlink_a_section_commentlink[commentlink section]
-of `gerrit.config`.
-|======================================
+|`commentlinks`               ||
+Map with the comment link configurations of the project. The name of
+the comment link configuration is mapped to the comment link
+configuration, which has the same format as the
+link:config-gerrit.html#_a_id_commentlink_a_section_commentlink[
+commentlink section] of `gerrit.config`.
+|`theme`                      |optional|
+The theme that is configured for the project as a link:#theme-info[
+ThemeInfo] entity.
+|==========================================
 
 [[dashboard-info]]
 DashboardInfo
@@ -967,6 +1285,9 @@
 |`require_change_id`         |`INHERIT` if not set|
 Whether the usage of Change-Ids is required for the project (`TRUE`,
 `FALSE`, `INHERIT`).
+|`max_object_size_limit`     |optional|
+Max allowed Git object size for this project.
+Common unit suffixes of 'k', 'm', or 'g' are supported.
 |=========================================
 
 [[project-parent-input]]
@@ -1002,6 +1323,22 @@
 |`size_of_packed_objects`  |Size of packed objects in bytes.
 |======================================
 
+[[theme-info]]
+ThemeInfo
+~~~~~~~~~
+The `ThemeInfo` entity describes a theme.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=============================
+|Field Name      ||Description
+|`css`           |optional|
+The path to the `GerritSite.css` file.
+|`header`        |optional|
+The path to the `GerritSiteHeader.html` file.
+|`footer`        |optional|
+The path to the `GerritSiteFooter.html` file.
+|=============================
+
 
 GERRIT
 ------
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index 303fc4b..b072173 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -5,12 +5,18 @@
 The API is suitable for automated tools to build upon, as well as
 supporting some ad-hoc scripting use cases.
 
+See also: link:dev-rest-api.html[REST API Developers' Notes].
+
 Endpoints
 ---------
+link:rest-api-access.html[/access/]::
+  Access Right related REST endpoints
 link:rest-api-accounts.html[/accounts/]::
   Account related REST endpoints
 link:rest-api-changes.html[/changes/]::
   Change related REST endpoints
+link:rest-api-config.html[/config/]::
+  Config related REST endpoints
 link:rest-api-groups.html[/groups/]::
   Group related REST endpoints
 link:rest-api-projects.html[/projects/]::
diff --git a/Documentation/user-changeid.txt b/Documentation/user-changeid.txt
index a4224bd..c13faa6 100644
--- a/Documentation/user-changeid.txt
+++ b/Documentation/user-changeid.txt
@@ -53,7 +53,7 @@
 
   $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
 
-  $ curl -o .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
+  $ curl -Lo .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 
 Then ensure that the execute bit is set on the hook script:
 
diff --git a/Documentation/user-dashboards.txt b/Documentation/user-dashboards.txt
index 3a12b76..d351259 100644
--- a/Documentation/user-dashboards.txt
+++ b/Documentation/user-dashboards.txt
@@ -44,7 +44,7 @@
 `&` or `;` or `,`
 
 The special `foreach=...` parameter is designed to facilitate
-more easily writting similar queries in a dashboard.  The value of the
+more easily writing similar queries in a dashboard.  The value of the
 foreach parameter will be used in every query in the dashboard by
 appending it to their ends with a space (ANDing it with the queries).
 
diff --git a/Documentation/user-notify.txt b/Documentation/user-notify.txt
index 711d1ac..558dcb5 100644
--- a/Documentation/user-notify.txt
+++ b/Documentation/user-notify.txt
@@ -22,6 +22,14 @@
 change notifications to specific subsets, for example `branch:master`
 to only see changes proposed for the master branch.
 
+Notification mails for new changes and new patch sets are not sent to
+the change owner.
+
+Notification mails for comments added on changes are not sent to the user
+who added the comment unless the user has enabled the 'CC Me On Comments I
+Write' option in the user preferences.
+
+
 Project Level Settings
 ----------------------
 
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 3d72732..d7e9fd7 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -278,7 +278,7 @@
 ----------------
 
 Operator values that are not bare words (roughly A-Z, a-z, 0-9, @,
-hypen, dot and underscore) must be quoted for the query parser.
+hyphen, dot and underscore) must be quoted for the query parser.
 
 Quoting is accepted as either double quotes
 (e.g.  `message:"the value"`) or as matched
@@ -291,8 +291,8 @@
 Unless otherwise specified, operators are joined using the `AND`
 boolean operator, thereby restricting the search results.
 
-Parentheses can be used to force a particular precendence on complex
-operator expressions, otherwise OR has higher precendence than AND.
+Parentheses can be used to force a particular precedence on complex
+operator expressions, otherwise OR has higher precedence than AND.
 
 Negation
 ~~~~~~~~
@@ -311,7 +311,7 @@
 OR
 ~~
 The boolean operator `OR` (in all caps) can be used to find changes
-that match either operator.  This increases the nubmer of results
+that match either operator.  This increases the number of results
 that are returned, as more changes are considered.
 
 
@@ -330,6 +330,12 @@
 * The one or two character abbreviation shown in the column header
   of change list pages.  Example: `label:R` or `label:V`.
 
+* The label name followed by a ',' followed by a reviewer id or a
+  group id.  To make it clear whether a user or group is being looked
+  for, precede the value by a user or group argument identifier
+  ('user=' or 'group=').  If an LDAP group is being referenced make
+  sure to use 'ldap/<groupname>'.
+
 A label name must be followed by a score, or an operator and a score.
 The easiest way to explain this is by example.
 
@@ -356,7 +362,20 @@
 +
 Matches changes with either a +1, +2, or any higher score.
 
-`label:Code-Review<=-1`::
+`label:CodeReview=+2,aname`::
++
+Matches changes with a +2 code review where the reviewer or group is aname.
+
+`label:CodeReview=2,user=jsmith`::
++
+Matches changes with a +2 code review where the reviewer is jsmith.
+
+`label:CodeReview=+1,group=ldap/linux.workflow`::
++
+Matches changes with a +1 code review where the reviewer is in the
+ldap/linux.workflow group.
+
+`label:CodeReview<=-1`::
 +
 Matches changes with either a -1, -2, or any lower score.
 
diff --git a/Documentation/user-signedoffby.txt b/Documentation/user-signedoffby.txt
index d07516a..56858bf 100644
--- a/Documentation/user-signedoffby.txt
+++ b/Documentation/user-signedoffby.txt
@@ -104,10 +104,10 @@
 mergers will sometimes manually convert an acker's "yep, looks good to me"
 into an Acked-by:.
 
-Acked-by: does not necessarily indicate acknowledgement of the entire patch.
+Acked-by: does not necessarily indicate acknowledgment of the entire patch.
 For example, if a patch affects multiple subsystems and has an Acked-by: from
-one subsystem maintainer then this usually indicates acknowledgement of just
-the part which affects that maintainer's code.  Judgement should be used here.
+one subsystem maintainer then this usually indicates acknowledgment of just
+the part which affects that maintainer's code.  Judgment should be used here.
 When in doubt people should refer to the original discussion in the mailing
 list archives.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.0.12.txt b/ReleaseNotes/ReleaseNotes-2.0.12.txt
index bc3d90d..c82bab0 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.12.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.12.txt
@@ -110,7 +110,7 @@
 * Delete dead CSS bundle code
 * Always use NpTextBox or NpTextArea to prevent GlobalKe...
 * Detect cases where system_config has too many rows
-* Remove unnecessary warning supressions
+* Remove unnecessary warning suppressions
 * Remove dead code, these aren't used anymore
 * Fix warnings about potential serialization problems
 * Fix warning about debug code in OpenIdServiceImpl
diff --git a/ReleaseNotes/ReleaseNotes-2.0.18.txt b/ReleaseNotes/ReleaseNotes-2.0.18.txt
index 8c3e44b..303690c 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.18.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.18.txt
@@ -117,7 +117,7 @@
 ---------
 * GERRIT-5    Remove PatchSetInfo from database and get it always fr...
 +
-A very, very old bug.  We no longer mirror the commit data into the SQL database, but instead pull it directly from Git when needed.  Removing duplicated data simplifies the data store model, something that is important as we shift from a SQL database to a Git backed database.
+A very, very old bug.  We no longer mirror the commit data into the SQL database, but instead pull it directly from Git when needed.  Removing duplicated data simplifies the data store model, something that is important as we shift from an SQL database to a Git backed database.
 
 * GERRIT-220  Fix infinite loop in PatchScriptBuilder
 +
@@ -314,4 +314,4 @@
 * Cleanup display of external ids in the user settings
 * Preserve negative approvals when replacing patch sets
 * Use gwtjsonrpc 1.1.1
-* gerrit 2.0.18
\ No newline at end of file
+* gerrit 2.0.18
diff --git a/ReleaseNotes/ReleaseNotes-2.0.19.txt b/ReleaseNotes/ReleaseNotes-2.0.19.txt
index 40dd390..b6a731b4 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.19.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.19.txt
@@ -149,7 +149,7 @@
 Uploading commits to a project now requires that the new commits
 share a common ancestry with the existing commits of that project.
 This catches and prevents problems caused by a user making a typo 
-in the project name, and inadverently selecting the wrong project.
+in the project name, and inadvertently selecting the wrong project.
 
 * Change-Id tags in commit messages to associate commits
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.0.21.txt b/ReleaseNotes/ReleaseNotes-2.0.21.txt
index 47ba654..053ec89 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.21.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.21.txt
@@ -190,7 +190,7 @@
 * Display abbreviated hexy Change-Id in screen titles
 * Use hexy Change-Id in emails sent from Gerrit
 +
-Change-Id abbreviations are now used throught more of the UI,
+Change-Id abbreviations are now used through more of the UI,
 including emails sent by Gerrit and window/page titles.  This
 change breaks email threading for any existing review emails.
 That is comments on a change created before the upgrade will
diff --git a/ReleaseNotes/ReleaseNotes-2.0.22.txt b/ReleaseNotes/ReleaseNotes-2.0.22.txt
index d27fcff..ae6d7dd 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.22.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.22.txt
@@ -60,7 +60,7 @@
 The following properties may now be configured from LDAP using
 more complex expressions: accountFullName, accountEmailAddress,
 accountSshUserName.  Property expressions permit forcing
-to a lowercase string, or performing string concentation.
+to a lowercase string, or performing string concatenation.
 These features may help some environments to better integrate
 with their local LDAP server.
 
@@ -118,7 +118,7 @@
 oddball characters like '\0' and '/'.  We really want them to
 be a restricted subset which won't cause errors when we try to
 map SSH usernames as file names in a Git repository as we try
-to move away from a SQL database.
+to move away from an SQL database.
 
 * GERRIT-282  Fix reply to comment on left side
 +
@@ -157,4 +157,4 @@
 * GERRIT-67   Wait for dependencies to submit before claiming merge ...
 * Move abandonChange to ChangeManageService
 * Remove trailing whitespace in install.txt
-* Gerrit 2.0.22
\ No newline at end of file
+* Gerrit 2.0.22
diff --git a/ReleaseNotes/ReleaseNotes-2.0.5.txt b/ReleaseNotes/ReleaseNotes-2.0.5.txt
index a17d1c5..78389fc 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.5.txt
@@ -52,7 +52,7 @@
 * Show the Web Identities panel when on HTTP authentication
 * Relabel the "Web Identities" tab as just "Identities
 * Use an &nbsp; when showing an empty cell in the identity...
-* Simplify the Gerrit install from source procedue to avoi...
+* Simplify the Gerrit install from source procedure to avoi...
 * Support -DgwtStyle=DETAILED to support browser debugging
 * Don't link to JIRA in our docs, link to our issues page
 * Use &nbsp; in the identities table email column when emp...
diff --git a/ReleaseNotes/ReleaseNotes-2.1.1.txt b/ReleaseNotes/ReleaseNotes-2.1.1.txt
index 5d14b1e..5f02146 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.1.txt
@@ -102,8 +102,8 @@
 
 * Use a glass pane behind our dialogs, make most modal
 +
-Error dialogs are now more noticable, and less easily dismissed
-by an accidential click.  This is especially useful when there
+Error dialogs are now more noticeable, and less easily dismissed
+by an accidental click.  This is especially useful when there
 is a merge error during submit.
 
 Bug Fixes
diff --git a/ReleaseNotes/ReleaseNotes-2.1.10.txt b/ReleaseNotes/ReleaseNotes-2.1.10.txt
new file mode 100644
index 0000000..9d43bf4
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.1.10.txt
@@ -0,0 +1,13 @@
+Release notes for Gerrit 2.1.10
+===============================
+
+There are no schema changes from link:ReleaseNotes-2.1.9.html[2.1.9].
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.1.10.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.1.10.war]
+
+Bug Fixes
+---------
+* Fix clone for modern Git clients
++
+The security fix in 2.1.9 broke clone for recent Git clients,
+throwing an ArrayIndexOutOfBoundsException. Fixed.
diff --git a/ReleaseNotes/ReleaseNotes-2.1.2.txt b/ReleaseNotes/ReleaseNotes-2.1.2.txt
index 8652db7..ca69e86 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.2.txt
@@ -195,7 +195,7 @@
 * issue 392 Make hooks/commit-msg available over HTTP
 +
 The scp filesystem holding client side tools and hooks is now
-avaliable over `http://review.example.com/tools/'$name'`.  User
+available over `http://review.example.com/tools/'$name'`.  User
 documentation is updated with example URLs.
 
 * issue 470 Allow /r/I... URLs
@@ -252,7 +252,7 @@
 +
 Additional public keys for the magical 'Gerrit Code Review' user may
 be specified in an OpenSSH authorized_keys style file and are
-functionally equivilent to authenticating with the daemon's host key.
+functionally equivalent to authenticating with the daemon's host key.
 The keys are primarily intended to be other daemons, most likely
 slaves, that share the same set of repositories and database.
 
@@ -434,7 +434,7 @@
 * issue 466 Reject pushing to invalid reference names
 +
 Gerrit allowed the invalid `HEAD:/refs/for/master` push refspec
-to actaully create the branch `refs/heads/refs/for/master`, which
+to actually create the branch `refs/heads/refs/for/master`, which
 confused any other client trying to push.  Fixed.
 
 * issue 485 Trim the username before requesting authentication
@@ -467,7 +467,7 @@
 
 * issue 451 gerrit.sh: Wait until the daemon is serving requests
 +
-The gerrit.sh script now waits until the deamon is actually running
+The gerrit.sh script now waits until the daemon is actually running
 and able to serve requests before returning to the caller with a
 successful exit status code.  This makes it easier to then start up
 dependent tasks that need the server to be ready before they can run.
diff --git a/ReleaseNotes/ReleaseNotes-2.1.4.txt b/ReleaseNotes/ReleaseNotes-2.1.4.txt
index d4cf741..639229a 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.4.txt
@@ -21,7 +21,7 @@
 
 * issue 504 Implement full query operators
 +
-The search box now implments a wide range of operators and boolean
+The search box now implements a wide range of operators and boolean
 expressions, permitting complex queries such as `is:open CodeReview>=1
 (has:draft OR is:starred)` to locate open changes that have been code
 reviewed, but still have unpublished drafts or were starred by the
diff --git a/ReleaseNotes/ReleaseNotes-2.1.5.txt b/ReleaseNotes/ReleaseNotes-2.1.5.txt
index c87dc42..b55aedf 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.5.txt
@@ -5,7 +5,7 @@
 
 link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.5.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.5.war]
 
-This is primarly a bug fix release to 2.1.4, but some additional
+This is primarily a bug fix release to 2.1.4, but some additional
 new features were included so its named 2.1.5 rather than 2.1.4.1.
 
 Upgrade Instructions
@@ -156,7 +156,7 @@
 if no new topic was supplied.  If a new topic is supplied, it is
 changed to match the new topic given.
 
-* Allow ; and & to seperate parameters in gitweb
+* Allow ; and & to separate parameters in gitweb
 +
 gitweb.cgi accepts either ';' or '&' between parameters, but
 Gerrit Code Review was only accepting the ';' syntax.  Fixed
diff --git a/ReleaseNotes/ReleaseNotes-2.1.6.txt b/ReleaseNotes/ReleaseNotes-2.1.6.txt
index 198a064..d1c6335 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.6.txt
@@ -89,7 +89,7 @@
 embedded Jetty web server with SSL enabled, and an LDAP directory to
 lookup individual account information.
 
-* issue 503 Inactive acounts may be disabled.
+* issue 503 Inactive accounts may be disabled.
 +
 Administrators can manually update the accounts table, setting
 inactive = `Y` to mark user accounts inactive.  Inactive accounts
diff --git a/ReleaseNotes/ReleaseNotes-2.1.7.2.txt b/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
index 9172b4a..802f1c7 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
@@ -25,7 +25,7 @@
 * Fix API breakage on ChangeDetailService
 +
 Version 2.1.7 broke the Gerrit Code Review plugin for Mylyn Reviews
-due to an accidential signature change of one of the remote JSON
+due to an accidental signature change of one of the remote JSON
 APIs. The ChangeDetailService.patchSetDetail() method is back to the
 old signature and a new patchSetDetail2() method has been added to
 handle the newer calling convention used in some contexts of the
diff --git a/ReleaseNotes/ReleaseNotes-2.1.7.txt b/ReleaseNotes/ReleaseNotes-2.1.7.txt
index 0dba5f9..b12713c 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.7.txt
@@ -71,7 +71,7 @@
 that differ between two patch sets. This new feature can speed up
 re-reviewing a change.
 
-* issue 913 Support different color pallete when not signed in
+* issue 913 Support different color palette when not signed in
 +
 Site administrators can configure a different theme in gerrit.config for
 the signed-in and signed-out states, making it more obvious to site users
@@ -328,7 +328,7 @@
 
 * Reject invalid Change-Id lines
 +
-Severly malformed Change-Id lines were previously accepted by the
+Severely malformed Change-Id lines were previously accepted by the
 server. These are now rejected.
 
 * Fix error message returned on push to closed change
@@ -362,7 +362,7 @@
 * issue 814 Evict initial members of group created by SSH
 * issue 879 Fix replication of initial empty commit in new project
 * Disallow setting a project as parent for itself
-* Autoamtically create user account(s) as necessary
+* Automatically create user account(s) as necessary
 * Move SSH command creation off NioProcessor threads
 
 Administration
diff --git a/ReleaseNotes/ReleaseNotes-2.1.9.txt b/ReleaseNotes/ReleaseNotes-2.1.9.txt
new file mode 100644
index 0000000..728d7cc
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.1.9.txt
@@ -0,0 +1,16 @@
+Release notes for Gerrit 2.1.9
+==============================
+
+There are no schema changes from link:ReleaseNotes-2.1.8.html[2.1.8].
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.1.9.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.1.9.war]
+
+Bug Fixes
+---------
+* Patch JGit security hole
++
+The security hole may permit a modified Git client to gain access
+to hidden or deleted branches if the user has read permission on
+at least one branch in the repository. Access requires knowing a
+SHA-1 to request, which may be discovered out-of-band from an issue
+tracker or gitweb instance.
diff --git a/ReleaseNotes/ReleaseNotes-2.2.1.txt b/ReleaseNotes/ReleaseNotes-2.2.1.txt
index 19af06d..ff8040e 100644
--- a/ReleaseNotes/ReleaseNotes-2.2.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.2.1.txt
@@ -35,7 +35,7 @@
 The name "-- All Projects --.git" is difficult to work with on
 the UNIX command line, due to tools assuming the name is actually
 part of a long option. The project has been renamed to remove these
-leading hypens, and remove spaces, making it more friendly to work
+leading hyphens, and remove spaces, making it more friendly to work
 with on the command line.
 
 * issue 997 Resolve Project Owners when checking access rights
@@ -66,7 +66,7 @@
 * Fix API breakage on ChangeDetailService
 +
 Version 2.1.7 broke the Gerrit Code Review plugin for Mylyn Reviews
-due to an accidential signature change of one of the remote JSON
+due to an accidental signature change of one of the remote JSON
 APIs. The ChangeDetailService.patchSetDetail() method is back to the
 old signature and a new patchSetDetail2() method has been added to
 handle the newer calling convention used in some contexts of the
diff --git a/ReleaseNotes/ReleaseNotes-2.2.2.2.txt b/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
index db5d750..f7c299e 100644
--- a/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
@@ -21,4 +21,4 @@
 section name as All-Projects. This is an unlikely scenario for
 most servers, as Gerrit does not normally set inheritFrom equal to
 All-Projects. The usual behavior is to not supply this property in
-project.config, and permit the implicit inheritence to take place.
+project.config, and permit the implicit inheritance to take place.
diff --git a/ReleaseNotes/ReleaseNotes-2.2.2.txt b/ReleaseNotes/ReleaseNotes-2.2.2.txt
index ddfe323..2c14fa1 100644
--- a/ReleaseNotes/ReleaseNotes-2.2.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.2.2.txt
@@ -172,7 +172,7 @@
 
 * Add a "Save" button to the PatchScriptSettingsPanel
 +
-The "Update" button now only updates the display.  Addittionally,
+The "Update" button now only updates the display.  Additionally,
 for logged in users, a "Save" button now behaves the way that
 "Update" used to behave for logged in users.
 
@@ -427,9 +427,9 @@
 * Refactor how permissions are matched by ProjectControl, RefControl
 +
 More aggressively cache many of the auth objects at a cost of memory,
-but this should be an improvement in response timse.
+but this should be an improvement in response times.
 
-* Substantialy speed up pushing changes for review
+* Substantially speed up pushing changes for review
 +
 Pushing a new change for review checks if the change is related to
 the branch it's destined for. It used to do this in a way that
diff --git a/ReleaseNotes/ReleaseNotes-2.3.1.txt b/ReleaseNotes/ReleaseNotes-2.3.1.txt
index 324a3c1..c37fbe4 100644
--- a/ReleaseNotes/ReleaseNotes-2.3.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.3.1.txt
@@ -21,4 +21,4 @@
 section name as All-Projects. This is an unlikely scenario for
 most servers, as Gerrit does not normally set inheritFrom equal to
 All-Projects. The usual behavior is to not supply this property in
-project.config, and permit the implicit inheritence to take place.
+project.config, and permit the implicit inheritance to take place.
diff --git a/ReleaseNotes/ReleaseNotes-2.3.txt b/ReleaseNotes/ReleaseNotes-2.3.txt
index 965c8e3..cf371c8 100644
--- a/ReleaseNotes/ReleaseNotes-2.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.3.txt
@@ -220,7 +220,7 @@
 This helps prevent a very slow LDAP server from blocking
 all SSH command creation threads.
 
-* Introduce a git maxObjectSizeLimit in the [recieve] config
+* Introduce a git maxObjectSizeLimit in the [receive] config
 +
 This limits the size of uploaded files
 
@@ -294,7 +294,7 @@
 
 * Allow Realm to participate when linking an account identity
 +
-When linking a new user identity to an exisiting account, permit the
+When linking a new user identity to an existing 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.
@@ -401,7 +401,7 @@
 If a project inherits from a non existing parent, prevent a
 StringIndexOutOfBoundsException.
 
-* Fix: Supress "Error on refs/cache-automerge" warnings.
+* Fix: Suppress "Error on refs/cache-automerge" warnings.
 
 * Don't allow registering for cleanup after cleanup runs
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.4.2.txt b/ReleaseNotes/ReleaseNotes-2.4.2.txt
index afa1d96..9ae9fd7 100644
--- a/ReleaseNotes/ReleaseNotes-2.4.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.4.2.txt
@@ -21,4 +21,4 @@
 section name as All-Projects. This is an unlikely scenario for
 most servers, as Gerrit does not normally set inheritFrom equal to
 All-Projects. The usual behavior is to not supply this property in
-project.config, and permit the implicit inheritence to take place.
+project.config, and permit the implicit inheritance to take place.
diff --git a/ReleaseNotes/ReleaseNotes-2.4.3.txt b/ReleaseNotes/ReleaseNotes-2.4.3.txt
new file mode 100644
index 0000000..c9c2d2c
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.4.3.txt
@@ -0,0 +1,16 @@
+Release notes for Gerrit 2.4.3
+==============================
+
+There are no schema changes from link:ReleaseNotes-2.4.2.html[2.4.2].
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.4.3.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.4.3.war]
+
+Bug Fixes
+---------
+* Patch JGit security hole
++
+The security hole may permit a modified Git client to gain access
+to hidden or deleted branches if the user has read permission on
+at least one branch in the repository. Access requires knowing a
+SHA-1 to request, which may be discovered out-of-band from an issue
+tracker or gitweb instance.
diff --git a/ReleaseNotes/ReleaseNotes-2.4.4.txt b/ReleaseNotes/ReleaseNotes-2.4.4.txt
new file mode 100644
index 0000000..de9e2cb
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.4.4.txt
@@ -0,0 +1,13 @@
+Release notes for Gerrit 2.4.4
+==============================
+
+There are no schema changes from link:ReleaseNotes-2.4.4.html[2.4.4].
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.4.4.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.4.4.war]
+
+Bug Fixes
+---------
+* Fix clone for modern Git clients
++
+The security fix in 2.4.3 broke clone for recent Git clients,
+throwing an ArrayIndexOutOfBoundsException. Fixed.
diff --git a/ReleaseNotes/ReleaseNotes-2.4.txt b/ReleaseNotes/ReleaseNotes-2.4.txt
index 82f3ed4..6f10c0b 100644
--- a/ReleaseNotes/ReleaseNotes-2.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.4.txt
@@ -131,7 +131,7 @@
 'refs/heads/' prefix). Change the branch operator so that it also
 supports full branch names as value.
 +
-It is intuive that searching with 'branch:master' and searching with
+It is intuitive that searching with 'branch:master' and searching with
 'branch:refs/for/master' deliver the same result. So far
 'branch:refs/for/master' was the same as searching with
 'refs:refs/heads/refs/heads/master' which is unexpected for most users.
@@ -154,7 +154,7 @@
 * issue 1272 Add scripts to create release notes from git log
 +
 These script generates a list of commits from git log between two given commits
-and outputs the asciidoc format containting list of commits subject and body.
+and outputs the asciidoc format containing list of commits subject and body.
 
 * Update URL for m2eclipse
 +
@@ -223,7 +223,7 @@
 
 * issue 1353 Fix case check for project name so that symlinks work again
 * Fix merging of access sections
-* Fix inconsistent behaviour when replicating refs/meta/config
+* Fix inconsistent behavior when replicating refs/meta/config
 * Fix duplicated results on status:open project:P branch:B
 
 Documentation
diff --git a/ReleaseNotes/ReleaseNotes-2.5.2.txt b/ReleaseNotes/ReleaseNotes-2.5.2.txt
index 08b4f86..f87328e 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.2.txt
@@ -124,9 +124,9 @@
 the 'Uploading Changes' documentation was broken.
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=1569[issue 1569]:
-Fix unexpected behaviour in the commit-msg hook caused by `GREP_OPTIONS`
+Fix unexpected behavior in the commit-msg hook caused by `GREP_OPTIONS`
 +
-If `GREP_OPTIONS` was set, it caused unexpected behaviour in the
+If `GREP_OPTIONS` was set, it caused unexpected behavior in the
 commit-msg hook.  For example if it included a setting like
 `--exclude=".git/*"` it caused a new `Change-Id` line to be appended
 to the commit message on every amend.
diff --git a/ReleaseNotes/ReleaseNotes-2.5.5.txt b/ReleaseNotes/ReleaseNotes-2.5.5.txt
new file mode 100644
index 0000000..57b6d24
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.5.5.txt
@@ -0,0 +1,16 @@
+Release notes for Gerrit 2.5.5
+==============================
+
+There are no schema changes from link:ReleaseNotes-2.5.4.html[2.5.4].
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.5.5.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.5.5.war]
+
+Bug Fixes
+---------
+* Patch JGit security hole
++
+The security hole may permit a modified Git client to gain access
+to hidden or deleted branches if the user has read permission on
+at least one branch in the repository. Access requires knowing a
+SHA-1 to request, which may be discovered out-of-band from an issue
+tracker or gitweb instance.
diff --git a/ReleaseNotes/ReleaseNotes-2.5.6.txt b/ReleaseNotes/ReleaseNotes-2.5.6.txt
new file mode 100644
index 0000000..2e9e888
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.5.6.txt
@@ -0,0 +1,13 @@
+Release notes for Gerrit 2.5.6
+==============================
+
+There are no schema changes from link:ReleaseNotes-2.5.6.html[2.5.6].
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.5.6.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.5.6.war]
+
+Bug Fixes
+---------
+* Fix clone for modern Git clients
++
+The security fix in 2.5.4 broke clone for recent Git clients,
+throwing an ArrayIndexOutOfBoundsException. Fixed.
diff --git a/ReleaseNotes/ReleaseNotes-2.5.txt b/ReleaseNotes/ReleaseNotes-2.5.txt
index 023b4bf..cdef554 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.txt
@@ -80,7 +80,7 @@
 configuration variables are now expressed in bytes of memory used,
 rather than number of entries in the cache. This is a change from
 previous versions of Gerrit and gives administrators more control over
-how memory is partioned within a server. Admins that set this variable
+how memory is partitioned within a server. Admins that set this variable
 must update their configurations, as the old values are too small.
 For example a setting of `memoryLimit = 1024` now means only 1 KiB of
 data (which may not even hold 1 patch set), not 1024 patch sets.  It
@@ -1014,7 +1014,7 @@
 code wants to use one of these caches during startup to resolve a
 group or project by name.
 +
-Tracking the Gauva backend caches with a DynamicMap makes it possible
+Tracking the Guava backend caches with a DynamicMap makes it possible
 for plugins to define their own in-memory caches using CacheModule's
 cache() function to declare the cache. It allows the core server to
 make the cache available to administrators over SSH with the gerrit
@@ -1129,7 +1129,7 @@
 * Add change topic in hook arguments
 +
 It was not possible for hook scripts to include topic-specific
-behaviour because the topic name was not included in the arguments.
+behavior because the topic name was not included in the arguments.
 
 * Add `--is-draft` argument on `patchset-created` hook
 +
@@ -1202,7 +1202,7 @@
 
 * Set `GERRIT_SITE` in Gerrit hooks as environment variable
 +
-Allows development of hooks parametrised on Gerrit location. This can
+Allows development of hooks parameterized on Gerrit location. This can
 be useful to allow hooks to load the Gerrit configuration when needed
 (from `$GERRIT_SITE`) or even store their additional config files under
 `$GERRIT_SITE/etc` and retrieve them at startup.
@@ -1210,7 +1210,7 @@
 * Add an exponentially rolling garbage collection script
 +
 `git-exproll.sh` is a git garbage collection script aimed specifically
-at reducing exccessive garbage collection and particularly large
+at reducing excessive garbage collection and particularly large
 packfile churn for Gerrit installations.
 +
 Excessive garbage collection on "dormant" repos is wasteful of both CPU
@@ -1275,7 +1275,7 @@
 just the list of groups.
 +
 Now the amount of data that needs to be downloaded to the browser is
-reduced by using the more leightweight `AccountGroup` type instead of
+reduced by using the more lightweight `AccountGroup` type instead of
 the `GroupDetail` type when showing the groups in a list format. As a
 consequence the `Owners` column that showed the name of the owner group
 had been dropped.
@@ -1305,9 +1305,9 @@
 used in the `refs/for/` push line.
 +
 Catch existing changes by looking for their exact commit SHA-1, rather
-than complete ancestory. This should have roughly the same outcome for
+than complete ancestry. This should have roughly the same outcome for
 anyone pushing a new commit on top of an existing open change, but
-with lower computional cost at the server.
+with lower computational cost at the server.
 
 * Lookup changes in parallel during `ReceiveCommits`
 +
@@ -1408,7 +1408,7 @@
 * Use gwtexpui 1.2.6
 +
 ** Hide superfluous status text from clippy flash widget
-** Fix diappearance of text in CopyableLabel when clicking on it
+** Fix disappearance of text in CopyableLabel when clicking on it
 
 * Update Guava to 12.0.1
 +
@@ -1436,7 +1436,7 @@
 is assigned) are able to edit the access control list of the projects
 they own. Hence being owner of the `All-Projects` project would allow
 to edit the global capabilities and assign the `administrateServer`
-capabilitiy without being Gerrit administrator.
+capability without being Gerrit administrator.
 +
 In earlier Gerrit versions (2.1.x) it was already implemented like
 this but the corresponding checks got lost.
@@ -1503,7 +1503,7 @@
 
 * Dependencies were lost in the ChangeScreen's "Needed By" table
 +
-Older patchsets are now iterated for decendents, so that the dependency
+Older patchsets are now iterated for descendants, so that the dependency
 chain does not break on new upstream patchsets.
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=1442[issue 1442]:
@@ -1712,7 +1712,7 @@
 
 * Fix the `export-review-notes` command's Guice bindings
 +
-The `export-review-notes` command was broken becasue of the CachePool
+The `export-review-notes` command was broken because of the CachePool
 class being bound twice. The startup of the command failed because of
 that.
 
@@ -1751,7 +1751,7 @@
 Replication
 ~~~~~~~~~~~
 
-* Fix inconsistent behaviour when replicating `refs/meta/config`
+* Fix inconsistent behavior when replicating `refs/meta/config`
 +
 In `replication.config`, if `authGroup` is set to be used together with
 `mirror = true`, refs blocked through the `authGroup` are deleted from
diff --git a/ReleaseNotes/ReleaseNotes-2.6.1.txt b/ReleaseNotes/ReleaseNotes-2.6.1.txt
new file mode 100644
index 0000000..e163b43d
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.6.1.txt
@@ -0,0 +1,16 @@
+Release notes for Gerrit 2.6.1
+==============================
+
+There are no schema changes from link:ReleaseNotes-2.6.html[2.6].
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.6.1.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.6.1.war]
+
+Bug Fixes
+---------
+* Patch JGit security hole
++
+The security hole may permit a modified Git client to gain access
+to hidden or deleted branches if the user has read permission on
+at least one branch in the repository. Access requires knowing a
+SHA-1 to request, which may be discovered out-of-band from an issue
+tracker or gitweb instance.
diff --git a/ReleaseNotes/ReleaseNotes-2.6.txt b/ReleaseNotes/ReleaseNotes-2.6.txt
index 0c21182..a000a62 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.txt
@@ -3,7 +3,7 @@
 
 Gerrit 2.6 is now available:
 
-link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.6.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.6.war]
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.6.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.6.war]
 
 Gerrit 2.6 includes the bug fixes done with
 link:ReleaseNotes-2.5.1.html[Gerrit 2.5.1],
@@ -439,7 +439,7 @@
 +
 Enable this on the REST API responses because we sometimes send back
 text/plain results that are really just plain text. Existing JSON
-responses are protected from accidential sniffing and treatment as
+responses are protected from accidental sniffing and treatment as
 HTML thanks to Gson encoding HTML control characters using Unicode
 character escapes within JSON strings.
 
@@ -870,13 +870,13 @@
 * PatchSet.isRef()-optimizations.
 +
 PatchSet.isRef() is used extensively when preparing for a ref
-advertisment and the regular expression used by isRefs() was notably
+advertisement and the regular expression used by isRefs() was notably
 costly in these circumstances, especially since it could not be
 pre-compiled.
 +
 The regular expression is removed and the check is now directly
 implemented. As result the performance of `git ls-remote` could be
-increased upto 15%.
+increased by up to 15%.
 
 * New config option `receive.checkReferencedObjectsAreReachable`
 +
@@ -898,11 +898,11 @@
 
 * Caching of changes
 +
-During Ref Advertisments (via VisibleRefFilter), all changes need to
+During Ref Advertisements (via VisibleRefFilter), all changes need to
 be fetched from the database to allow Gerrit to figure out which change
 refs are visible and should be advertised to the user. To reduce
 database traffic a cache for changes was introduced. This cache is
-disabled by default since it can mess-up multi-server setups.
+disabled by default since it can mess up multi-server setups.
 
 Misc
 ~~~~
@@ -1140,13 +1140,13 @@
 +
 ** Sometimes a second comment editor was shown instead of using the
    existing comment editor.
-** Fix doublicated border line between comments.
+** Fix duplicated border line between comments.
 ** Sometimes the parts of the border were missing when a comment was
    expanded.
 ** Fix displaying the blue line that marks the current line when there
    are several published comments.
 ** Sometimes on discard of a comment some frames of the comment editor
-   stayed and some border lines of neighbour comments disappeared.
+   stayed and some border lines of neighbor comments disappeared.
 
 * In diff view don't let arrow column accept clicks.
 
@@ -1178,7 +1178,7 @@
 +
 The provided suggestions should highlight the part that the user has
 already typed as bold text. This only worked for the first operator.
-For suggestions of any further operator no hightlighting was done.
+For suggestions of any further operator no highlighting was done.
 
 * Fix style of hint text in search box on initial page load
 +
@@ -1335,7 +1335,7 @@
 branch doesn't exist anymore.
 +
 In addition Gerrit was writing an error into the error log if a change
-couldn't be merged because the destination branch wass missing.
+couldn't be merged because the destination branch was missing.
 That was not really a server error and is not logged anymore.
 
 * Fix NPE when pushing a patch with an invalid author with
@@ -1612,7 +1612,7 @@
 servlet API in a new `WEB-INF/pgm-lib` directory.
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1822[Issue 1822]:
-  Verify session matches container authentiation header
+  Verify session matches container authentication header
 +
 If the user alters their identity in the container invalidate
 the Gerrit user session and force a new one to begin.
diff --git a/ReleaseNotes/ReleaseNotes-2.7.txt b/ReleaseNotes/ReleaseNotes-2.7.txt
index 0e76f61..397a959 100644
--- a/ReleaseNotes/ReleaseNotes-2.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.7.txt
@@ -4,8 +4,7 @@
 
 Gerrit 2.7 is now available:
 
-link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.7.war[
-http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.7.war]
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.7-rc2.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.7-rc2.war]
 
 Gerrit 2.7 includes the bug fixes done with
 link:ReleaseNotes-2.6.1.html[Gerrit 2.6.1] and
diff --git a/ReleaseNotes/ReleaseNotes-2.8.txt b/ReleaseNotes/ReleaseNotes-2.8.txt
new file mode 100644
index 0000000..4a5685c
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.8.txt
@@ -0,0 +1,406 @@
+Release notes for Gerrit 2.8
+============================
+
+
+Gerrit 2.8 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.8.war[
+http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.8.war]
+
+
+Schema Change
+-------------
+
+
+There is no schema change from link:ReleaseNotes-2.7.html[Gerrit 2.7].
+
+
+Release Highlights
+------------------
+
+
+* Lots of new link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api.html[
+REST API endpoints].
+
+* New build system using link:http://facebook.github.io/buck/[Facebook Buck].
+
+
+New Features
+------------
+
+
+Configuration
+~~~~~~~~~~~~~
+
+* Project owners can define `receive.maxObjectSizeLimit` in the
+link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/config-gerrit.html#receive.maxObjectSizeLimit[
+project configuration] to further reduce the global setting.
+
+* Site administrators can define a
+link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/config-mail.html#_footer_vm[
+footer template] that will be appended to the end of all outgoing emails after
+the 'ChangeFooter' and 'CommentFooter'.
+
+* New `topic-changed` hook and stream event is fired when a change's topic is
+edited from the Web UI or via a REST API.
+
+
+Web UI
+~~~~~~
+
+
+Global
+^^^^^^
+
+* The change status is shown in a separate column on dashboards and search results.
+
+Change Screens
+^^^^^^^^^^^^^^
+
+
+* New button to cherry-pick the change to another branch.
+
+* When issuing a rebase via the Web UI, the committer is now the logged in
+  user, rather than "Gerrit Code Review".
++
+If the user has more than one email address, the preferred email address will
+be used.
+
+* Default user's full name to git committer name if user has not configured a
+full name in their profile.
+
+* Include comment author attributes in comment panels.
++
+Comment author's email address and name are included as attributes in comment
+panels.  This makes it easier to filter out CI-based comments using user
+scripts.
+
+
+REST API
+~~~~~~~~
+
+* Several new link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api.html[
+REST API endpoints] are added.
+
+* REST views can determine how long their response should be cached.
+
+Access Rights
+^^^^^^^^^^^^^
+
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-access.html#list-access[
+List access rights for project(s)]
+
+Accounts
+^^^^^^^^
+
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#create-account[
+Create account]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#get-account-name[
+Get account full name]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#set-account-name[
+Set account full name]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#delete-account-name[
+Delete account full name]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#list-account-emails[
+List account email addresses]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#get-account-email[
+Get account email address]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#set-preferred-email[
+Set account preferred email address]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#create-account-email[
+Create account email]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#delete-account-email[
+Delete account email]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#get-active[
+Get account state]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#set-active[
+Set account state to active]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#delete-active[
+Set account state to inactive]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#get-http-password[
+Get account HTTP password]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#set-http-password[
+Set or generate account HTTP password]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#delete-http-password[
+Delete account HTTP password]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#list-ssh-keys[
+List account SSH keys]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#get-ssh-key[
+Get account SSH key]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#add-ssh-key[
+Add account SSH key]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#delete-ssh-key[
+Delete account SSH key]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-accounts.html#get-username[
+Get account username]
+
+Changes
+^^^^^^^
+
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-changes.html#rebase-change[
+Rebase change]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-changes.html#cherry-pick[
+Cherry-pick revision]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-changes.html#get-content[
+Get content of a file in a revision]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-changes.html#get-patch[
+Get revision as a formatted patch]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-changes.html#get-diff[
+Get diff of a file in a revision]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-changes.html#get-commit[
+Get parsed commit of a revision]
+
+
+Config
+^^^^^^
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-config.html#get-capabilities[
+Get capabilities]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-config.html#get-version[
+Get version] (of the Gerrit server)
+
+
+Projects
+^^^^^^^^
+
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-projects.html#list-branches[
+List branches]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-projects.html#get-branch[
+Get branch]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-projects.html#create-branch[
+Create branch]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-projects.html#delete-branch[
+Delete branch]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-projects.html#list-child-projects[
+List child projects]
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-projects.html#get-child-project[
+Get child project]
+
+
+Capabilities
+~~~~~~~~~~~~
+
+
+New global capabilities are added.
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/access_control.html#capability_generateHttpPassword[
+Generate Http Password] allows non-administrator users to generate HTTP
+passwords for users other than themselves.
++
+This capability would typically be assigned to a non-interactive group
+to be able to generate HTTP passwords for users from a tool or web service
+that uses the Gerrit REST API.
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/access_control.html#capability_runAs[
+Run As] allows users to impersonate other users by setting the `X-Gerrit-RunAs`
+HTTP header on REST API calls.
++
+Site administrators do not inherit this capability;  it must be granted
+explicitly.
+
+
+Emails
+~~~~~~
+
+* The `RebasedPatchSet` template is removed.  Email notifications for rebased
+changes are now sent with the `ReplacePatchSet` template.
+
+
+Plugins
+~~~~~~~
+
+
+Global
+^^^^^^
+
+
+* Plugins may now contribute buttons to various parts of the UI.
+
+* Plugins may now provide an 'About' section on their documentation index page.
+
+
+Commit Message Length Checker
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+* Commits whose subject or body length exceeds the limit can be rejected.
+
+Replication
+^^^^^^^^^^^
+
+* The `{$name}` placeholder is optional when replicating a single project,
+allowing a single project to be replicated under a different name.
+
+* Projects can be matched with wildcard or regex patterns in replication.config.
+
+ssh
+~~~
+
+
+* The `commit-msg` hook installation command is now
+link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/config_gerrit.html#gerrit.installCommitMsgHookCommand[
+configurable].
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/cmd-ls-members.html[
+New `ls-members` command].
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/cmd-set-members.html[
+New `set-members` command].
++
+New command to manipulate group membership. Members can be added or removed
+and groups can be included or excluded in one specific group or number of groups.
+
+
+Daemon
+~~~~~~
+
+
+* Add `--init` option to Daemon to initialize site on daemon start.
++
+The `--init` option will also upgrade an already existing site and is processed in
+non-interactive (batch) mode.
+
+
+Bug Fixes
+---------
+
+
+Configuration
+~~~~~~~~~~~~~
+
+
+* Do not persist default project state in `project.config`.
+
+* Honor `gerrit.cannonicalWebUrl` when opening browser after init.
+
+* Fix 'query disabled' error when Query Limit is set.
+
+Web UI
+~~~~~~
+
+
+Global
+^^^^^^
+
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1574[Issue 1574]:
+Correctly highlight matches of text in escaped HTML entities in suggestion results.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1996[Issue 1996]:
+The "Keyboard Shortcuts" help popup can be closed by pressing the Escape key.
+
+
+Change Screens
+^^^^^^^^^^^^^^
+
+
+* Default review comment visibility is changed to expand all recent.
++
+By default all comments within the last week are expanded, rather than
+only the most recent.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1814[Issue 1814]:
+Sort labels alphabetically by name in the approval table.
+
+Project Screens
+^^^^^^^^^^^^^^^
+
+
+* Only enable the delete branch button when branches are selected.
+
+* Disable the delete branch button while branch deletion requests are
+still being processed.
+
+User Profile Screens
+^^^^^^^^^^^^^^^^^^^^
+
+
+* The preferred email address field is shown as empty if the user has no
+preferred email address.
+
+
+REST API
+~~~~~~~~
+
+
+* Support raw input also in POST requests.
+
+* Show granted date for labels/all when using `/changes/`.
+
+* Return all revisions when `o=ALL_REVISIONS` is set on `/changes/`.
+
+ssh
+~~~
+
+
+* The `--force-message` option is removed from the `review` command.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1908[Issue 1908]:
+Provide more informative error messages when rejecting updates.
+
+* Remove the limit in the query of patch sets by revision.
+
+* Add `isDraft` in the `patchSet` attribute of stream-events data.
++
+This allows consumers of the event stream to determine whether or not
+the event is related to a draft patch set.
+
+
+Emails
+~~~~~~
+
+* Email notifications are sent for new changes created via actions in the
+Web UI such as cherry-picking or reverting a change.
+
+
+Tools
+~~~~~
+
+
+* git-exproll.sh: return non-zero on errors
+
+
+Documentation
+-------------
+
+
+* The link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/index.html[
+documentation index page] is rewritten in a hierarchical structure.
+
+* Various spelling mistakes are corrected in the documentation and previous
+release notes.
+
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 092f149..28b9f65 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -1,6 +1,11 @@
 Gerrit Code Review - Release Notes
 ==================================
 
+[[2_8]]
+Version 2.8.x
+-------------
+* link:ReleaseNotes-2.8.html[2.8]
+
 [[2_7]]
 Version 2.7.x
 -------------
@@ -9,11 +14,14 @@
 [[2_6]]
 Version 2.6.x
 -------------
+* link:ReleaseNotes-2.6.1.html[2.6.1]
 * link:ReleaseNotes-2.6.html[2.6]
 
 [[2_5]]
 Version 2.5.x
 -------------
+* link:ReleaseNotes-2.5.6.html[2.5.6]
+* link:ReleaseNotes-2.5.5.html[2.5.5]
 * link:ReleaseNotes-2.5.4.html[2.5.4]
 * link:ReleaseNotes-2.5.3.html[2.5.3]
 * link:ReleaseNotes-2.5.2.html[2.5.2]
@@ -23,6 +31,8 @@
 [[2_4]]
 Version 2.4.x
 -------------
+* link:ReleaseNotes-2.4.4.html[2.4.4]
+* link:ReleaseNotes-2.4.3.html[2.4.3]
 * link:ReleaseNotes-2.4.2.html[2.4.2]
 * link:ReleaseNotes-2.4.1.html[2.4.1]
 * link:ReleaseNotes-2.4.html[2.4]
@@ -45,6 +55,8 @@
 [[2_1]]
 Version 2.1.x
 -------------
+* link:ReleaseNotes-2.1.10.html[2.1.10]
+* link:ReleaseNotes-2.1.9.html[2.1.9]
 * link:ReleaseNotes-2.1.8.html[2.1.8]
 * link:ReleaseNotes-2.1.7.2.html[2.1.7.2]
 * link:ReleaseNotes-2.1.7.html[2.1.7]
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..359b6d9
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+GERRIT_VER = '2.8-SNAPSHOT'
diff --git a/contrib/.pep8rc b/contrib/.pep8rc
new file mode 100644
index 0000000..568bcfb
--- /dev/null
+++ b/contrib/.pep8rc
@@ -0,0 +1,5 @@
+[pep8]
+max_line_length = 80
+show_pep8 = True
+show_source = True
+ignore = E111,E121
diff --git a/contrib/check-valid-commit.py b/contrib/check-valid-commit.py
index ca1785e..150b310 100755
--- a/contrib/check-valid-commit.py
+++ b/contrib/check-valid-commit.py
@@ -1,5 +1,8 @@
 #!/usr/bin/env python
-import commands
+
+from __future__ import print_function
+
+import subprocess
 import getopt
 import sys
 
@@ -24,8 +27,8 @@
     try:
         opts, args = getopt.getopt(sys.argv[1:], '', \
             ['change=', 'project=', 'branch=', 'commit=', 'patchset='])
-    except getopt.GetoptError, err:
-        print 'Error: %s' % (err)
+    except getopt.GetoptError as err:
+        print('Error: %s' % (err))
         usage()
         sys.exit(-1)
 
@@ -41,7 +44,7 @@
         elif arg == '--patchset':
             patchset = value
         else:
-            print 'Error: option %s not recognized' % (arg)
+            print('Error: option %s not recognized' % (arg))
             usage()
             sys.exit(-1)
 
@@ -51,11 +54,11 @@
         sys.exit(-1)
 
     command = 'git cat-file commit %s' % (commit)
-    status, output = commands.getstatusoutput(command)
+    status, output = subprocess.getstatusoutput(command)
 
     if status != 0:
-        print 'Error running \'%s\'. status: %s, output:\n\n%s' % \
-            (command, status, output)
+        print('Error running \'%s\'. status: %s, output:\n\n%s' % \
+            (command, status, output))
         sys.exit(-1)
 
     commitMessage = output[(output.find('\n\n')+2):]
@@ -74,21 +77,21 @@
     passes(commit)
 
 def usage():
-    print 'Usage:\n'
-    print sys.argv[0] + ' --change <change id> --project <project name> ' \
-        + '--branch <branch> --commit <sha1> --patchset <patchset id>'
+    print('Usage:\n')
+    print(sys.argv[0] + ' --change <change id> --project <project name> ' \
+        + '--branch <branch> --commit <sha1> --patchset <patchset id>')
 
 def fail( commit, message ):
     command = SSH_COMMAND + FAILURE_SCORE + ' -m \\\"' \
         + _shell_escape( FAILURE_MESSAGE + '\n\n' + message) \
         + '\\\" ' + commit
-    commands.getstatusoutput(command)
+    subprocess.getstatusoutput(command)
     sys.exit(1)
 
 def passes( commit ):
     command = SSH_COMMAND + PASS_SCORE + ' -m \\\"' \
         + _shell_escape(PASS_MESSAGE) + ' \\\" ' + commit
-    commands.getstatusoutput(command)
+    subprocess.getstatusoutput(command)
 
 def _shell_escape(x):
     s = ''
diff --git a/contrib/git-exproll.sh b/contrib/git-exproll.sh
index 9526d9f..066c57c 100644
--- a/contrib/git-exproll.sh
+++ b/contrib/git-exproll.sh
@@ -126,7 +126,7 @@
 
     [ -n "$1" ] && info "ERROR $1"
 
-    exit
+    exit 128
 }
 
 debug() { [ -n "$SW_V" ] && info "$1" ; }
diff --git a/contrib/themes/diffy/etc/GerritSite.css b/contrib/themes/diffy/etc/GerritSite.css
index d476957..76c595a 100644
--- a/contrib/themes/diffy/etc/GerritSite.css
+++ b/contrib/themes/diffy/etc/GerritSite.css
@@ -15,15 +15,15 @@
 #gerrit_topmenu {
   left: 60px;
   margin-right: 60px;
-  padding-right: 10px;
   position: relative;
+  margin-bottom: 5px;
 }
 
 #diffy_logo {
   display: block !important;
   margin-bottom: -55px;
-  padding-left: 20px;
+  padding-left: 10px;
   position: relative;
-  top: -45px;
+  top: -55px;
   width: 60px;
 }
diff --git a/contrib/trivial_rebase.py b/contrib/trivial_rebase.py
index 7764470..e16c57d 100755
--- a/contrib/trivial_rebase.py
+++ b/contrib/trivial_rebase.py
@@ -36,6 +36,8 @@
 
 """
 
+from __future__ import print_function
+
 import argparse
 import json
 import re
@@ -95,7 +97,7 @@
     try:
       process = subprocess.Popen(command, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
       std_out, std_err = process.communicate()
-    except OSError, e:
+    except OSError as e:
       raise self.CheckCallError(command, cwd, e.errno, None)
     if process.returncode:
       raise self.CheckCallError(command, cwd, process.returncode, std_out, std_err)
@@ -107,9 +109,9 @@
                 '--format', 'JSON', '-c', sql_query]
     try:
       (gsql_out, _gsql_stderr) = self.CheckCall(gsql_cmd)
-    except self.CheckCallError, e:
-      print "return code is %s" % e.retcode
-      print "stdout and stderr is\n%s%s" % (e.stdout, e.stderr)
+    except self.CheckCallError as e:
+      print("return code is %s" % e.retcode)
+      print("stdout and stderr is\n%s%s" % (e.stdout, e.stderr))
       raise
 
     new_out = gsql_out.replace('}}\n', '}}\nsplit here\n')
@@ -194,7 +196,7 @@
     prev_patch_id = self.GetPatchId(prev_revision)
     cur_patch_id = self.GetPatchId(self.commit)
     if prev_patch_id == '0' and cur_patch_id == '0':
-      print "commits %s and %s are both empty or merge commits" % (prev_revision, self.commit)
+      print("commits %s and %s are both empty or merge commits" % (prev_revision, self.commit))
       return
     if cur_patch_id != prev_patch_id:
       # patch-ids don't match
@@ -237,7 +239,7 @@
 
     gerrit_review_msg = ("\'Automatically re-added by Gerrit trivial rebase "
                           "detection script.\'")
-    for acct, flags in self.acct_approvals.items():
+    for acct, flags in list(self.acct_approvals.items()):
       gerrit_review_cmd = ['gerrit', 'review', '--project', self.project,
                             '--message', gerrit_review_msg, flags, self.commit]
       email_addr = self.GetEmailFromAcctId(acct)
@@ -246,5 +248,5 @@
 if __name__ == "__main__":
   try:
     TrivialRebase().Run()
-  except AssertionError, e:
-    print >> sys.stderr, e
+  except AssertionError as e:
+    print(e, file=sys.stderr)
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
new file mode 100644
index 0000000..f3a27ff
--- /dev/null
+++ b/gerrit-acceptance-tests/BUCK
@@ -0,0 +1,38 @@
+TEST = [
+  '//gerrit-httpd:httpd',
+  '//gerrit-sshd:sshd',
+  '//gerrit-server:server',
+]
+
+java_test(
+  name = 'acceptance_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  deps = TEST + [
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-launcher:launcher',
+    '//gerrit-pgm:pgm',
+    '//gerrit-reviewdb:server',
+
+    '//lib:args4j',
+    '//lib:gson',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:h2',
+    '//lib:jsch',
+    '//lib:jsr305',
+    '//lib:junit',
+    '//lib:servlet-api-3_0',
+
+    '//lib/commons:httpclient',
+    '//lib/commons:httpcore',
+    '//lib/log:impl_log4j',
+    '//lib/log:log4j',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
+    '//lib/jgit:junit',
+  ],
+  source_under_test = TEST,
+  labels = ['slow'],
+  visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-acceptance-tests/pom.xml b/gerrit-acceptance-tests/pom.xml
deleted file mode 100644
index b71546d..0000000
--- a/gerrit-acceptance-tests/pom.xml
+++ /dev/null
@@ -1,148 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-acceptance-tests</artifactId>
-
-  <name>Gerrit Code Review - Acceptance Tests</name>
-
-  <description>
-    Gerrit Acceptance Tests
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-reviewdb</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-main</artifactId>
-      <version>${project.version}</version>
-      <scope>runtime</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-server</artifactId>
-      <version>${project.version}</version>
-      <exclusions>
-        <exclusion>
-          <groupId>org.apache.tomcat</groupId>
-          <artifactId>servlet-api</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-log4j12</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>log4j</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-openid</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-sshd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-httpd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-pgm</artifactId>
-      <version>${project.version}</version>
-      <exclusions>
-        <exclusion>
-          <groupId>org.eclipse.jetty</groupId>
-          <artifactId>jetty-servlet</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlet</artifactId>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-
-  <profiles>
-    <profile>
-      <id>acceptance</id>
-      <activation>
-        <property>
-          <name>!gerrit.acceptance-tests.skip</name>
-        </property>
-      </activation>
-      <build>
-        <plugins>
-          <plugin>
-            <groupId>org.apache.maven.plugins</groupId>
-            <artifactId>maven-failsafe-plugin</artifactId>
-            <version>2.5</version>
-            <executions>
-              <execution>
-                <id>integration-test</id>
-                <goals>
-                  <goal>integration-test</goal>
-                </goals>
-              </execution>
-              <execution>
-                <id>verify</id>
-                <goals>
-                  <goal>verify</goal>
-                </goals>
-              </execution>
-            </executions>
-          </plugin>
-        </plugins>
-      </build>
-    </profile>
-  </profiles>
-</project>
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 65bab6a..fab8397 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
@@ -14,17 +14,6 @@
 
 package com.google.gerrit.acceptance;
 
-import java.lang.reflect.Field;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.concurrent.BrokenBarrierException;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.pgm.Daemon;
 import com.google.gerrit.pgm.Init;
@@ -32,15 +21,21 @@
 import com.google.inject.Injector;
 import com.google.inject.Module;
 
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
 class GerritServer {
 
   /** Returns fully started Gerrit server */
   static GerritServer start() throws Exception {
-
-    final String sitePath = initSite();
-
+    final File site = initSite();
     final CyclicBarrier serverStarted = new CyclicBarrier(2);
-
     final Daemon daemon = new Daemon(new Runnable() {
       public void run() {
         try {
@@ -56,10 +51,10 @@
     ExecutorService daemonService = Executors.newSingleThreadExecutor();
     daemonService.submit(new Callable<Void>() {
       public Void call() throws Exception {
-        int rc = daemon.main(new String[] {"-d", sitePath, "--headless" });
+        int rc = daemon.main(new String[] {"-d", site.getPath(), "--headless" });
         if (rc != 0) {
           System.out.println("Failed to start Gerrit daemon. Check "
-              + sitePath + "/logs/error_log");
+              + site.getPath() + "/logs/error_log");
           serverStarted.reset();
         }
         return null;
@@ -70,18 +65,18 @@
     System.out.println("Gerrit Server Started");
 
     Injector i = createTestInjector(daemon);
-    return new GerritServer(i, daemon, daemonService);
+    return new GerritServer(site, i, daemon, daemonService);
   }
 
-  private static String initSite() throws Exception {
-    DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
-    String path = "target/test_site_" + df.format(new Date());
+  private static File initSite() throws Exception {
+    File tmp = TempFileUtil.createTempDirectory();
     Init init = new Init();
-    int rc = init.main(new String[] {"-d", path, "--batch", "--no-auto-start"});
+    int rc = init.main(new String[] {
+        "-d", tmp.getPath(), "--batch", "--no-auto-start"});
     if (rc != 0) {
       throw new RuntimeException("Couldn't initialize site");
     }
-    return path;
+    return tmp;
   }
 
   private static Injector createTestInjector(Daemon daemon) throws Exception {
@@ -103,12 +98,14 @@
     return (T) f.get(obj);
   }
 
+  private File sitePath;
   private Daemon daemon;
   private ExecutorService daemonService;
   private Injector testInjector;
 
-  private GerritServer(Injector testInjector,
+  private GerritServer(File sitePath, Injector testInjector,
       Daemon daemon, ExecutorService daemonService) {
+    this.sitePath = sitePath;
     this.testInjector = testInjector;
     this.daemon = daemon;
     this.daemonService = daemonService;
@@ -124,5 +121,6 @@
     manager.stop();
     daemonService.shutdownNow();
     daemonService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
+    TempFileUtil.recursivelyDelete(sitePath);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
index 2bf6523..8954af3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.acceptance;
 
+import com.google.common.base.Charsets;
 import com.google.gson.Gson;
 
 import org.apache.http.auth.AuthScope;
@@ -25,7 +26,6 @@
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.message.BasicHeader;
-import org.apache.http.protocol.HTTP;
 
 import java.io.IOException;
 
@@ -51,7 +51,9 @@
     HttpPut put = new HttpPut("http://localhost:8080/a" + endPoint);
     if (content != null) {
       put.addHeader(new BasicHeader("Content-Type", "application/json"));
-      put.setEntity(new StringEntity((new Gson()).toJson(content), HTTP.UTF_8));
+      put.setEntity(new StringEntity(
+          new Gson().toJson(content),
+          Charsets.UTF_8.name()));
     }
     return new RestResponse(getClient().execute(put));
   }
@@ -64,7 +66,9 @@
     HttpPost post = new HttpPost("http://localhost:8080/a" + endPoint);
     if (content != null) {
       post.addHeader(new BasicHeader("Content-Type", "application/json"));
-      post.setEntity(new StringEntity((new Gson()).toJson(content), HTTP.UTF_8));
+      post.setEntity(new StringEntity(
+          new Gson().toJson(content),
+          Charsets.UTF_8.name()));
     }
     return new RestResponse(getClient().execute(post));
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
index aae9236..c0847c0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
@@ -33,6 +33,7 @@
     this.account = account;
   }
 
+  @SuppressWarnings("resource")
   public String exec(String command) throws JSchException, IOException {
     ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
     try {
@@ -42,7 +43,7 @@
       channel.connect();
 
       Scanner s = new Scanner(channel.getErrStream()).useDelimiter("\\A");
-      error =  s.hasNext() ? s.next() : null;
+      error = s.hasNext() ? s.next() : null;
 
       s = new Scanner(in).useDelimiter("\\A");
       return s.hasNext() ? s.next() : "";
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TempFileUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TempFileUtil.java
index adee361..1dad479 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TempFileUtil.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TempFileUtil.java
@@ -15,27 +15,41 @@
 package com.google.gerrit.acceptance;
 
 import java.io.File;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.io.IOException;
 
 public class TempFileUtil {
-
-  private static int testCount;
-  private static DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
-  private static final File temp = new File(new File("target"), "temp");
-
-  private static String createUniqueTestFolderName() {
-    return "test_" + (df.format(new Date()) + "_" + (testCount++));
+  public static File createTempDirectory() throws IOException {
+    File tmp = File.createTempFile("gerrit_test_", "");
+    if (!tmp.delete() || !tmp.mkdir()) {
+      throw new IOException("Cannot create " + tmp.getPath());
+    }
+    return tmp;
   }
 
-  public static File createTempDirectory() {
-    final String name = createUniqueTestFolderName();
-    final File directory = new File(temp, name);
-    if (!directory.mkdirs()) {
-      throw new RuntimeException("failed to create folder '"
-          + directory.getAbsolutePath() + "'");
+  public static void recursivelyDelete(File dir) throws IOException {
+    if (!dir.getPath().equals(dir.getCanonicalPath())) {
+      // Directory symlink reaching outside of temporary space.
+      throw new IOException("Refusing to clear symlink " + dir.getPath());
     }
-    return directory;
+    File[] contents = dir.listFiles();
+    if (contents != null) {
+      for (File d : contents) {
+        if (d.isDirectory()) {
+          recursivelyDelete(d);
+        } else {
+          deleteNowOrOnExit(d);
+        }
+      }
+    }
+    deleteNowOrOnExit(dir);
+  }
+
+  private static void deleteNowOrOnExit(File dir) {
+    if (!dir.delete()) {
+      dir.deleteOnExit();
+    }
+  }
+
+  private TempFileUtil() {
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
index c8158c07..bbf7484 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.acceptance.SshSession;
 import com.google.gerrit.acceptance.TempFileUtil;
 import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.reviewdb.client.Project;
 
 import com.jcraft.jsch.JSch;
 import com.jcraft.jsch.JSchException;
@@ -74,10 +75,34 @@
 
   public static void createProject(SshSession s, String name)
       throws JSchException, IOException {
-    s.exec("gerrit create-project --empty-commit --name \"" + name + "\"");
+    createProject(s, name, null);
   }
 
-  public static Git cloneProject(String url) throws GitAPIException {
+  public static void createProject(SshSession s, String name, Project.NameKey parent)
+      throws JSchException, IOException {
+    createProject(s, name, parent, true);
+  }
+
+  public static void createProject(SshSession s, String name,
+      Project.NameKey parent, boolean emptyCommit)
+      throws JSchException, IOException {
+    StringBuilder b = new StringBuilder();
+    b.append("gerrit create-project");
+    if (emptyCommit) {
+      b.append(" --empty-commit");
+    }
+    b.append(" --name \"");
+    b.append(name);
+    b.append("\"");
+    if (parent != null) {
+      b.append(" --parent \"");
+      b.append(parent.get());
+      b.append("\"");
+    }
+    s.exec(b.toString());
+  }
+
+  public static Git cloneProject(String url) throws GitAPIException, IOException {
     final File gitDir = TempFileUtil.createTempDirectory();
     final CloneCommand cloneCmd = Git.cloneRepository();
     cloneCmd.setURI(url);
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
similarity index 71%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
index b2fb901..4c9325e 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
@@ -12,12 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.acceptance.rest.change;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import java.util.List;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class ChangeInfo {
+  List<ChangeMessageInfo> messages;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java
similarity index 71%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java
index b2fb901..b3c584a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java
@@ -12,12 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.acceptance.rest.change;
 
-import java.io.IOException;
-import java.io.OutputStream;
-
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class ChangeMessageInfo {
+  String message;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
new file mode 100644
index 0000000..351c320
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -0,0 +1,151 @@
+// 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.acceptance.rest.change;
+
+import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ChangeMessagesIT extends AbstractDaemonTest {
+
+  @Inject
+  private AccountCreator accounts;
+
+  @Inject
+  private SchemaFactory<ReviewDb> reviewDbProvider;
+
+  private TestAccount admin;
+  private RestSession session;
+  private Git git;
+  private ReviewDb db;
+
+  @Before
+  public void setUp() throws Exception {
+    admin = accounts.create("admin", "admin@example.com", "Administrator",
+            "Administrators");
+    session = new RestSession(admin);
+    initSsh(admin);
+    Project.NameKey project = new Project.NameKey("p");
+    SshSession sshSession = new SshSession(admin);
+    createProject(sshSession, project.get());
+    git = cloneProject(sshSession.getUrl() + "/" + project.get());
+    sshSession.close();
+    db = reviewDbProvider.open();
+  }
+
+  @After
+  public void cleanup() {
+    db.close();
+  }
+
+  @Test
+  public void messagesNotReturnedByDefault() throws GitAPIException,
+      IOException {
+    String changeId = createChange();
+    postMessage(changeId, "Some nits need to be fixed.");
+    ChangeInfo c = getChange(changeId);
+    assertNull(c.messages);
+  }
+
+  @Test
+  public void noMessages() throws GitAPIException,
+  IOException {
+    String changeId = createChange();
+    ChangeInfo c = getChangeWithMessages(changeId);
+    assertNotNull(c.messages);
+    assertTrue(c.messages.isEmpty());
+  }
+
+  @Test
+  public void messagesReturnedInChronologicalOrder() throws GitAPIException,
+      IOException {
+    String changeId = createChange();
+    String firstMessage = "Some nits need to be fixed.";
+    postMessage(changeId, firstMessage);
+    String secondMessage = "I like this feature.";
+    postMessage(changeId, secondMessage);
+    ChangeInfo c = getChangeWithMessages(changeId);
+    assertNotNull(c.messages);
+    assertEquals(2, c.messages.size());
+    assertMessage(firstMessage, c.messages.get(0).message);
+    assertMessage(secondMessage, c.messages.get(1).message);
+  }
+
+  private String createChange() throws GitAPIException,
+      IOException {
+    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    return push.to(git, "refs/for/master").getChangeId();
+  }
+
+  private ChangeInfo getChange(String changeId) throws IOException {
+    return getChange(changeId, false);
+  }
+
+  private ChangeInfo getChangeWithMessages(String changeId) throws IOException {
+    return getChange(changeId, true);
+  }
+
+  private ChangeInfo getChange(String changeId, boolean includeMessages)
+      throws IOException {
+    RestResponse r =
+        session.get("/changes/?q=" + changeId
+            + (includeMessages ? "&o=MESSAGES" : ""));
+    List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
+        new TypeToken<List<ChangeInfo>>() {}.getType());
+    return c.get(0);
+  }
+
+  private void assertMessage(String expected, String actual) {
+    assertEquals("Patch Set 1:\n\n" + expected, actual);
+  }
+
+  private void postMessage(String changeId, String msg) throws IOException {
+    ReviewInput in = new ReviewInput();
+    in.message = msg;
+    session.post("/changes/" + changeId + "/revisions/1/review", in).consume();
+  }
+
+  @SuppressWarnings("unused")
+  private class ReviewInput {
+    String message;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java
new file mode 100644
index 0000000..654ef65
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java
@@ -0,0 +1,62 @@
+// 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.acceptance.rest.project;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+public class BranchAssert {
+
+  public static void assertBranches(List<BranchInfo> expectedBranches,
+      List<BranchInfo> actualBranches) {
+    List<BranchInfo> missingBranches = Lists.newArrayList(actualBranches);
+    for (final BranchInfo b : expectedBranches) {
+      BranchInfo info =
+          Iterables.find(actualBranches, new Predicate<BranchInfo>() {
+            @Override
+            public boolean apply(BranchInfo info) {
+              return info.ref.equals(b.ref);
+            }
+          }, null);
+      assertNotNull("missing branch: " + b.ref, info);
+      assertBranchInfo(b, info);
+      missingBranches.remove(info);
+    }
+    assertTrue("unexpected branches: " + missingBranches,
+        missingBranches.isEmpty());
+  }
+
+  public static void assertBranchInfo(BranchInfo expected, BranchInfo actual) {
+    assertEquals(expected.ref, actual.ref);
+    if (expected.revision != null) {
+      assertEquals(expected.revision, actual.revision);
+    }
+    assertEquals(expected.can_delete, toBoolean(actual.can_delete));
+  }
+
+  private static boolean toBoolean(Boolean b) {
+    if (b == null) {
+      return false;
+    }
+    return b.booleanValue();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchInfo.java
new file mode 100644
index 0000000..2b7933e
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchInfo.java
@@ -0,0 +1,35 @@
+// 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.acceptance.rest.project;
+
+public class BranchInfo {
+  public String ref;
+  public String revision;
+  public Boolean can_delete;
+
+  public BranchInfo() {
+  }
+
+  public BranchInfo(String ref, String revision, boolean canDelete) {
+    this.ref = ref;
+    this.revision = revision;
+    this.can_delete = canDelete;
+  }
+
+  @Override
+  public String toString() {
+    return ref;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
new file mode 100644
index 0000000..1677d42
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
@@ -0,0 +1,126 @@
+// 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.acceptance.rest.project;
+
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+
+import com.jcraft.jsch.JSchException;
+
+import org.apache.http.HttpStatus;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class GetChildProjectIT extends AbstractDaemonTest {
+
+  @Inject
+  private AccountCreator accounts;
+
+  @Inject
+  private AllProjectsName allProjects;
+
+  @Inject
+  private ProjectCache projectCache;
+
+  private TestAccount admin;
+  private RestSession session;
+
+  @Before
+  public void setUp() throws Exception {
+    admin =
+        accounts.create("admin", "admin@example.com", "Administrator",
+            "Administrators");
+    session = new RestSession(admin);
+  }
+
+  @Test
+  public void getNonExistingChildProject_NotFound() throws IOException {
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        GET("/projects/" + allProjects.get() + "/children/non-existing").getStatusCode());
+  }
+
+  @Test
+  public void getNonChildProject_NotFound() throws IOException, JSchException {
+    SshSession sshSession = new SshSession(admin);
+    Project.NameKey p1 = new Project.NameKey("p1");
+    createProject(sshSession, p1.get());
+    Project.NameKey p2 = new Project.NameKey("p2");
+    createProject(sshSession, p2.get());
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        GET("/projects/" + p1.get() + "/children/" + p2.get()).getStatusCode());
+  }
+
+  @Test
+  public void getChildProject() throws IOException, JSchException {
+    SshSession sshSession = new SshSession(admin);
+    Project.NameKey child = new Project.NameKey("p1");
+    createProject(sshSession, child.get());
+    RestResponse r = GET("/projects/" + allProjects.get() + "/children/" + child.get());
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    ProjectInfo childInfo =
+        (new Gson()).fromJson(r.getReader(),
+            new TypeToken<ProjectInfo>() {}.getType());
+    assertProjectInfo(projectCache.get(child).getProject(), childInfo);
+  }
+
+  @Test
+  public void getGrandChildProject_NotFound() throws IOException, JSchException {
+    SshSession sshSession = new SshSession(admin);
+    Project.NameKey child = new Project.NameKey("p1");
+    createProject(sshSession, child.get());
+    Project.NameKey grandChild = new Project.NameKey("p1.1");
+    createProject(sshSession, grandChild.get(), child);
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        GET("/projects/" + allProjects.get() + "/children/" + grandChild.get())
+            .getStatusCode());
+  }
+
+  @Test
+  public void getGrandChildProjectWithRecursiveFlag() throws IOException,
+      JSchException {
+    SshSession sshSession = new SshSession(admin);
+    Project.NameKey child = new Project.NameKey("p1");
+    createProject(sshSession, child.get());
+    Project.NameKey grandChild = new Project.NameKey("p1.1");
+    createProject(sshSession, grandChild.get(), child);
+    RestResponse r =
+        GET("/projects/" + allProjects.get() + "/children/" + grandChild.get()
+            + "?recursive");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    ProjectInfo grandChildInfo =
+        (new Gson()).fromJson(r.getReader(), new TypeToken<ProjectInfo>() {}.getType());
+    assertProjectInfo(projectCache.get(grandChild).getProject(), grandChildInfo);
+  }
+
+  private RestResponse GET(String endpoint) throws IOException {
+    return session.get(endpoint);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
new file mode 100644
index 0000000..3fee3d3
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
@@ -0,0 +1,227 @@
+// 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.acceptance.rest.project;
+
+import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static com.google.gerrit.acceptance.rest.project.BranchAssert.assertBranches;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import com.jcraft.jsch.JSchException;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class ListBranchesIT extends AbstractDaemonTest {
+
+  @Inject
+  private AccountCreator accounts;
+
+  @Inject
+  private MetaDataUpdate.Server metaDataUpdateFactory;
+
+  @Inject
+  private ProjectCache projectCache;
+
+  @Inject
+  private GroupCache groupCache;
+
+  @Inject
+  private SchemaFactory<ReviewDb> reviewDbProvider;
+
+  private TestAccount admin;
+  private RestSession session;
+  private SshSession sshSession;
+  private Project.NameKey project;
+  private Git git;
+  private ReviewDb db;
+
+  @Before
+  public void setUp() throws Exception {
+    admin = accounts.create("admin", "admin@example.com", "Administrator",
+            "Administrators");
+    session = new RestSession(admin);
+
+    project = new Project.NameKey("p");
+    initSsh(admin);
+    sshSession = new SshSession(admin);
+    createProject(sshSession, project.get());
+    git = cloneProject(sshSession.getUrl() + "/" + project.get());
+    db = reviewDbProvider.open();
+  }
+
+  @After
+  public void cleanup() {
+    db.close();
+  }
+
+  @Test
+  public void listBranchesOfNonExistingProject_NotFound() throws IOException {
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        GET("/projects/non-existing/branches").getStatusCode());
+  }
+
+  @Test
+  public void listBranchesOfNonVisibleProject_NotFound() throws IOException,
+      OrmException, JSchException, ConfigInvalidException {
+    blockRead(project, "refs/*");
+    RestSession session =
+        new RestSession(accounts.create("user", "user@example.com", "User"));
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        session.get("/projects/" + project.get() + "/branches").getStatusCode());
+  }
+
+  @Test
+  public void listBranchesOfEmptyProject() throws IOException, JSchException {
+    Project.NameKey emptyProject = new Project.NameKey("empty");
+    createProject(sshSession, emptyProject.get(), null, false);
+    RestResponse r = session.get("/projects/" + emptyProject.get() + "/branches");
+    List<BranchInfo> result =
+        (new Gson()).fromJson(r.getReader(),
+            new TypeToken<List<BranchInfo>>() {}.getType());
+    List<BranchInfo> expected = Lists.asList(
+        new BranchInfo("refs/meta/config",  null, false),
+        new BranchInfo[] {
+          new BranchInfo("HEAD", null, false)
+        });
+    assertBranches(expected, result);
+  }
+
+  @Test
+  public void listBranches() throws IOException, GitAPIException {
+    pushTo("refs/heads/master");
+    String masterCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
+    pushTo("refs/heads/dev");
+    String devCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
+    RestResponse r = session.get("/projects/" + project.get() + "/branches");
+    List<BranchInfo> result =
+        (new Gson()).fromJson(r.getReader(),
+            new TypeToken<List<BranchInfo>>() {}.getType());
+    List<BranchInfo> expected = Lists.asList(
+        new BranchInfo("refs/meta/config",  null, false),
+        new BranchInfo[] {
+          new BranchInfo("HEAD", "master", false),
+          new BranchInfo("refs/heads/master", masterCommit, false),
+          new BranchInfo("refs/heads/dev", devCommit, true)
+        });
+    assertBranches(expected, result);
+
+    // verify correct sorting
+    assertEquals("HEAD", result.get(0).ref);
+    assertEquals("refs/meta/config", result.get(1).ref);
+    assertEquals("refs/heads/dev", result.get(2).ref);
+    assertEquals("refs/heads/master", result.get(3).ref);
+  }
+
+  @Test
+  public void listBranchesSomeHidden() throws IOException, GitAPIException,
+      ConfigInvalidException, OrmException, JSchException {
+    blockRead(project, "refs/heads/dev");
+    RestSession session =
+        new RestSession(accounts.create("user", "user@example.com", "User"));
+    pushTo("refs/heads/master");
+    String masterCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
+    pushTo("refs/heads/dev");
+    RestResponse r = session.get("/projects/" + project.get() + "/branches");
+    List<BranchInfo> result =
+        (new Gson()).fromJson(r.getReader(),
+            new TypeToken<List<BranchInfo>>() {}.getType());
+    // refs/meta/config is hidden since user is no project owner
+    List<BranchInfo> expected = Lists.asList(
+        new BranchInfo("HEAD", "master", false),
+        new BranchInfo[] {
+          new BranchInfo("refs/heads/master", masterCommit, false),
+        });
+    assertBranches(expected, result);
+  }
+
+  @Test
+  public void listBranchesHeadHidden() throws IOException, GitAPIException,
+      ConfigInvalidException, OrmException, JSchException {
+    blockRead(project, "refs/heads/master");
+    RestSession session =
+        new RestSession(accounts.create("user", "user@example.com", "User"));
+    pushTo("refs/heads/master");
+    pushTo("refs/heads/dev");
+    String devCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
+    RestResponse r = session.get("/projects/" + project.get() + "/branches");
+    List<BranchInfo> result =
+        (new Gson()).fromJson(r.getReader(),
+            new TypeToken<List<BranchInfo>>() {}.getType());
+    // refs/meta/config is hidden since user is no project owner
+    assertBranches(Collections.singletonList(new BranchInfo("refs/heads/dev",
+        devCommit, false)), result);
+  }
+
+  private RestResponse GET(String endpoint) throws IOException {
+    return session.get(endpoint);
+  }
+
+  private void blockRead(Project.NameKey project, String ref)
+      throws RepositoryNotFoundException, IOException, ConfigInvalidException {
+    MetaDataUpdate md = metaDataUpdateFactory.create(project);
+    md.setMessage("Grant submit on " + ref);
+    ProjectConfig config = ProjectConfig.read(md);
+    AccessSection s = config.getAccessSection(ref, true);
+    Permission p = s.getPermission(Permission.READ, true);
+    AccountGroup adminGroup = groupCache.get(AccountGroup.REGISTERED_USERS);
+    PermissionRule rule = new PermissionRule(config.resolve(adminGroup));
+    rule.setBlock();
+    p.add(rule);
+    config.commit(md);
+    projectCache.evict(config.getProject());
+  }
+
+  private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
+      IOException {
+    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    return push.to(git, ref);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
new file mode 100644
index 0000000..57e938a
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
@@ -0,0 +1,122 @@
+// 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.acceptance.rest.project;
+
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjects;
+
+import com.google.gson.reflect.TypeToken;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+
+import com.jcraft.jsch.JSchException;
+
+import org.apache.http.HttpStatus;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+public class ListChildProjectsIT extends AbstractDaemonTest {
+
+  @Inject
+  private AccountCreator accounts;
+
+  @Inject
+  private AllProjectsName allProjects;
+
+  private TestAccount admin;
+  private RestSession session;
+
+  @Before
+  public void setUp() throws Exception {
+    admin =
+        accounts.create("admin", "admin@example.com", "Administrator",
+            "Administrators");
+    session = new RestSession(admin);
+  }
+
+  @Test
+  public void listChildrenOfNonExistingProject_NotFound() throws IOException {
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        GET("/projects/non-existing/children/").getStatusCode());
+  }
+
+  @Test
+  public void listNoChildren() throws IOException {
+    RestResponse r = GET("/projects/" + allProjects.get() + "/children/");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    List<ProjectInfo> children =
+        (new Gson()).fromJson(r.getReader(),
+            new TypeToken<List<ProjectInfo>>() {}.getType());
+    assertTrue(children.isEmpty());
+  }
+
+  @Test
+  public void listChildren() throws IOException, JSchException {
+    SshSession sshSession = new SshSession(admin);
+    Project.NameKey child1 = new Project.NameKey("p1");
+    createProject(sshSession, child1.get());
+    Project.NameKey child2 = new Project.NameKey("p2");
+    createProject(sshSession, child2.get());
+    createProject(sshSession, "p1.1", child1);
+
+    RestResponse r = GET("/projects/" + allProjects.get() + "/children/");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    List<ProjectInfo> children =
+        (new Gson()).fromJson(r.getReader(),
+            new TypeToken<List<ProjectInfo>>() {}.getType());
+    assertProjects(Arrays.asList(child1, child2), children);
+  }
+
+  @Test
+  public void listChildrenRecursively() throws IOException, JSchException {
+    SshSession sshSession = new SshSession(admin);
+    Project.NameKey child1 = new Project.NameKey("p1");
+    createProject(sshSession, child1.get());
+    createProject(sshSession, "p2");
+    Project.NameKey child1_1 = new Project.NameKey("p1.1");
+    createProject(sshSession, child1_1.get(), child1);
+    Project.NameKey child1_2 = new Project.NameKey("p1.2");
+    createProject(sshSession, child1_2.get(), child1);
+    Project.NameKey child1_1_1 = new Project.NameKey("p1.1.1");
+    createProject(sshSession, child1_1_1.get(), child1_1);
+    Project.NameKey child1_1_1_1 = new Project.NameKey("p1.1.1.1");
+    createProject(sshSession, child1_1_1_1.get(), child1_1_1);
+
+    RestResponse r = GET("/projects/" + child1.get() + "/children/?recursive");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    List<ProjectInfo> children =
+        (new Gson()).fromJson(r.getReader(),
+            new TypeToken<List<ProjectInfo>>() {}.getType());
+    assertProjects(Arrays.asList(child1_1, child1_2, child1_1_1, child1_1_1_1), children);
+  }
+
+  private RestResponse GET(String endpoint) throws IOException {
+    return session.get(endpoint);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
index 25ccbee..224d59d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
@@ -15,18 +15,36 @@
 package com.google.gerrit.acceptance.rest.project;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.project.ProjectState;
 
+import java.util.List;
 import java.util.Set;
 
 public class ProjectAssert {
 
+  public static void assertProjects(Iterable<Project.NameKey> expected,
+      List<ProjectInfo> actual) {
+    for (final Project.NameKey p : expected) {
+      ProjectInfo info = Iterables.find(actual, new Predicate<ProjectInfo>() {
+        @Override
+        public boolean apply(ProjectInfo info) {
+          return new Project.NameKey(info.name).equals(p);
+        }}, null);
+      assertNotNull("missing project: " + p, info);
+      actual.remove(info);
+    }
+    assertTrue("unexpected projects: " + actual, actual.isEmpty());
+  }
+
   public static void assertProjectInfo(Project project, ProjectInfo info) {
     if (info.name != null) {
       // 'name' is not set if returned in a map
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
index 72dd2d6..2e209d1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
@@ -19,4 +19,9 @@
   public String name;
   public String parent;
   public String description;
+
+  @Override
+  public String toString() {
+    return name;
+  }
 }
diff --git a/gerrit-antlr/BUCK b/gerrit-antlr/BUCK
new file mode 100644
index 0000000..2071656
--- /dev/null
+++ b/gerrit-antlr/BUCK
@@ -0,0 +1,42 @@
+ANTLR_OUTS = [
+  'QueryLexer.java',
+  'QueryParser.java',
+]
+PARSER_DEPS = [
+  ':query_exception',
+  '//lib/antlr:java_runtime',
+]
+
+java_library(
+  name = 'query_exception',
+  srcs = ['src/main/java/com/google/gerrit/server/query/QueryParseException.java'],
+  visibility = ['PUBLIC'],
+)
+
+genantlr(
+  name = 'query_antlr',
+  srcs = ['src/main/antlr3/com/google/gerrit/server/query/Query.g'],
+  outs = ANTLR_OUTS,
+)
+
+# Hack necessary to expose ANTLR generated code as JAR to Eclipse.
+java_library(
+  name = 'lib',
+  srcs = [genfile(f) for f in ANTLR_OUTS],
+  deps = PARSER_DEPS + [':' + f for f in ANTLR_OUTS],
+)
+
+genrule(
+  name = 'query_link',
+  cmd = 'ln -s $SRCS $OUT',
+  srcs = [genfile('lib__lib__output/lib.jar')],
+  deps = [':lib'],
+  out = 'query_parser.jar',
+)
+
+prebuilt_jar(
+  name = 'query_parser',
+  binary_jar = genfile('query_parser.jar'),
+  deps = PARSER_DEPS + [':query_link'],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-antlr/pom.xml b/gerrit-antlr/pom.xml
deleted file mode 100644
index e1eba40..0000000
--- a/gerrit-antlr/pom.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-antlr</artifactId>
-  <name>Gerrit Code Review - ANTLR</name>
-
-  <description>
-    ANTLR generated sources
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.antlr</groupId>
-      <artifactId>antlr</artifactId>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.antlr</groupId>
-        <artifactId>antlr3-maven-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>antlr</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-cache-h2/BUCK b/gerrit-cache-h2/BUCK
new file mode 100644
index 0000000..d3e8994
--- /dev/null
+++ b/gerrit-cache-h2/BUCK
@@ -0,0 +1,14 @@
+java_library(
+  name = 'cache-h2',
+  srcs = glob(['src/main/java/**/*.java']),
+  deps = [
+    '//gerrit-extension-api:api',
+    '//gerrit-server:server',
+    '//lib:guava',
+    '//lib:h2',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
+    '//lib/log:api',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-cache-h2/pom.xml b/gerrit-cache-h2/pom.xml
deleted file mode 100644
index 4abd77c..0000000
--- a/gerrit-cache-h2/pom.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-cache-h2</artifactId>
-  <name>Gerrit Code Review - Guava + H2 caching</name>
-
-  <description>
-    Implementation of caching backed by Guava and H2
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-server</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.h2database</groupId>
-      <artifactId>h2</artifactId>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/gerrit-common/BUCK b/gerrit-common/BUCK
new file mode 100644
index 0000000..9a8c1d8
--- /dev/null
+++ b/gerrit-common/BUCK
@@ -0,0 +1,68 @@
+SRC = 'src/main/java/com/google/gerrit/'
+VER = 'resources/com/google/gerrit/common/Version'
+
+gwt_module(
+  name = 'client',
+  srcs = glob([SRC + 'common/**/*.java']),
+  gwtxml = SRC + 'Common.gwt.xml',
+  deps = [
+    '//gerrit-patch-jgit:client',
+    '//gerrit-prettify:client',
+    '//gerrit-reviewdb:client',
+    '//lib:gwtjsonrpc',
+    '//lib:gwtorm',
+    '//lib:jsr305',
+    '//lib/jgit:jgit',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'server',
+  srcs = glob([SRC + 'common/**/*.java']),
+  deps = [
+    '//gerrit-patch-jgit:server',
+    '//gerrit-prettify:server',
+    '//gerrit-reviewdb:server',
+    '//lib:gwtjsonrpc',
+    '//lib:gwtorm',
+    '//lib:jsr305',
+    '//lib/jgit:jgit',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'version',
+  resources = [genfile(VER)],
+  deps = [':git_describe'],
+  visibility = ['PUBLIC'],
+)
+
+# TODO(sop): Move git describe into an uncacheable genrule()
+def git_describe():
+  import subprocess
+  cmd = ['git', 'describe', '--match', 'v[0-9].*', '--dirty']
+  p = subprocess.Popen(cmd, stdout = subprocess.PIPE)
+  v = p.communicate()[0].strip()
+  r = p.returncode
+  if r != 0:
+    raise subprocess.CalledProcessError(r, ' '.join(cmd))
+  return v
+
+genrule(
+  name = 'git_describe',
+  cmd = 'mkdir -p $(dirname $OUT); echo "%s" >$OUT' % git_describe(),
+  srcs = [],
+  out = VER,
+)
+
+java_test(
+  name = 'client_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  deps = [
+    ':client',
+    '//lib:junit',
+  ],
+  source_under_test = [':client'],
+)
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
deleted file mode 100644
index 8f133e9..0000000
--- a/gerrit-common/pom.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-common</artifactId>
-  <name>Gerrit Code Review - Common</name>
-
-  <description>
-    Classes common to both server and client.
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-servlet</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-gwtexpui</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-reviewdb</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-prettify</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-patch-jgit</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.code.findbugs</groupId>
-      <artifactId>jsr305</artifactId>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-antrun-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>generate-version</id>
-            <phase>generate-resources</phase>
-            <configuration>
-              <target>
-                <property name="dst" location="${project.build.outputDirectory}" />
-                <property name="pkg" location="${dst}/com/google/gerrit/common" />
-                <mkdir dir="${pkg}" />
-                <exec executable="git" outputproperty="v">
-                  <arg value="describe"/>
-                  <arg value="HEAD"/>
-                </exec>
-                <echo file="${pkg}/Version">${v}</echo>
-              </target>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index beab1d9..ad415d4 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -54,6 +54,14 @@
     return "/c/" + c + "/";
   }
 
+  public static String toChange2(final Change.Id c) {
+    return "/c2/" + c + "/";
+  }
+
+  public static String toChange2(Change.Id c, String p) {
+    return "/c2/" + c + "/" + p;
+  }
+
   public static String toChange(final PatchSet.Id ps) {
     return "/c/" + ps.getParentKey() + "/" + ps.get();
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java b/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
index f5ef9c5..6db0847 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
@@ -37,7 +37,10 @@
   DETAILED_ACCOUNTS(7),
 
   /** Include messages associated with the change. */
-  MESSAGES(9);
+  MESSAGES(9),
+
+  /** Include allowed actions client could perform. */
+  CURRENT_ACTIONS(10);
 
   private final int value;
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java b/gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java
similarity index 67%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java
copy to gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java
index b2d62b3..777467d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.common.changes;
 
-import java.io.IOException;
-import java.io.InputStream;
-
-
-/** Raw data stream supplied by the body of a PUT. */
-public interface PutInput {
-  String getContentType();
-  long getContentLength();
-  InputStream getInputStream() throws IOException;
-}
+/** The side on which a comment was added. */
+public enum Side {
+  PARENT, REVISION;
+}
\ No newline at end of file
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
index a9b5e85..5ddf1ae 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
@@ -124,7 +124,7 @@
     if (!super.equals(obj) || !(obj instanceof AccessSection)) {
       return false;
     }
-    return new HashSet<Permission>(permissions).equals(new HashSet<Permission>(
-        ((AccessSection) obj).permissions));
+    return new HashSet<Permission>(getPermissions()).equals(new HashSet<Permission>(
+        ((AccessSection) obj).getPermissions()));
   }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
index c2d03ab..f382d49 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.reviewdb.client.ContactInformation;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
@@ -31,32 +30,10 @@
 
 @RpcImpl(version = Version.V2_0)
 public interface AccountSecurity extends RemoteJsonService {
-  @SignInRequired
-  void mySshKeys(AsyncCallback<List<AccountSshKey>> callback);
-
-  @Audit
-  @SignInRequired
-  void addSshKey(String keyText, AsyncCallback<AccountSshKey> callback);
-
-  @Audit
-  @SignInRequired
-  void deleteSshKeys(Set<AccountSshKey.Id> ids,
-      AsyncCallback<VoidResult> callback);
-
   @Audit
   @SignInRequired
   void changeUserName(String newName, AsyncCallback<VoidResult> callback);
 
-  @Audit
-  @SignInRequired
-  void generatePassword(AccountExternalId.Key key,
-      AsyncCallback<AccountExternalId> callback);
-
-  @Audit
-  @SignInRequired
-  void clearPassword(AccountExternalId.Key key,
-      AsyncCallback<AccountExternalId> gerritCallback);
-
   @SignInRequired
   void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);
 
@@ -77,9 +54,5 @@
 
   @Audit
   @SignInRequired
-  void registerEmail(String address, AsyncCallback<Account> callback);
-
-  @Audit
-  @SignInRequired
   void validateEmail(String token, AsyncCallback<VoidResult> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AddBranchResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AddBranchResult.java
deleted file mode 100644
index 24e527dc..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AddBranchResult.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// 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.common.data;
-
-public class AddBranchResult {
-  protected ListBranchesResult listBranchesResult;
-  protected Error error;
-
-  protected AddBranchResult() {
-  }
-
-  public AddBranchResult(final Error error) {
-    this.error = error;
-  }
-
-  public AddBranchResult(final ListBranchesResult listBranchesResult) {
-    this.listBranchesResult = listBranchesResult;
-  }
-
-  public ListBranchesResult getListBranchesResult() {
-    return listBranchesResult;
-  }
-
-  public boolean hasError() {
-    return error != null;
-  }
-
-  public Error getError() {
-    return error;
-  }
-
-  public static class Error {
-    public static enum Type {
-      /** The branch cannot be created because the given branch name is invalid. */
-      INVALID_NAME,
-
-      /** The branch cannot be created because the given revision is invalid. */
-      INVALID_REVISION,
-
-      /**
-       * The branch cannot be created under the given refname prefix (e.g
-       * branches cannot be created under magic refname prefixes).
-       */
-      BRANCH_CREATION_NOT_ALLOWED_UNDER_REFNAME_PREFIX,
-
-      /** The branch that should be created exists already. */
-      BRANCH_ALREADY_EXISTS,
-
-      /**
-       * The branch cannot be created because it conflicts with an existing
-       * branch (branches cannot be nested).
-       */
-      BRANCH_CREATION_CONFLICT
-    }
-
-    protected Type type;
-    protected String refname;
-
-    protected Error() {
-    }
-
-    public Error(final Type type) {
-      this(type, null);
-    }
-
-    public Error(final Type type, final String refname) {
-      this.type = type;
-      this.refname = refname;
-    }
-
-    public Type getType() {
-      return type;
-    }
-
-    public String getRefname() {
-      return refname;
-    }
-
-    @Override
-    public String toString() {
-      return type + " " + refname;
-    }
-  }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
index f6d5ea33..3342bc2 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
@@ -28,6 +28,7 @@
   protected boolean allowsAnonymous;
   protected boolean canAbandon;
   protected boolean canEditCommitMessage;
+  protected boolean canCherryPick;
   protected boolean canPublish;
   protected boolean canRebase;
   protected boolean canRestore;
@@ -84,6 +85,14 @@
     canEditCommitMessage = a;
   }
 
+  public boolean canCherryPick() {
+    return canCherryPick;
+  }
+
+  public void setCanCherryPick(final boolean a) {
+    canCherryPick = a;
+  }
+
   public boolean canPublish() {
     return canPublish;
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index 8c08feb..e18aee2 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -58,6 +58,9 @@
   /** Can flush any cache except the active web_sessions cache. */
   public static final String FLUSH_CACHES = "flushCaches";
 
+  /** Can generate HTTP passwords for user other than self. */
+  public static final String GENERATE_HTTP_PASSWORD = "generateHttpPassword";
+
   /** Can terminate any task using the kill command. */
   public static final String KILL_TASK = "killTask";
 
@@ -67,6 +70,9 @@
   /** Maximum result limit per executed query. */
   public static final String QUERY_LIMIT = "queryLimit";
 
+  /** Ability to impersonate another user. */
+  public static final String RUN_AS = "runAs";
+
   /** Can run the Git garbage collection. */
   public static final String RUN_GC = "runGC";
 
@@ -100,6 +106,7 @@
     NAMES_ALL.add(KILL_TASK);
     NAMES_ALL.add(PRIORITY);
     NAMES_ALL.add(QUERY_LIMIT);
+    NAMES_ALL.add(RUN_AS);
     NAMES_ALL.add(RUN_GC);
     NAMES_ALL.add(START_REPLICATION);
     NAMES_ALL.add(STREAM_EVENTS);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
index f014f5f..f143405 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
@@ -21,6 +21,7 @@
 
 /** Data sent as part of the host page, to bootstrap the UI. */
 public class HostPageData {
+  public String version;
   public Account account;
   public AccountDiffPreference accountDiffPref;
   public String xGerritAuth;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ListBranchesResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ListBranchesResult.java
deleted file mode 100644
index 1c830e9..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ListBranchesResult.java
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (C) 2010 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.common.data;
-
-import com.google.gerrit.reviewdb.client.Branch;
-
-import java.util.List;
-
-/**
- * It holds list of branches and boolean to indicate if it is allowed to add new
- * branches.
- */
-public final class ListBranchesResult {
-  protected boolean noRepository;
-  protected boolean canAdd;
-  protected List<Branch> branches;
-
-  protected ListBranchesResult() {
-  }
-
-  public ListBranchesResult(List<Branch> branches, boolean canAdd,
-      boolean noRepository) {
-    this.branches = branches;
-    this.canAdd = canAdd;
-    this.noRepository = noRepository;
-  }
-
-  public boolean getNoRepository() {
-    return noRepository;
-  }
-
-  public boolean getCanAdd() {
-    return canAdd;
-  }
-
-  public List<Branch> getBranches() {
-    return branches;
-  }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
index fecbb76..914e69f 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
@@ -48,6 +48,8 @@
   protected List<Edit> edits;
   protected DisplayMethod displayMethodA;
   protected DisplayMethod displayMethodB;
+  protected transient String mimeTypeA;
+  protected transient String mimeTypeB;
   protected CommentDetail comments;
   protected List<Patch> history;
   protected boolean hugeFile;
@@ -60,8 +62,9 @@
       final List<String> h, final AccountDiffPreference dp,
       final SparseFileContent ca, final SparseFileContent cb,
       final List<Edit> e, final DisplayMethod ma, final DisplayMethod mb,
-      final CommentDetail cd, final List<Patch> hist, final boolean hf,
-      final boolean id, final boolean idf, final boolean idt) {
+      final String mta, final String mtb, final CommentDetail cd,
+      final List<Patch> hist, final boolean hf, final boolean id,
+      final boolean idf, final boolean idt) {
     changeId = ck;
     changeType = ct;
     oldName = on;
@@ -75,6 +78,8 @@
     edits = e;
     displayMethodA = ma;
     displayMethodB = mb;
+    mimeTypeA = mta;
+    mimeTypeB = mtb;
     comments = cd;
     history = hist;
     hugeFile = hf;
@@ -170,6 +175,14 @@
     return b;
   }
 
+  public String getMimeTypeA() {
+    return mimeTypeA;
+  }
+
+  public String getMimeTypeB() {
+    return mimeTypeB;
+  }
+
   public List<Edit> getEdits() {
     return edits;
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetDetail.java
index 39f5cb0..9f4da74 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetDetail.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.Project;
 
+import java.util.Collections;
 import java.util.List;
 
 public class PatchSetDetail {
@@ -26,6 +27,7 @@
   protected PatchSetInfo info;
   protected List<Patch> patches;
   protected Project.NameKey project;
+  protected List<UiCommandDetail> commands;
 
   public PatchSetDetail() {
   }
@@ -61,4 +63,15 @@
   public void setProject(final Project.NameKey p) {
     project = p;
   }
+
+  public List<UiCommandDetail> getCommands() {
+    if (commands != null) {
+      return commands;
+    }
+    return Collections.emptyList();
+  }
+
+  public void setCommands(List<UiCommandDetail> cmds) {
+    commands = cmds.isEmpty() ? null : cmds;
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 0585651..69263d9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -29,6 +29,7 @@
   public static final String FORGE_COMMITTER = "forgeCommitter";
   public static final String FORGE_SERVER = "forgeServerAsCommitter";
   public static final String LABEL = "label-";
+  public static final String LABEL_AS = "labelAs-";
   public static final String OWNER = "owner";
   public static final String PUBLISH_DRAFTS = "publishDrafts";
   public static final String PUSH = "push";
@@ -43,6 +44,7 @@
 
   private static final List<String> NAMES_LC;
   private static final int labelIndex;
+  private static final int labelAsIndex;
 
   static {
     NAMES_LC = new ArrayList<String>();
@@ -58,6 +60,7 @@
     NAMES_LC.add(PUSH_TAG.toLowerCase());
     NAMES_LC.add(PUSH_SIGNED_TAG.toLowerCase());
     NAMES_LC.add(LABEL.toLowerCase());
+    NAMES_LC.add(LABEL_AS.toLowerCase());
     NAMES_LC.add(REBASE.toLowerCase());
     NAMES_LC.add(REMOVE_REVIEWER.toLowerCase());
     NAMES_LC.add(SUBMIT.toLowerCase());
@@ -67,15 +70,18 @@
     NAMES_LC.add(PUBLISH_DRAFTS.toLowerCase());
 
     labelIndex = NAMES_LC.indexOf(Permission.LABEL);
+    labelAsIndex = NAMES_LC.indexOf(Permission.LABEL_AS.toLowerCase());
   }
 
   /** @return true if the name is recognized as a permission name. */
   public static boolean isPermission(String varName) {
-    String lc = varName.toLowerCase();
-    if (lc.startsWith(LABEL)) {
-      return LABEL.length() < lc.length();
-    }
-    return NAMES_LC.contains(lc);
+    return isLabel(varName)
+        || isLabelAs(varName)
+        || NAMES_LC.contains(varName.toLowerCase());
+  }
+
+  public static boolean hasRange(String varName) {
+    return isLabel(varName) || isLabelAs(varName);
   }
 
   /** @return true if the permission name is actually for a review label. */
@@ -83,11 +89,30 @@
     return varName.startsWith(LABEL) && LABEL.length() < varName.length();
   }
 
+  /** @return true if the permission is for impersonated review labels. */
+  public static boolean isLabelAs(String var) {
+    return var.startsWith(LABEL_AS) && LABEL_AS.length() < var.length();
+  }
+
   /** @return permission name for the given review label. */
   public static String forLabel(String labelName) {
     return LABEL + labelName;
   }
 
+  /** @return permission name to apply a label for another user. */
+  public static String forLabelAs(String labelName) {
+    return LABEL_AS + labelName;
+  }
+
+  public static String extractLabel(String varName) {
+    if (isLabel(varName)) {
+      return varName.substring(LABEL.length());
+    } else if (isLabelAs(varName)) {
+      return varName.substring(LABEL_AS.length());
+    }
+    return null;
+  }
+
   public static boolean canBeOnAllProjects(String ref, String permissionName) {
     if (AccessSection.ALL.equals(ref)) {
       return !OWNER.equals(permissionName);
@@ -110,15 +135,8 @@
     return name;
   }
 
-  public boolean isLabel() {
-    return isLabel(getName());
-  }
-
   public String getLabel() {
-    if (isLabel()) {
-      return getName().substring(LABEL.length());
-    }
-    return null;
+    return extractLabel(getName());
   }
 
   public Boolean getExclusiveGroup() {
@@ -223,8 +241,10 @@
   }
 
   private static int index(Permission a) {
-    if (a.isLabel()) {
+    if (isLabel(a.getName())) {
       return labelIndex;
+    } else if (isLabelAs(a.getName())) {
+      return labelAsIndex;
     }
 
     int index = NAMES_LC.indexOf(a.getName().toLowerCase());
@@ -241,8 +261,8 @@
     if (!name.equals(other.name) || exclusiveGroup != other.exclusiveGroup) {
       return false;
     }
-    return new HashSet<PermissionRule>(rules)
-        .equals(new HashSet<PermissionRule>(other.rules));
+    return new HashSet<PermissionRule>(getRules())
+        .equals(new HashSet<PermissionRule>(other.getRules()));
   }
 
   @Override
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
index 3490dd7..0363fd6 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
@@ -86,7 +86,7 @@
   }
 
   public String getLabel() {
-    return isLabel() ? getName().substring(Permission.LABEL.length()) : null;
+    return Permission.extractLabel(getName());
   }
 
   public int getMin() {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
index 904c5c7..ee6cc95 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class ProjectAccess {
@@ -28,6 +29,7 @@
   protected boolean isConfigVisible;
   protected boolean canUpload;
   protected LabelTypes labelTypes;
+  protected Map<String, String> capabilities;
 
   public ProjectAccess() {
   }
@@ -112,4 +114,12 @@
   public void setLabelTypes(LabelTypes labelTypes) {
     this.labelTypes = labelTypes;
   }
+
+  public Map<String, String> getCapabilities() {
+    return capabilities;
+  }
+
+  public void setCapabilities(Map<String, String> capabilities) {
+    this.capabilities = capabilities;
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
index 0638906..dc91502 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwtjsonrpc.common.AsyncCallback;
@@ -25,7 +24,6 @@
 import com.google.gwtjsonrpc.common.RpcImpl.Version;
 
 import java.util.List;
-import java.util.Set;
 
 @RpcImpl(version = Version.V2_0)
 public interface ProjectAdminService extends RemoteJsonService {
@@ -52,17 +50,4 @@
   void reviewProjectAccess(Project.NameKey projectName, String baseRevision,
       String message, List<AccessSection> sections,
       AsyncCallback<Change.Id> callback);
-
-  void listBranches(Project.NameKey projectName,
-      AsyncCallback<ListBranchesResult> callback);
-
-  @Audit
-  @SignInRequired
-  void addBranch(Project.NameKey projectName, String branchName,
-      String startingRevision, AsyncCallback<AddBranchResult> callback);
-
-  @Audit
-  @SignInRequired
-  void deleteBranch(Project.NameKey projectName, Set<Branch.NameKey> ids,
-      AsyncCallback<Set<Branch.NameKey>> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java
index 1e7589b..17ea73c 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java
@@ -24,6 +24,7 @@
   public boolean canModifyAgreements;
   public boolean canModifyAccess;
   public boolean canModifyState;
+  public boolean canModifyMaxObjectSizeLimit;
   public boolean isPermissionOnly;
   public InheritedBoolean useContributorAgreements;
   public InheritedBoolean useSignedOffBy;
@@ -49,6 +50,10 @@
     canModifyState = cms;
   }
 
+  public void setCanModifyMaxObjectSizeLimit(final boolean cmmosl) {
+    canModifyMaxObjectSizeLimit = cmmosl;
+  }
+
   public void setCanModifyAgreements(final boolean cma) {
     canModifyAgreements = cma;
   }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
similarity index 71%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
index b2fb901..cd011860 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/UiCommandDetail.java
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.common.data;
 
-import java.io.IOException;
-import java.io.OutputStream;
-
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+/** Detail necessary to display an action. */
+public class UiCommandDetail {
+  public String id;
+  public String method;
+  public String label;
+  public String title;
+  public boolean enabled;
 }
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
new file mode 100644
index 0000000..4145636
--- /dev/null
+++ b/gerrit-extension-api/BUCK
@@ -0,0 +1,14 @@
+SRCS = glob(['src/main/java/com/google/gerrit/extensions/**/*.java'])
+
+java_library2(
+  name = 'api',
+  srcs = SRCS,
+  compile_deps = ['//lib/guice:guice'],
+  visibility = ['PUBLIC'],
+)
+
+java_sources(
+  name = 'api-src',
+  srcs = SRCS,
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
deleted file mode 100644
index 4f57209..0000000
--- a/gerrit-extension-api/pom.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-extension-api</artifactId>
-  <name>Gerrit Code Review - Extension API</name>
-
-  <description>
-    Interfaces describing the extension API
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.inject</groupId>
-      <artifactId>guice</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.inject.extensions</groupId>
-      <artifactId>guice-servlet</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>servlet-api</artifactId>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-shade-plugin</artifactId>
-        <configuration>
-          <createSourcesJar>true</createSourcesJar>
-          <shadedArtifactAttached>true</shadedArtifactAttached>
-          <shadedClassifierName>all</shadedClassifierName>
-        </configuration>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>shade</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
index 40bbb80..a4c5eb5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java
@@ -22,6 +22,7 @@
 import com.google.inject.util.Types;
 
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.SortedMap;
 import java.util.SortedSet;
@@ -39,7 +40,7 @@
  * internally, and resolve the provider to an instance on demand. This enables
  * registrations to decide between singleton and non-singleton members.
  */
-public abstract class DynamicMap<T> {
+public abstract class DynamicMap<T> implements Iterable<DynamicMap.Entry<T>> {
   /**
    * Declare a singleton {@code DynamicMap<T>} with a binder.
    * <p>
@@ -136,6 +137,50 @@
     return Collections.unmodifiableSortedMap(r);
   }
 
+  /** Iterate through all entries in an undefined order. */
+  public Iterator<Entry<T>> iterator() {
+    final Iterator<Map.Entry<NamePair, Provider<T>>> i =
+        items.entrySet().iterator();
+    return new Iterator<Entry<T>>() {
+      @Override
+      public boolean hasNext() {
+        return i.hasNext();
+      }
+
+      @Override
+      public Entry<T> next() {
+        final Map.Entry<NamePair, Provider<T>> e = i.next();
+        return new Entry<T>() {
+          @Override
+          public String getPluginName() {
+            return e.getKey().pluginName;
+          }
+
+          @Override
+          public String getExportName() {
+            return e.getKey().exportName;
+          }
+
+          @Override
+          public Provider<T> getProvider() {
+            return e.getValue();
+          }
+        };
+      }
+
+      @Override
+      public void remove() {
+        throw new UnsupportedOperationException();
+      }
+    };
+  }
+
+  public interface Entry<T> {
+    String getPluginName();
+    String getExportName();
+    Provider<T> getProvider();
+  }
+
   static class NamePair {
     private final String pluginName;
     private final String exportName;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
index ec34887..b2f19e5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -26,6 +26,7 @@
 import com.google.inject.util.Types;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -127,6 +128,11 @@
     return binder.bind(type).annotatedWith(name);
   }
 
+  public static <T> DynamicSet<T> emptySet() {
+    return new DynamicSet<T>(
+        Collections.<AtomicReference<Provider<T>>> emptySet());
+  }
+
   private final CopyOnWriteArrayList<AtomicReference<Provider<T>>> items;
 
   DynamicSet(Collection<AtomicReference<Provider<T>>> base) {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
index 188011c..103874f 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
@@ -61,6 +61,7 @@
   private String characterEncoding;
   private long contentLength = -1;
   private boolean gzip = true;
+  private boolean base64 = false;
 
   /** @return the MIME type of the result, for HTTP clients. */
   public String getContentType() {
@@ -110,6 +111,17 @@
     return this;
   }
 
+  /** @return true if the result must be base64 encoded. */
+  public boolean isBase64() {
+    return base64;
+  }
+
+  /** Wrap the binary data in base64 encoding. */
+  public BinaryResult base64() {
+    base64 = true;
+    return this;
+  }
+
   /**
    * Write or copy the result onto the specified output stream.
    *
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java
new file mode 100644
index 0000000..a8ffe94
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java
@@ -0,0 +1,56 @@
+// 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.restapi;
+
+import java.util.concurrent.TimeUnit;
+
+public class CacheControl {
+
+  public enum Type {
+    NONE, PUBLIC, PRIVATE;
+  }
+
+  public final static CacheControl NONE = new CacheControl(Type.NONE, 0, null);
+
+  public static CacheControl PUBLIC(long age, TimeUnit unit) {
+    return new CacheControl(Type.PUBLIC, age, unit);
+  }
+
+  public static CacheControl PRIVATE(long age, TimeUnit unit) {
+    return new CacheControl(Type.PRIVATE, age, unit);
+  }
+
+  private final Type type;
+  private final long age;
+  private final TimeUnit unit;
+
+  private CacheControl(Type type, long age, TimeUnit unit) {
+    this.type = type;
+    this.age = age;
+    this.unit = unit;
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  public long getAge() {
+    return age;
+  }
+
+  public TimeUnit getUnit() {
+    return unit;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/IdString.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/IdString.java
index f987425..f0f7dea 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/IdString.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/IdString.java
@@ -25,6 +25,11 @@
     return new IdString(id);
   }
 
+  /** Construct an identifier from an already decoded string. */
+  public static IdString fromDecoded(String id) {
+    return new IdString(Url.encode(id));
+  }
+
   private final String urlEncoded;
 
   private IdString(String s) {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/MethodNotAllowedException.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/MethodNotAllowedException.java
index 8b0fdd3..61c6345 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/MethodNotAllowedException.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/MethodNotAllowedException.java
@@ -17,4 +17,13 @@
 /** Method is not acceptable on the resource (HTTP 405 Method Not Allowed). */
 public class MethodNotAllowedException extends RestApiException {
   private static final long serialVersionUID = 1L;
+
+  public MethodNotAllowedException() {
+    super();
+  }
+
+  /** @param msg error text for client describing why the method is not allowed. */
+  public MethodNotAllowedException(String msg) {
+    super(msg);
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RawInput.java
similarity index 90%
rename from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RawInput.java
index b2d62b3..4f195e4 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RawInput.java
@@ -17,9 +17,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 
-
-/** Raw data stream supplied by the body of a PUT. */
-public interface PutInput {
+/** Raw data stream supplied by the body of a PUT or POST. */
+public interface RawInput {
   String getContentType();
   long getContentLength();
   InputStream getInputStream() throws IOException;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
index aa891c9..0e358ec 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/ResourceNotFoundException.java
@@ -31,4 +31,10 @@
   public ResourceNotFoundException(IdString id) {
     super(id.get());
   }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public ResourceNotFoundException caching(CacheControl c) {
+    return super.caching(c);
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java
index 97c4cbc..848004d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/Response.java
@@ -50,11 +50,14 @@
 
   public abstract int statusCode();
   public abstract T value();
+  public abstract CacheControl caching();
+  public abstract Response<T> caching(CacheControl c);
   public abstract String toString();
 
   private static final class Impl<T> extends Response<T> {
     private final int statusCode;
     private final T value;
+    private CacheControl caching = CacheControl.NONE;
 
     private Impl(int sc, T val) {
       statusCode = sc;
@@ -72,6 +75,17 @@
     }
 
     @Override
+    public CacheControl caching() {
+      return caching;
+    }
+
+    @Override
+    public Response<T> caching(CacheControl c) {
+      caching = c;
+      return this;
+    }
+
+    @Override
     public String toString() {
       return "[" + statusCode() + "] " + value();
     }
@@ -91,6 +105,16 @@
     }
 
     @Override
+    public CacheControl caching() {
+      return CacheControl.NONE;
+    }
+
+    @Override
+    public Response<Object> caching(CacheControl c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public String toString() {
       return "[204 No Content] None";
     }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestApiException.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestApiException.java
index a6d27cd..3fae128 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestApiException.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestApiException.java
@@ -17,6 +17,7 @@
 /** Root exception type for JSON API failures. */
 public abstract class RestApiException extends Exception {
   private static final long serialVersionUID = 1L;
+  private CacheControl caching = CacheControl.NONE;
 
   public RestApiException() {
   }
@@ -28,4 +29,14 @@
   public RestApiException(String msg, Throwable cause) {
     super(msg, cause);
   }
+
+  public CacheControl caching() {
+    return caching;
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T extends RestApiException> T caching(CacheControl c) {
+    caching = c;
+    return (T) this;
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PrivateInternals_UiActionDescription.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PrivateInternals_UiActionDescription.java
new file mode 100644
index 0000000..6545db8
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/PrivateInternals_UiActionDescription.java
@@ -0,0 +1,33 @@
+// 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.webui;
+
+/**
+ * Internal implementation helper for Gerrit Code Review server.
+ * <p>
+ * Extensions and plugins should not invoke this class.
+ */
+public class PrivateInternals_UiActionDescription {
+  public static void setMethod(UiAction.Description d, String method) {
+    d.setMethod(method);
+  }
+
+  public static void setId(UiAction.Description d, String id) {
+    d.setId(id);
+  }
+
+  private PrivateInternals_UiActionDescription() {
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java
new file mode 100644
index 0000000..63172ce
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java
@@ -0,0 +1,104 @@
+// 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.webui;
+
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+
+public interface UiAction<R extends RestResource> extends RestView<R> {
+  /**
+   * Get the description of the action customized for the resource.
+   *
+   * @param resource the resource the view would act upon if the action is
+   *        invoked by the client. Information from the resource can be used to
+   *        customize the description.
+   * @return a description of the action. The server will populate the
+   *         {@code id} and {@code method} properties. If null the action will
+   *         assumed unavailable and not presented. This is usually the same as
+   *         {@code setVisible(false)}.
+   */
+  public Description getDescription(R resource);
+
+  /** Describes an action invokable through the web interface. */
+  public static class Description {
+    private String method;
+    private String id;
+    private String label;
+    private String title;
+    private boolean visible = true;
+    private boolean enabled = true;
+
+    public String getMethod() {
+      return method;
+    }
+
+    /** {@code PrivateInternals_UiActionDescription.setMethod()} */
+    void setMethod(String method) {
+      this.method = method;
+    }
+
+    public String getId() {
+      return id;
+    }
+
+    /** {@code PrivateInternals_UiActionDescription.setId()} */
+    void setId(String id) {
+      this.id = id;
+    }
+
+    public String getLabel() {
+      return label;
+    }
+
+    /** Set the label to appear on the button to activate this action. */
+    public Description setLabel(String label) {
+      this.label = label;
+      return this;
+    }
+
+    public String getTitle() {
+      return title;
+    }
+
+    /** Set the tool-tip text to appear when the mouse hovers on the button. */
+    public Description setTitle(String title) {
+      this.title = title;
+      return this;
+    }
+
+    public boolean isVisible() {
+      return visible;
+    }
+
+    /**
+     * Set if the action's button is visible on screen for the current client.
+     * If not visible the action description may not be sent to the client.
+     */
+    public Description setVisible(boolean visible) {
+      this.visible = visible;
+      return this;
+    }
+
+    public boolean isEnabled() {
+      return enabled && isVisible();
+    }
+
+    /** Set if the button should be invokable (true), or greyed out (false). */
+    public Description setEnabled(boolean enabled) {
+      this.enabled = enabled;
+      return this;
+    }
+  }
+}
diff --git a/gerrit-gwtdebug/BUCK b/gerrit-gwtdebug/BUCK
new file mode 100644
index 0000000..308c2f1
--- /dev/null
+++ b/gerrit-gwtdebug/BUCK
@@ -0,0 +1,6 @@
+java_library(
+  name = 'gwtdebug',
+  srcs = ['src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java'],
+  deps = ['//lib/gwt:dev'],
+  visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
deleted file mode 100644
index 0622e1b..0000000
--- a/gerrit-gwtdebug/pom.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-gwtdebug</artifactId>
-  <name>Gerrit Code Review - GWT UI Debugging Support</name>
-
-  <description>
-    Debugging support for the GWT UI
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-gwtui</artifactId>
-      <version>${project.version}</version>
-      <classifier>classes</classifier>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-war</artifactId>
-      <version>${project.version}</version>
-      <classifier>classes</classifier>
-      <exclusions>
-        <exclusion>
-          <groupId>com.google.gerrit</groupId>
-          <artifactId>gerrit-pgm</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-
-    <dependency>
-      <groupId>com.h2database</groupId>
-      <artifactId>h2</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>postgresql</groupId>
-      <artifactId>postgresql</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>bouncycastle</groupId>
-      <artifactId>bcprov-jdk15</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>bouncycastle</groupId>
-      <artifactId>bcpg-jdk15</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <!-- GWT should require these itself, but doesn't. -->
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-      <classifier>sources</classifier>
-      <scope>provided</scope>
-    </dependency>
-
-     <!-- Workaround for overwriting our dependencies (like args4j) by additional
-      classes put in gwt-dev.jar -->
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-dev</artifactId>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
index d23aa35..a2a770e 100644
--- a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
+++ b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
@@ -381,9 +381,15 @@
     Server server = new Server();
     server.addConnector(connector);
 
-    // warDir is "$top/gerrit-gwtui/target/gwt-hosted-mode"
-    //
-    File top = warDir.getParentFile().getParentFile().getParentFile();
+    File top;
+    String root = System.getProperty("gerrit.source_root");
+    if (root != null) {
+      top = new File(root);
+    } else {
+      // Under Maven warDir is "$top/gerrit-gwtui/target/gwt-hosted-mode"
+      top = warDir.getParentFile().getParentFile().getParentFile();
+    }
+
     File app = new File(top, "gerrit-war/src/main/webapp");
     File webxml = new File(app, "WEB-INF/web.xml");
 
diff --git a/gerrit-gwtexpui/BUCK b/gerrit-gwtexpui/BUCK
new file mode 100644
index 0000000..105ee7d
--- /dev/null
+++ b/gerrit-gwtexpui/BUCK
@@ -0,0 +1,104 @@
+SRC = 'src/main/java/com/google/gwtexpui/'
+
+gwt_module(
+  name = 'Clippy',
+  srcs = glob([SRC + 'clippy/client/*.java']),
+  gwtxml = SRC + 'clippy/Clippy.gwt.xml',
+  resources = [
+    SRC + 'clippy/client/clippy.css',
+    SRC + 'clippy/client/clippy.swf',
+  ],
+  deps = [
+    ':SafeHtml',
+    ':UserAgent',
+    '//lib/gwt:user',
+    '//lib:LICENSE-clippy',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+gwt_module(
+  name = 'CSS',
+  srcs = glob([SRC + 'css/rebind/*.java']),
+  gwtxml = SRC + 'css/CSS.gwt.xml',
+  deps = ['//lib/gwt:dev'],
+  visibility = ['PUBLIC'],
+)
+
+gwt_module(
+  name = 'GlobalKey',
+  srcs = glob([SRC + 'globalkey/client/*.java']),
+  gwtxml = SRC + 'globalkey/GlobalKey.gwt.xml',
+  resources = [
+    SRC + 'globalkey/client/KeyConstants.properties',
+    SRC + 'globalkey/client/key.css',
+  ],
+  deps = [
+    ':SafeHtml',
+    ':UserAgent',
+    '//lib/gwt:user',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+gwt_module(
+  name = 'Linker',
+  srcs = glob([SRC + 'linker/rebind/*.java']),
+  gwtxml = SRC + 'linker/ServerPlannedIFrameLinker.gwt.xml',
+  deps = ['//lib/gwt:dev'],
+  visibility = ['PUBLIC'],
+)
+
+java_library2(
+  name = 'linker_server',
+  srcs = glob([SRC + 'linker/server/*.java']),
+  compile_deps = ['//lib:servlet-api-3_0'],
+  visibility = ['PUBLIC'],
+)
+
+gwt_module(
+  name = 'Progress',
+  srcs = glob([SRC + 'progress/client/*.java']),
+  gwtxml = SRC + 'progress/Progress.gwt.xml',
+  resources = [SRC + 'progress/client/progress.css'],
+  deps = ['//lib/gwt:user'],
+  visibility = ['PUBLIC'],
+)
+
+gwt_module(
+  name = 'SafeHtml',
+  srcs = glob([SRC + 'safehtml/client/*.java']),
+  gwtxml = SRC + 'safehtml/SafeHtml.gwt.xml',
+  resources = [SRC + 'safehtml/client/safehtml.css'],
+  deps = ['//lib/gwt:user'],
+  visibility = ['PUBLIC'],
+)
+
+java_test(
+  name = 'SafeHtml_tests',
+  srcs = glob([
+    'src/test/java/com/google/gwtexpui/safehtml/client/**/*.java',
+  ]),
+  deps = [
+    ':SafeHtml',
+    '//lib:junit',
+    '//lib/gwt:user',
+    '//lib/gwt:dev',
+  ],
+  source_under_test = [':SafeHtml'],
+)
+
+gwt_module(
+  name = 'UserAgent',
+  srcs = glob([SRC + 'user/client/*.java']),
+  gwtxml = SRC + 'user/User.gwt.xml',
+  deps = ['//lib/gwt:user'],
+  visibility = ['PUBLIC'],
+)
+
+java_library2(
+  name = 'server',
+  srcs = glob([SRC + 'server/*.java']),
+  compile_deps = ['//lib:servlet-api-3_0'],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-gwtexpui/pom.xml b/gerrit-gwtexpui/pom.xml
deleted file mode 100644
index 86845d3..0000000
--- a/gerrit-gwtexpui/pom.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-gwtexpui</artifactId>
-
-  <name>Gerrit Code Review - GWT expui</name>
-  <description>Extended UI tools for GWT</description>
-
-  <build>
-    <plugins>
-      <plugin>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-
-    <extensions>
-      <extension>
-        <groupId>com.googlesource.gerrit</groupId>
-        <artifactId>gs-maven-wagon</artifactId>
-        <version>3.3</version>
-      </extension>
-    </extensions>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-user</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-dev</artifactId>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyResources.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyResources.java
index 4c2b8981..dfa7679 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyResources.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/ClippyResources.java
@@ -16,10 +16,16 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.DataResource.DoNotEmbed;
 
 public interface ClippyResources extends ClientBundle {
   public static final ClippyResources I = GWT.create(ClippyResources.class);
 
   @Source("clippy.css")
   ClippyCss css();
+
+  @Source("clippy.swf")
+  @DoNotEmbed
+  DataResource swf();
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
index 273318b..baae37a 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
@@ -14,7 +14,6 @@
 
 package com.google.gwtexpui.clippy.client;
 
-import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.event.dom.client.BlurEvent;
 import com.google.gwt.event.dom.client.BlurHandler;
@@ -47,7 +46,6 @@
 public class CopyableLabel extends Composite implements HasText {
   private static final int SWF_WIDTH = 110;
   private static final int SWF_HEIGHT = 14;
-  private static String swfUrl;
   private static boolean flashEnabled = true;
 
   static {
@@ -63,10 +61,7 @@
   }
 
   private static String swfUrl() {
-    if (swfUrl == null) {
-      swfUrl = GWT.getModuleBaseURL() + "gwtexpui_clippy1.cache.swf";
-    }
-    return swfUrl;
+    return ClippyResources.I.swf().getSafeUri().asString();
   }
 
   private final FlowPanel content;
@@ -76,6 +71,10 @@
   private TextBox textBox;
   private Element swf;
 
+  public CopyableLabel() {
+    this("");
+  }
+
   /**
    * Create a new label
    *
@@ -122,7 +121,6 @@
   public void setPreviewText(final String text) {
     if (textLabel != null) {
       textLabel.setText(text);
-      visibleLen = text.length();
     }
   }
 
@@ -183,6 +181,7 @@
       textBox = new TextBox();
       textBox.setText(getText());
       textBox.setVisibleLength(visibleLen);
+      textBox.setReadOnly(true);
       textBox.addKeyPressHandler(new KeyPressHandler() {
         @Override
         public void onKeyPress(final KeyPressEvent event) {
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/public/gwtexpui_clippy1.cache.swf b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clippy.swf
similarity index 100%
rename from gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/public/gwtexpui_clippy1.cache.swf
rename to gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/clippy.swf
Binary files differ
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
index 1eaaa3c..daf5d61 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
@@ -15,6 +15,8 @@
 package com.google.gwtexpui.globalkey.client;
 
 import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwt.event.logical.shared.CloseEvent;
@@ -92,6 +94,14 @@
     active = new State(panel);
     active.add(new HidePopupPanelCommand(0, KeyCodes.KEY_ESCAPE, panel));
     panel.addCloseHandler(restoreGlobal);
+    panel.addDomHandler(new KeyDownHandler() {
+      @Override
+      public void onKeyDown(KeyDownEvent event) {
+        if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+          panel.hide();
+        }
+      }
+    }, KeyDownEvent.getType());
   }
 
   public static HandlerRegistration addApplication(final Widget widget,
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
index 7bd0233..49c1b01 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
@@ -16,8 +16,11 @@
 
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlowPanel;
@@ -38,7 +41,7 @@
 
 
 public class KeyHelpPopup extends PluginSafePopupPanel implements
-    KeyPressHandler {
+    KeyPressHandler, KeyDownHandler {
   private final FocusPanel focus;
 
   public KeyHelpPopup() {
@@ -77,6 +80,7 @@
     DOM.setStyleAttribute(focus.getElement(), "outline", "0px");
     DOM.setElementAttribute(focus.getElement(), "hideFocus", "true");
     focus.addKeyPressHandler(this);
+    focus.addKeyDownHandler(this);
     add(focus);
   }
 
@@ -100,6 +104,13 @@
     hide();
   }
 
+  @Override
+  public void onKeyDown(final KeyDownEvent event) {
+    if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+      hide();
+    }
+  }
+
   private void populate(final Grid lists) {
     int end[] = new int[5];
     int column = 0;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpFlowPanel.java
similarity index 67%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpFlowPanel.java
index b2fb901..7ae7fd0 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/NpFlowPanel.java
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gwtexpui.globalkey.client;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.user.client.ui.FlowPanel;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class NpFlowPanel extends FlowPanel {
+  public NpFlowPanel() {
+    addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
+  }
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
index e2c576b..61bec18 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
@@ -79,7 +79,18 @@
       if (!html) {
         ds = escape(ds);
       }
-      displayString = sgi(ds, qstr, "<strong>$1</strong>");
+
+      // We now surround qstr by <strong>. But the chosen approach is not too
+      // smooth, if qstr is small (e.g.: "t") and this small qstr may occur in
+      // escapes (e.g.: "Tim &lt;email@example.org&gt;"). Those escapes will
+      // get <strong>-ed as well (e.g.: "&lt;" -> "&<strong>l</strong>t;"). But
+      // as repairing those mangled escapes is easier than not mangling them in
+      // the first place, we repair them afterwards.
+      ds = sgi(ds, qstr, "<strong>$1</strong>");
+      // Repairing <strong>-ed escapes.
+      ds = sgi(ds, "(&[a-z]*)<strong>([a-z]*)</strong>([a-z]*;)", "$1$2$3");
+
+      displayString = ds;
     }
 
     private static native String sgi(String inString, String pat, String newHtml)
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
index 0a9f7a2..41e4abc 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
@@ -29,7 +29,9 @@
 import java.util.List;
 
 /** Immutable string safely placed as HTML without further escaping. */
-public abstract class SafeHtml {
+@SuppressWarnings("serial")
+public abstract class SafeHtml
+    implements com.google.gwt.safehtml.shared.SafeHtml {
   public static final SafeHtmlResources RESOURCES;
 
   static {
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
index 9fe3267..8ff99ee 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
@@ -19,6 +19,7 @@
 /**
  * Safely constructs a {@link SafeHtml}, escaping user provided content.
  */
+@SuppressWarnings("serial")
 public class SafeHtmlBuilder extends SafeHtml {
   private static final Impl impl;
 
@@ -317,6 +318,16 @@
     return closeElement("td");
   }
 
+  /** Append "&lt;th&gt;"; attributes may be set if needed */
+  public SafeHtmlBuilder openTh() {
+    return openElement("th");
+  }
+
+  /** Append "&lt;/th&gt;" */
+  public SafeHtmlBuilder closeTh() {
+    return closeElement("th");
+  }
+
   /** Append "&lt;div&gt;"; attributes may be set if needed */
   public SafeHtmlBuilder openDiv() {
     return openElement("div");
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlString.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlString.java
index a229421..57392bf 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlString.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlString.java
@@ -14,6 +14,7 @@
 
 package com.google.gwtexpui.safehtml.client;
 
+@SuppressWarnings("serial")
 class SafeHtmlString extends SafeHtml {
   private final String html;
 
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
index c4d681f..2033c62 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
@@ -72,17 +72,14 @@
 
   private static boolean cacheForever(final String pathInfo,
       final HttpServletRequest req) {
-    if (pathInfo.endsWith(".cache.html")) {
-      return true;
-    } else if (pathInfo.endsWith(".cache.gif")) {
-      return true;
-    } else if (pathInfo.endsWith(".cache.png")) {
-      return true;
-    } else if (pathInfo.endsWith(".cache.css")) {
-      return true;
-    } else if (pathInfo.endsWith(".cache.jar")) {
-      return true;
-    } else if (pathInfo.endsWith(".cache.swf")) {
+    if (pathInfo.endsWith(".cache.html")
+        || pathInfo.endsWith(".cache.gif")
+        || pathInfo.endsWith(".cache.png")
+        || pathInfo.endsWith(".cache.css")
+        || pathInfo.endsWith(".cache.jar")
+        || pathInfo.endsWith(".cache.swf")
+        || pathInfo.endsWith(".cache.txt")
+        || pathInfo.endsWith(".cache.js")) {
       return true;
     } else if (pathInfo.endsWith(".nocache.js")) {
       final String v = req.getParameter("content");
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
new file mode 100644
index 0000000..ef8d3cb
--- /dev/null
+++ b/gerrit-gwtui/BUCK
@@ -0,0 +1,104 @@
+include_defs('//gerrit-gwtui/DEFS')
+
+genrule(
+  name = 'ui_optdbg',
+  cmd = 'cd $TMP;' +
+    'unzip -q $SRCDIR/ui_dbg.zip;' +
+    'mv' +
+    ' gerrit_ui/gerrit_ui.nocache.js' +
+    ' gerrit_ui/gerrit_dbg.nocache.js;' +
+    'unzip -qo $SRCDIR/ui_opt.zip;' +
+    'mkdir -p $(dirname $OUT);' +
+    'zip -qr $OUT .',
+  srcs = [
+    genfile('ui_dbg.zip'),
+    genfile('ui_opt.zip'),
+  ],
+  deps = [
+    ':ui_dbg',
+    ':ui_opt',
+  ],
+  out = 'ui_optdbg.zip',
+  visibility = ['PUBLIC'],
+)
+
+gwt_application(
+  name = 'ui_opt',
+  module_target = MODULE,
+  compiler_opts = [
+    '-strict',
+    '-style', 'OBF',
+    '-optimize', '9',
+    '-XdisableClassMetadata',
+    '-XdisableCastChecking',
+  ],
+  deps = APP_DEPS,
+)
+
+gwt_application(
+  name = 'ui_dbg',
+  module_target = MODULE,
+  compiler_opts = DEBUG_OPTS + ['-strict'],
+  deps = APP_DEPS,
+  visibility = ['//:eclipse'],
+)
+
+gwt_user_agent_permutations(
+  name = 'ui',
+  module_name = 'gerrit_ui',
+  module_target = MODULE,
+  compiler_opts = DEBUG_OPTS + ['-draftCompile'],
+  browsers = BROWSERS,
+  deps = APP_DEPS,
+  visibility = ['//:'],
+)
+
+gwt_module(
+  name = 'ui_module',
+  srcs = glob(['src/main/java/**/*.java']),
+  gwtxml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'),
+  resources = glob(['src/main/java/**/*']),
+  deps = [
+    '//gerrit-gwtexpui:Clippy',
+    '//gerrit-gwtexpui:CSS',
+    '//gerrit-gwtexpui:GlobalKey',
+    '//gerrit-gwtexpui:Linker',
+    '//gerrit-gwtexpui:Progress',
+    '//gerrit-gwtexpui:SafeHtml',
+    '//gerrit-gwtexpui:UserAgent',
+    '//gerrit-common:client',
+    '//gerrit-patch-jgit:client',
+    '//gerrit-prettify:client',
+    '//gerrit-reviewdb:client',
+    '//lib:gwtjsonrpc',
+    '//lib:gwtjsonrpc_src',
+    '//lib:gwtorm',
+    '//lib:jsr305',
+    '//lib/codemirror:codemirror',
+    '//lib/gwt:user',
+    '//lib/jgit:jgit',
+  ],
+  visibility = [
+    '//tools/eclipse:classpath',
+    '//Documentation:licenses.txt',
+  ],
+)
+
+java_test(
+  name = 'ui_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  resources = glob(['src/test/resources/**/*']) + [
+    'src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml',
+  ],
+  deps = [
+    ':ui_module',
+    '//gerrit-common:client',
+    '//lib:junit',
+    '//lib/gwt:dev',
+    '//lib/gwt:user',
+    '//lib/gwt:gwt-test-utils',
+    '//lib/jgit:jgit',
+  ],
+  source_under_test = [':ui_module'],
+  visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-gwtui/DEFS b/gerrit-gwtui/DEFS
new file mode 100644
index 0000000..6ace5bf
--- /dev/null
+++ b/gerrit-gwtui/DEFS
@@ -0,0 +1,75 @@
+# 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.
+
+BROWSERS = [
+  'chrome',
+  'firefox',
+  'gecko1_8',
+  'safari',
+  'msie', 'ie6', 'ie8', 'ie9',
+]
+ALIASES = {
+  'chrome': 'safari',
+  'firefox': 'gecko1_8',
+  'msie': 'ie9',
+}
+MODULE = 'com.google.gerrit.GerritGwtUI'
+
+DEBUG_OPTS = [
+  '-style', 'PRETTY',
+  '-optimize', '0',
+]
+
+APP_DEPS = [':ui_module']
+
+def gwt_user_agent_permutations(
+    name,
+    module_name,
+    module_target,
+    compiler_opts = [],
+    deps = [],
+    browsers = [],
+    visibility = []):
+  for ua in browsers:
+    impl = ua
+    if ua in ALIASES:
+      impl = ALIASES[ua]
+    xml = ''.join([
+      "<module rename-to='%s'>" % module_name,
+      "<inherits name='%s'/>" % module_target,
+      "<set-property name='user.agent' value='%s'/>" % impl,
+      "<set-property name='locale' value='default'/>",
+      "</module>",
+    ])
+    gwt = 'resources/%s_%s.gwt.xml' % (module_target.replace('.', '/'), ua)
+    genrule(
+      name = '%s_%s_gwtxml_gen' % (name, ua),
+      cmd = 'mkdir -p $(dirname $OUT);echo "%s">$OUT' % xml,
+      srcs = [], 
+      deps = [],
+      out = gwt,
+    )
+    java_library(
+      name = '%s_%s_gwtxml_lib' % (name, ua),
+      resources = [genfile(gwt)],
+      deps = [':%s_%s_gwtxml_gen' % (name, ua)],
+    )
+    gwt_application(
+      name = '%s_%s' % (name, ua),
+      module_target = module_target + '_' + ua,
+      compiler_opts = compiler_opts,
+      deps = deps + [':%s_%s_gwtxml_lib' % (name, ua)],
+      visibility = visibility,
+    )
+
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
deleted file mode 100644
index 7d09989..0000000
--- a/gerrit-gwtui/pom.xml
+++ /dev/null
@@ -1,249 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-gwtui</artifactId>
-  <name>Gerrit Code Review - GWT UI</name>
-  <packaging>war</packaging>
-
-  <description>
-    Web interface built on top of Google Web Toolkit
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-user</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-gwtexpui</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-gwtexpui</artifactId>
-      <version>${project.version}</version>
-      <classifier>sources</classifier>
-      <type>jar</type>
-    </dependency>
-
-    <dependency>
-      <groupId>gwtjsonrpc</groupId>
-      <artifactId>gwtjsonrpc</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>gwtjsonrpc</groupId>
-      <artifactId>gwtjsonrpc</artifactId>
-      <classifier>sources</classifier>
-      <type>jar</type>
-    </dependency>
-
-    <dependency>
-      <groupId>gwtorm</groupId>
-      <artifactId>gwtorm</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>gwtorm</groupId>
-      <artifactId>gwtorm</artifactId>
-      <classifier>sources</classifier>
-      <type>jar</type>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-reviewdb</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-reviewdb</artifactId>
-      <version>${project.version}</version>
-      <classifier>sources</classifier>
-      <type>jar</type>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-common</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-common</artifactId>
-      <version>${project.version}</version>
-      <classifier>sources</classifier>
-      <type>jar</type>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-patch-jgit</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-patch-jgit</artifactId>
-      <version>${project.version}</version>
-      <classifier>sources</classifier>
-      <type>jar</type>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit</artifactId>
-      <classifier>sources</classifier>
-      <type>jar</type>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-prettify</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-prettify</artifactId>
-      <version>${project.version}</version>
-      <classifier>sources</classifier>
-      <type>jar</type>
-    </dependency>
-
-    <!-- GWT should require these itself, but doesn't. -->
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>javax.validation</groupId>
-      <artifactId>validation-api</artifactId>
-      <classifier>sources</classifier>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>gwt-maven-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>optimized</id>
-            <configuration>
-              <module>com.google.gerrit.GerritGwtUI</module>
-              <extraJvmArgs>-Xmx512m</extraJvmArgs>
-              <compileReport>${gwt.compileReport}</compileReport>
-              <disableClassMetadata>true</disableClassMetadata>
-              <disableCastChecking>true</disableCastChecking>
-            </configuration>
-            <goals>
-              <goal>compile</goal>
-            </goals>
-          </execution>
-          <execution>
-            <id>debug</id>
-            <configuration>
-              <style>PRETTY</style>
-              <module>com.google.gerrit.GerritGwtUI</module>
-              <extraJvmArgs>-Xmx512m</extraJvmArgs>
-              <disableClassMetadata>true</disableClassMetadata>
-              <disableRunAsync>true</disableRunAsync>
-              <webappDirectory>${project.build.directory}/${project.build.finalName}_dbg</webappDirectory>
-            </configuration>
-            <goals>
-              <goal>compile</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-antrun-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>compress-html</id>
-            <phase>prepare-package</phase>
-            <goals>
-              <goal>run</goal>
-            </goals>
-            <configuration>
-              <target>
-                <property name="dst" location="${project.build.directory}/${project.build.finalName}"/>
-                <property name="dbg" location="${project.build.directory}/${project.build.finalName}_dbg"/>
-                <property name="app" location="${dst}/gerrit_ui"/>
-
-                <mkdir dir="${app}"/>
-                <apply executable="gzip" addsourcefile="false">
-                  <arg value="-9"/>
-                  <fileset dir="${app}">
-                    <include name="**/*.html"/>
-                    <include name="**/*.css"/>
-                    <include name="deferredjs/**/*.js"/>
-                  </fileset>
-                  <redirector>
-                    <inputmapper type="glob" from="*" to="${app}/*"/>
-                    <outputmapper type="glob" from="*" to="${app}/*.gz"/>
-                  </redirector>
-                </apply>
-
-                <copy file="${dbg}/gerrit_ui/gerrit_ui.nocache.js"
-                      tofile="${app}/gerrit_dbg.nocache.js"/>
-                <copy todir="${app}" overwrite="false">
-                  <fileset dir="${dbg}/gerrit_ui">
-                    <include name="**/*.html"/>
-                    <include name="**/*.css"/>
-                    <include name="deferredjs/**/*.js"/>
-                  </fileset>
-                </copy>
-              </target>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-war-plugin</artifactId>
-        <configuration>
-          <packagingExcludes>WEB-INF/classes/**,WEB-INF/deploy/**,WEB-INF/lib/**</packagingExcludes>
-          <attachClasses>true</attachClasses>
-          <archive>
-            <addMavenDescriptor>false</addMavenDescriptor>
-          </archive>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
index 5fdc5bb..d33a525 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
@@ -28,11 +28,15 @@
   <inherits name='com.google.gerrit.Common'/>
   <inherits name='com.google.gerrit.UserAgent'/>
   <inherits name='org.eclipse.jgit.JGit'/>
+  <inherits name='net.codemirror.CodeMirror'/>
 
   <extend-property name='locale' values='en'/>
   <set-property-fallback name='locale' value='en'/>
   <set-property name='locale' value='en'/>
   <set-configuration-property name='UiBinder.useSafeHtmlTemplates' value='true'/>
 
+  <set-property name='gwt.logging.logLevel' value='SEVERE'/>
+  <set-property name='gwt.logging.popupHandler' value='DISABLED'/>
+
   <entry-point class='com.google.gerrit.client.Gerrit'/>
 </module>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationCallback.java
index e3b7525..8fef111 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationCallback.java
@@ -18,12 +18,18 @@
  * Interface that a caller must implement to react on the result of a
  * {@link ConfirmationDialog}.
  */
-public interface ConfirmationCallback {
+public abstract class ConfirmationCallback {
 
   /**
    * Called when the {@link ConfirmationDialog} is finished with OK.
    * To be overwritten by subclasses.
    */
-  public void onOk();
+  public abstract void onOk();
 
+  /**
+   * Called when the {@link ConfirmationDialog} is finished with Cancel.
+   * To be overwritten by subclasses.
+   */
+  public void onCancel() {
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java
index 36169db..d510f05 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ConfirmationDialog.java
@@ -55,6 +55,7 @@
       @Override
       public void onClick(ClickEvent event) {
         hide();
+        callback.onCancel();
       }
     });
     buttons.add(cancelButton);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 87a09cf..2f5f87d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -60,6 +60,7 @@
 import com.google.gerrit.client.admin.ProjectInfoScreen;
 import com.google.gerrit.client.admin.ProjectListScreen;
 import com.google.gerrit.client.admin.ProjectScreen;
+import com.google.gerrit.client.change.ChangeScreen2;
 import com.google.gerrit.client.changes.AccountDashboardScreen;
 import com.google.gerrit.client.changes.ChangeScreen;
 import com.google.gerrit.client.changes.CustomDashboardScreen;
@@ -69,6 +70,7 @@
 import com.google.gerrit.client.changes.QueryScreen;
 import com.google.gerrit.client.dashboards.DashboardInfo;
 import com.google.gerrit.client.dashboards.DashboardList;
+import com.google.gerrit.client.diff.SideBySide2;
 import com.google.gerrit.client.groups.GroupApi;
 import com.google.gerrit.client.groups.GroupInfo;
 import com.google.gerrit.client.patches.PatchScreen;
@@ -90,6 +92,8 @@
 import com.google.gwtorm.client.KeyUtil;
 
 public class Dispatcher {
+  private static boolean useChangeScreen2;
+
   public static String toPatchSideBySide(final Patch.Key id) {
     return toPatch("", null, id);
   }
@@ -189,6 +193,9 @@
     } else if (matchPrefix("/c/", token)) {
       change(token);
 
+    } else if (matchPrefix("/c2/", token)) {
+      change2(token);
+
     } else if (matchExact(MINE, token)) {
       Gerrit.display(token, mine(token));
 
@@ -462,8 +469,8 @@
     }
 
     if (rest.isEmpty()) {
-      Gerrit.display(token, panel== null //
-          ? new ChangeScreen(id) //
+      Gerrit.display(token, panel== null
+          ? (useChangeScreen2 ? new ChangeScreen2(id, null) : new ChangeScreen(id))
           : new NotFoundScreen());
       return;
     }
@@ -494,7 +501,9 @@
       patch(token, base, p, 0, null, null, panel);
     } else {
       if (panel == null) {
-        Gerrit.display(token, new ChangeScreen(ps));
+        Gerrit.display(token, useChangeScreen2
+            ? new ChangeScreen2(id, String.valueOf(ps.get()))
+            : new ChangeScreen(id));
       } else if ("publish".equals(panel)) {
         publish(ps);
       } else {
@@ -503,6 +512,21 @@
     }
   }
 
+  private static void change2(final String token) {
+    String rest = skip(token);
+    Change.Id id;
+    int s = rest.indexOf('/');
+    if (0 <= s) {
+      id = Change.Id.parse(rest.substring(0, s));
+      rest = rest.substring(s + 1);
+    } else {
+      id = Change.Id.parse(rest);
+      rest = "";
+    }
+    useChangeScreen2 = true;
+    Gerrit.display(token, new ChangeScreen2(id, rest));
+  }
+
   private static void publish(final PatchSet.Id ps) {
     String token = toPublish(ps);
     new AsyncSplit(token) {
@@ -567,6 +591,8 @@
                 top, //
                 baseId //
             );
+          } else if ("cm".equals(panel)) {
+            return new SideBySide2(baseId, id.getParentKey(), id.get());
           }
         }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 0b1af89..48a7753 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -31,7 +31,6 @@
 import com.google.gerrit.client.ui.PatchLink;
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.client.ui.ScreenLoadEvent;
-import com.google.gerrit.common.ClientVersion;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.GerritConfig;
 import com.google.gerrit.common.data.GitwebConfig;
@@ -73,7 +72,6 @@
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwt.user.client.ui.InlineHTML;
 import com.google.gwt.user.client.ui.InlineLabel;
-import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.user.client.UserAgent;
@@ -434,35 +432,24 @@
     }
   }
 
-  private static void populateBottomMenu(final RootPanel btmmenu) {
-    final Label keyHelp = new Label(C.keyHelp());
-    keyHelp.setStyleName(RESOURCES.css().keyhelp());
-    btmmenu.add(keyHelp);
-
-    String vs;
-    if (GWT.isScript()) {
-      final ClientVersion v = GWT.create(ClientVersion.class);
-      vs = v.version().getText();
-      if (vs.startsWith("v")) {
-        vs = vs.substring(1);
-      }
-    } else {
+  private static void populateBottomMenu(RootPanel btmmenu, HostPageData hpd) {
+    String vs = hpd.version;
+    if (vs == null || vs.isEmpty()) {
       vs = "dev";
     }
 
-    FlowPanel poweredBy = new FlowPanel();
-    poweredBy.setStyleName(RESOURCES.css().version());
-    poweredBy.add(new InlineHTML(M.poweredBy(vs)));
+    btmmenu.add(new InlineLabel(C.keyHelp()));
+    btmmenu.add(new InlineLabel(" | "));
+    btmmenu.add(new InlineHTML(M.poweredBy(vs)));
     if (getConfig().getReportBugUrl() != null) {
-      poweredBy.add(new InlineLabel(" | "));
       Anchor a = new Anchor(
           C.reportBug(),
           getConfig().getReportBugUrl());
       a.setTarget("_blank");
       a.setStyleName("");
-      poweredBy.add(a);
+      btmmenu.add(new InlineLabel(" | "));
+      btmmenu.add(a);
     }
-    btmmenu.add(poweredBy);
   }
 
   private void onModuleLoad2(HostPageData hpd) {
@@ -539,7 +526,7 @@
 
     applyUserPreferences();
     initHistoryHooks();
-    populateBottomMenu(gBottomMenu);
+    populateBottomMenu(gBottomMenu, hpd);
     refreshMenuBar();
 
     History.addValueChangeHandler(new ValueChangeHandler<String>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index a33556e..4ad2651 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -140,7 +140,6 @@
   String infoBlock();
   String infoTable();
   String inputFieldTypeHint();
-  String keyhelp();
   String labelList();
   String leftMostCell();
   String lineHeader();
@@ -230,6 +229,5 @@
   String userInfoPopup();
   String useridentity();
   String usernameField();
-  String version();
   String watchedProjectFilter();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GitwebLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GitwebLink.java
index ec97e58..493b3f8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GitwebLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GitwebLink.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client;
 
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
 import com.google.gerrit.common.data.GitWebType;
 import com.google.gerrit.common.data.ParameterizedString;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -45,19 +46,26 @@
     return !ps.isDraft() || type.getLinkDrafts();
   }
 
+  public boolean canLink(RevisionInfo revision) {
+    return revision.draft() || type.getLinkDrafts();
+  }
+
   public String getLinkName() {
     return "(" + type.getLinkName() + ")";
   }
 
-  public String toRevision(final Project.NameKey project, final PatchSet ps) {
+  public String toRevision(String  project, String commit) {
     ParameterizedString pattern = new ParameterizedString(type.getRevision());
-
-    final Map<String, String> p = new HashMap<String, String>();
-    p.put("project", encode(project.get()));
-    p.put("commit", encode(ps.getRevision().get()));
+    Map<String, String> p = new HashMap<String, String>();
+    p.put("project", encode(project));
+    p.put("commit", encode(commit));
     return baseUrl + pattern.replace(p);
   }
 
+  public String toRevision(final Project.NameKey project, final PatchSet ps) {
+    return toRevision(project.get(), ps.getRevision().get());
+  }
+
   public String toProject(final Project.NameKey project) {
     ParameterizedString pattern = new ParameterizedString(type.getProject());
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index dd4de33..da9f1c6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -79,6 +79,7 @@
     suggestions.add("reviewerin:");
 
     suggestions.add("commit:");
+    suggestions.add("comment:");
     suggestions.add("project:");
     suggestions.add("branch:");
     suggestions.add("topic:");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Themer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Themer.java
index a532209..ceb50a8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Themer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Themer.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.client;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/AccessMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/AccessMap.java
new file mode 100644
index 0000000..629f725
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/AccessMap.java
@@ -0,0 +1,53 @@
+// 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.client.access;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import java.util.Collections;
+import java.util.Set;
+
+/** Access rights available from {@code /access/}. */
+public class AccessMap extends NativeMap<ProjectAccessInfo> {
+  public static void get(Set<Project.NameKey> projects,
+      AsyncCallback<AccessMap> callback) {
+    RestApi api = new RestApi("/access/");
+    for (Project.NameKey p : projects) {
+      api.addParameter("project", p.get());
+    }
+    api.get(NativeMap.copyKeysIntoChildren(callback));
+  }
+
+  public static void get(final Project.NameKey project,
+      final AsyncCallback<ProjectAccessInfo> cb) {
+    get(Collections.singleton(project), new AsyncCallback<AccessMap>() {
+      @Override
+      public void onSuccess(AccessMap result) {
+        cb.onSuccess(result.get(project.get()));
+      }
+
+      @Override
+      public void onFailure(Throwable caught) {
+        cb.onFailure(caught);
+      }
+    });
+  }
+
+  protected AccessMap() {
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
similarity index 68%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
index b2fb901..b81eca4 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.client.access;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gwt.core.client.JavaScriptObject;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class ProjectAccessInfo extends JavaScriptObject {
+  public final native boolean canAddRefs() /*-{ return this.can_add ? true : false; }-*/;
+
+  protected ProjectAccessInfo() {
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
new file mode 100644
index 0000000..2de46084
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -0,0 +1,112 @@
+// 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.client.account;
+
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import java.util.Set;
+
+/**
+ * A collection of static methods which work on the Gerrit REST API for specific
+ * accounts.
+ */
+public class AccountApi {
+  /** Retrieve the username */
+  public static void getUsername(String account, AsyncCallback<NativeString> cb) {
+    new RestApi("/accounts/").id(account).view("username").get(cb);
+  }
+
+  /** Retrieve email addresses */
+  public static void getEmails(String account,
+      AsyncCallback<JsArray<EmailInfo>> cb) {
+    new RestApi("/accounts/").id(account).view("emails").get(cb);
+  }
+
+  /** Register a new email address */
+  public static void registerEmail(String account, String email,
+      AsyncCallback<EmailInfo> cb) {
+    JavaScriptObject in = JavaScriptObject.createObject();
+    new RestApi("/accounts/").id(account).view("emails").id(email)
+        .ifNoneMatch().put(in, cb);
+  }
+
+  /** Retrieve SSH keys */
+  public static void getSshKeys(String account,
+      AsyncCallback<JsArray<SshKeyInfo>> cb) {
+    new RestApi("/accounts/").id(account).view("sshkeys").get(cb);
+  }
+
+  /** Add a new SSH keys */
+  public static void addSshKey(String account, String sshPublicKey,
+      AsyncCallback<SshKeyInfo> cb) {
+    new RestApi("/accounts/").id(account).view("sshkeys")
+        .post(sshPublicKey, cb);
+  }
+
+  /**
+   * Delete SSH keys. For each key to be deleted a separate DELETE request is
+   * fired to the server. The {@code onSuccess} method of the provided callback
+   * is invoked once after all requests succeeded. If any request fails the
+   * callbacks' {@code onFailure} method is invoked. In a failure case it can be
+   * that still some of the keys were successfully deleted.
+   */
+  public static void deleteSshKeys(String account,
+      Set<Integer> sequenceNumbers, AsyncCallback<VoidResult> cb) {
+    CallbackGroup group = new CallbackGroup();
+    for (int seq : sequenceNumbers) {
+      new RestApi("/accounts/").id(account).view("sshkeys").id(seq)
+          .delete(group.add(cb));
+      cb = CallbackGroup.emptyCallback();
+    }
+    group.done();
+  }
+
+  /** Retrieve the HTTP password */
+  public static void getHttpPassword(String account,
+      AsyncCallback<NativeString> cb) {
+    new RestApi("/accounts/").id(account).view("password.http").get(cb);
+  }
+
+  /** Generate a new HTTP password */
+  public static void generateHttpPassword(String account,
+      AsyncCallback<NativeString> cb) {
+    HttpPasswordInput in = HttpPasswordInput.create();
+    in.generate(true);
+    new RestApi("/accounts/").id(account).view("password.http").put(in, cb);
+  }
+
+  /** Clear HTTP password */
+  public static void clearHttpPassword(String account,
+      AsyncCallback<VoidResult> cb) {
+    new RestApi("/accounts/").id(account).view("password.http").delete(cb);
+  }
+
+  private static class HttpPasswordInput extends JavaScriptObject {
+    final native void generate(boolean g) /*-{ if(g)this.generate=g; }-*/;
+
+    static HttpPasswordInput create() {
+      return createObject().cast();
+    }
+
+    protected HttpPasswordInput() {
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
index f35bd4b..66f676e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
@@ -17,14 +17,15 @@
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.OnEditEnabler;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Account.FieldName;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.client.ContactInformation;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.event.dom.client.ChangeEvent;
 import com.google.gwt.event.dom.client.ChangeHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -45,12 +46,6 @@
 import com.google.gwtexpui.user.client.AutoCenterDialogBox;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 class ContactPanelShort extends Composite {
   protected final FlowPanel body;
   protected int labelIdx, fieldIdx;
@@ -150,7 +145,6 @@
         doSave(null);
       }
     });
-    new OnEditEnabler(save, nameTxt);
 
     emailPick.addChangeHandler(new ChangeHandler() {
       @Override
@@ -209,28 +203,19 @@
         postLoad();
       }
     });
-    Util.ACCOUNT_SEC
-        .myExternalIds(new GerritCallback<List<AccountExternalId>>() {
-          public void onSuccess(final List<AccountExternalId> result) {
-            if (!isAttached()) {
-              return;
-            }
-            final Set<String> emails = new HashSet<String>();
-            for (final AccountExternalId i : result) {
-              if (i.getEmailAddress() != null
-                  && i.getEmailAddress().length() > 0) {
-                emails.add(i.getEmailAddress());
-              }
-            }
-            final List<String> addrs = new ArrayList<String>(emails);
-            Collections.sort(addrs);
-            for (String s : addrs) {
-              emailPick.addItem(s);
-            }
-            haveEmails = true;
-            postLoad();
-          }
-        });
+    AccountApi.getEmails("self", new GerritCallback<JsArray<EmailInfo>>() {
+      @Override
+      public void onSuccess(JsArray<EmailInfo> result) {
+        if (!isAttached()) {
+          return;
+        }
+        for (EmailInfo i : Natives.asList(result)) {
+          emailPick.addItem(i.email());
+        }
+        haveEmails = true;
+        postLoad();
+      }
+    });
   }
 
   private void postLoad() {
@@ -255,6 +240,7 @@
     currentEmail = userAccount.getPreferredEmail();
     nameTxt.setText(userAccount.getFullName());
     save.setEnabled(false);
+    new OnEditEnabler(save, nameTxt);
   }
 
   private void doRegisterNewEmail() {
@@ -283,13 +269,16 @@
 
         inEmail.setEnabled(false);
         register.setEnabled(false);
-        Util.ACCOUNT_SEC.registerEmail(addr, new GerritCallback<Account>() {
-          public void onSuccess(Account currentUser) {
+        AccountApi.registerEmail("self", addr, new GerritCallback<EmailInfo>() {
+          @Override
+          public void onSuccess(EmailInfo result) {
             box.hide();
             if (Gerrit.getConfig().getAuthType() == AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT) {
               currentEmail = addr;
               if (emailPick.getItemCount() == 0) {
-                onSaveSuccess(currentUser);
+                final Account me = Gerrit.getUserAccount();
+                me.setPreferredEmail(addr);
+                onSaveSuccess(me);
               } else {
                 save.setEnabled(true);
               }
@@ -419,6 +408,14 @@
       }
     }
     if (emailPick.getItemCount() > 0) {
+      if (currentEmail == null) {
+        int index = emailListIndexOf("");
+        if (index != -1) {
+          emailPick.removeItem(index);
+        }
+        emailPick.insertItem("", 0);
+        emailPick.setSelectedIndex(0);
+      }
       emailPick.setVisible(true);
       emailPick.setEnabled(true);
       if (canRegisterNewEmail()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EmailInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EmailInfo.java
new file mode 100644
index 0000000..d0bbd8c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EmailInfo.java
@@ -0,0 +1,26 @@
+// 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.client.account;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class EmailInfo extends JavaScriptObject {
+  public final native String email() /*-{ return this.email; }-*/;
+  public final native boolean isPreferred() /*-{ return this['preferred'] ? true : false; }-*/;
+  public final native boolean isConfirmationPending() /*-{ return this['pending_confirmation'] ? true : false; }-*/;
+
+  protected EmailInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
index abb1f4d..72ea795 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
@@ -14,12 +14,12 @@
 
 package com.google.gerrit.client.account;
 
-import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
-
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.i18n.client.LocaleInfo;
@@ -31,13 +31,10 @@
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 
-import java.util.List;
-
 public class MyPasswordScreen extends SettingsScreen {
   private CopyableLabel password;
   private Button generatePassword;
   private Button clearPassword;
-  private AccountExternalId id;
 
   @Override
   protected void onInitUI() {
@@ -101,37 +98,48 @@
     }
 
     enableUI(false);
-    Util.ACCOUNT_SEC
-        .myExternalIds(new ScreenLoadCallback<List<AccountExternalId>>(this) {
-          public void preDisplay(final List<AccountExternalId> result) {
-            AccountExternalId id = null;
-            for (AccountExternalId i : result) {
-              if (i.isScheme(SCHEME_USERNAME)) {
-                id = i;
-                break;
-              }
-            }
-            display(id);
-          }
-        });
+    AccountApi.getUsername("self", new GerritCallback<NativeString>() {
+      @Override
+      public void onSuccess(NativeString user) {
+        Gerrit.getUserAccount().setUserName(user.asString());
+        refreshHttpPassword();
+      }
+
+      @Override
+      public void onFailure(final Throwable caught) {
+        if (RestApi.isNotFound(caught)) {
+          Gerrit.getUserAccount().setUserName(null);
+          display();
+        } else {
+          super.onFailure(caught);
+        }
+      }
+    });
   }
 
-  private void display(AccountExternalId id) {
-    String user, pass;
-    if (id != null) {
-      user = id.getSchemeRest();
-      pass = id.getPassword();
-    } else {
-      user = null;
-      pass = null;
-    }
-    this.id = id;
+  private void refreshHttpPassword() {
+    AccountApi.getHttpPassword("self", new ScreenLoadCallback<NativeString>(
+        this) {
+      @Override
+      protected void preDisplay(NativeString httpPassword) {
+        display(httpPassword.asString());
+      }
 
-    Gerrit.getUserAccount().setUserName(user);
+      @Override
+      public void onFailure(final Throwable caught) {
+        if (RestApi.isNotFound(caught)) {
+          display(null);
+          display();
+        } else {
+          super.onFailure(caught);
+        }
+      }
+    });
+  }
 
+  private void display(String pass) {
     password.setText(pass != null ? pass : "");
     password.setVisible(pass != null);
-
     enableUI(true);
   }
 
@@ -150,12 +158,13 @@
   }
 
   private void doGeneratePassword() {
-    if (id != null) {
+    if (Gerrit.getUserAccount().getUserName() != null) {
       enableUI(false);
-      Util.ACCOUNT_SEC.generatePassword(id.getKey(),
-          new GerritCallback<AccountExternalId>() {
-            public void onSuccess(final AccountExternalId result) {
-              display(result);
+      AccountApi.generateHttpPassword("self",
+          new GerritCallback<NativeString>() {
+            @Override
+            public void onSuccess(NativeString newPassword) {
+              display(newPassword.asString());
             }
 
             @Override
@@ -167,12 +176,13 @@
   }
 
   private void doClearPassword() {
-    if (id != null) {
+    if (Gerrit.getUserAccount().getUserName() != null) {
       enableUI(false);
-      Util.ACCOUNT_SEC.clearPassword(id.getKey(),
-          new GerritCallback<AccountExternalId>() {
-            public void onSuccess(final AccountExternalId result) {
-              display(result);
+      AccountApi.clearHttpPassword("self",
+          new GerritCallback<VoidResult>() {
+            @Override
+            public void onSuccess(VoidResult result) {
+              display(null);
             }
 
             @Override
@@ -184,9 +194,9 @@
   }
 
   private void enableUI(boolean on) {
-    on &= id != null;
+    on &= Gerrit.getUserAccount().getUserName() != null;
 
     generatePassword.setEnabled(on);
-    clearPassword.setVisible(on && id.getPassword() != null);
+    clearPassword.setVisible(on && !"".equals(password.getText()));
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index 639a1cf..3d1d30e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -218,7 +218,7 @@
         p.getTimeFormat());
     relativeDateInChangeTable.setValue(p.isRelativeDateInChangeTable());
     setListBox(commentVisibilityStrategy,
-        AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_MOST_RECENT,
+        AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_RECENT,
         p.getCommentVisibilityStrategy());
   }
 
@@ -285,7 +285,7 @@
         AccountGeneralPreferences.TimeFormat.values()));
     p.setRelativeDateInChangeTable(relativeDateInChangeTable.getValue());
     p.setCommentVisibilityStrategy(getListBox(commentVisibilityStrategy,
-        CommentVisibilityStrategy.EXPAND_MOST_RECENT,
+        CommentVisibilityStrategy.EXPAND_RECENT,
         CommentVisibilityStrategy.values()));
 
     enable(false);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshKeyInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshKeyInfo.java
new file mode 100644
index 0000000..a4fed8b
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshKeyInfo.java
@@ -0,0 +1,29 @@
+// 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.client.account;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SshKeyInfo extends JavaScriptObject {
+  public final native int seq() /*-{ return this.seq || 0; }-*/;
+  public final native String sshPublicKey() /*-{ return this.ssh_public_key; }-*/;
+  public final native String encodedKey() /*-{ return this.encoded_key; }-*/;
+  public final native String algorithm() /*-{ return this.algorithm; }-*/;
+  public final native String comment() /*-{ return this.comment; }-*/;
+  public final native boolean isValid() /*-{ return this['valid'] ? true : false; }-*/;
+
+  protected SshKeyInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
index aaadc5e6..0dfb520 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
@@ -16,13 +16,15 @@
 
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.ComplexDisclosurePanel;
 import com.google.gerrit.client.ui.FancyFlexTable;
 import com.google.gerrit.client.ui.SmallHeading;
 import com.google.gerrit.common.data.SshHostKey;
 import com.google.gerrit.common.errors.InvalidSshKeyException;
-import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
@@ -40,7 +42,6 @@
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.globalkey.client.NpTextArea;
 import com.google.gwtjsonrpc.client.RemoteJsonException;
-import com.google.gwtjsonrpc.common.VoidResult;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -157,11 +158,11 @@
     final String txt = addTxt.getText();
     if (txt != null && txt.length() > 0) {
       addNew.setEnabled(false);
-      Util.ACCOUNT_SEC.addSshKey(txt, new GerritCallback<AccountSshKey>() {
-        public void onSuccess(final AccountSshKey result) {
+      AccountApi.addSshKey("self", txt, new GerritCallback<SshKeyInfo>() {
+        public void onSuccess(final SshKeyInfo k) {
           addNew.setEnabled(true);
           addTxt.setText("");
-          keys.addOneKey(result);
+          keys.addOneKey(k);
           if (!keys.isVisible()) {
             showAddKeyBlock(false);
             setKeyTableVisible(true);
@@ -195,24 +196,27 @@
   @Override
   protected void onLoad() {
     super.onLoad();
-
-    Util.ACCOUNT_SEC.mySshKeys(new GerritCallback<List<AccountSshKey>>() {
-      public void onSuccess(final List<AccountSshKey> result) {
-        keys.display(result);
-        if (result.isEmpty() && keys.isVisible()) {
-          showAddKeyBlock(true);
+    refreshSshKeys();
+    Gerrit.SYSTEM_SVC.daemonHostKeys(new GerritCallback<List<SshHostKey>>() {
+      public void onSuccess(final List<SshHostKey> result) {
+        serverKeys.clear();
+        for (final SshHostKey keyInfo : result) {
+          serverKeys.add(new SshHostKeyPanel(keyInfo));
         }
         if (++loadCount == 2) {
           display();
         }
       }
     });
+  }
 
-    Gerrit.SYSTEM_SVC.daemonHostKeys(new GerritCallback<List<SshHostKey>>() {
-      public void onSuccess(final List<SshHostKey> result) {
-        serverKeys.clear();
-        for (final SshHostKey keyInfo : result) {
-          serverKeys.add(new SshHostKeyPanel(keyInfo));
+  private void refreshSshKeys() {
+    AccountApi.getSshKeys("self", new GerritCallback<JsArray<SshKeyInfo>>() {
+      @Override
+      public void onSuccess(JsArray<SshKeyInfo> result) {
+        keys.display(Natives.asList(result));
+        if (result.length() == 0 && keys.isVisible()) {
+          showAddKeyBlock(true);
         }
         if (++loadCount == 2) {
           display();
@@ -229,7 +233,7 @@
     addKeyBlock.setVisible(show);
   }
 
-  private class SshKeyTable extends FancyFlexTable<AccountSshKey> {
+  private class SshKeyTable extends FancyFlexTable<SshKeyInfo> {
     private ValueChangeHandler<Boolean> updateDeleteHandler;
 
     SshKeyTable() {
@@ -255,44 +259,53 @@
     }
 
     void deleteChecked() {
-      final HashSet<AccountSshKey.Id> ids = new HashSet<AccountSshKey.Id>();
+      final HashSet<Integer> sequenceNumbers = new HashSet<Integer>();
       for (int row = 1; row < table.getRowCount(); row++) {
-        final AccountSshKey k = getRowItem(row);
+        final SshKeyInfo k = getRowItem(row);
         if (k != null && ((CheckBox) table.getWidget(row, 1)).getValue()) {
-          ids.add(k.getKey());
+          sequenceNumbers.add(k.seq());
         }
       }
-      if (ids.isEmpty()) {
+      if (sequenceNumbers.isEmpty()) {
         updateDeleteButton();
       } else {
-        Util.ACCOUNT_SEC.deleteSshKeys(ids, new GerritCallback<VoidResult>() {
-          public void onSuccess(final VoidResult result) {
-            for (int row = 1; row < table.getRowCount();) {
-              final AccountSshKey k = getRowItem(row);
-              if (k != null && ids.contains(k.getKey())) {
-                table.removeRow(row);
-              } else {
-                row++;
+        deleteKey.setEnabled(false);
+        AccountApi.deleteSshKeys("self", sequenceNumbers,
+            new GerritCallback<VoidResult>() {
+              public void onSuccess(VoidResult result) {
+                for (int row = 1; row < table.getRowCount();) {
+                  final SshKeyInfo k = getRowItem(row);
+                  if (k != null && sequenceNumbers.contains(k.seq())) {
+                    table.removeRow(row);
+                  } else {
+                    row++;
+                  }
+                }
+                if (table.getRowCount() == 1) {
+                  display(Collections.<SshKeyInfo> emptyList());
+                } else {
+                  updateDeleteButton();
+                }
               }
-            }
-            if (table.getRowCount() == 1) {
-              display(Collections.<AccountSshKey> emptyList());
-            } else {
-              updateDeleteButton();
-            }
-          }
-        });
+
+              @Override
+              public void onFailure(Throwable caught) {
+                refreshSshKeys();
+                updateDeleteButton();
+                super.onFailure(caught);
+              }
+            });
       }
     }
 
-    void display(final List<AccountSshKey> result) {
+    void display(final List<SshKeyInfo> result) {
       if (result.isEmpty()) {
         setKeyTableVisible(false);
         showAddKeyBlock(true);
       } else {
         while (1 < table.getRowCount())
           table.removeRow(table.getRowCount() - 1);
-        for (final AccountSshKey k : result) {
+        for (final SshKeyInfo k : result) {
           addOneKey(k);
         }
         setKeyTableVisible(true);
@@ -300,7 +313,7 @@
       }
     }
 
-    void addOneKey(final AccountSshKey k) {
+    void addOneKey(final SshKeyInfo k) {
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       final int row = table.getRowCount();
       table.insertRow(row);
@@ -318,13 +331,13 @@
         table.setText(row, 2, Util.C.sshKeyInvalid());
         fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().sshKeyPanelInvalid());
       }
-      table.setText(row, 3, k.getAlgorithm());
+      table.setText(row, 3, k.algorithm());
 
-      CopyableLabel keyLabel = new CopyableLabel(k.getSshPublicKey());
-      keyLabel.setPreviewText(elide(k.getEncodedKey(), 40));
+      CopyableLabel keyLabel = new CopyableLabel(k.sshPublicKey());
+      keyLabel.setPreviewText(elide(k.encodedKey(), 40));
       table.setWidget(row, 4, keyLabel);
 
-      table.setText(row, 5, k.getComment());
+      table.setText(row, 5, k.comment());
 
       fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().iconCell());
       fmt.addStyleName(row, 4, Gerrit.RESOURCES.css().sshKeyPanelEncodedKey());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index 344104d..b734b41 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -93,9 +93,8 @@
 
   public AccessSectionEditor(ProjectAccess access) {
     projectAccess = access;
-
-    permissionSelector =
-        new ValueListBox<String>(PermissionNameRenderer.INSTANCE);
+    permissionSelector = new ValueListBox<String>(
+        new PermissionNameRenderer(access.getCapabilities()));
     permissionSelector.addValueChangeHandler(new ValueChangeHandler<String>() {
       @Override
       public void onValueChange(ValueChangeEvent<String> event) {
@@ -222,12 +221,15 @@
     List<String> perms = new ArrayList<String>();
 
     if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) {
-      for (String varName : Util.C.capabilityNames().keySet()) {
+      for (String varName : projectAccess.getCapabilities().keySet()) {
         addPermission(varName, perms);
       }
     } else if (RefConfigSection.isValid(value.getName())) {
       for (LabelType t : projectAccess.getLabelTypes().getLabelTypes()) {
-        addPermission(Permission.LABEL + t.getName(), perms);
+        addPermission(Permission.forLabel(t.getName()), perms);
+      }
+      for (LabelType t : projectAccess.getLabelTypes().getLabelTypes()) {
+        addPermission(Permission.forLabelAs(t.getName()), perms);
       }
       for (String varName : Util.C.permissionNames().keySet()) {
         addPermission(varName, perms);
@@ -282,7 +284,7 @@
     @Override
     public PermissionEditor create(int index) {
       PermissionEditor subEditor =
-          new PermissionEditor(projectAccess.getProjectName(), readOnly, value,
+          new PermissionEditor(projectAccess, readOnly, value,
               projectAccess.getLabelTypes());
       permissionContainer.insert(subEditor, index);
       return subEditor;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
index ea836c6..8e0d22b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
@@ -109,8 +109,6 @@
     });
     groupNamePanel.add(saveName);
     add(groupNamePanel);
-
-    new OnEditEnabler(saveName, groupNameTxt);
   }
 
   private void initOwner() {
@@ -120,8 +118,9 @@
 
     ownerTxtBox = new NpTextBox();
     ownerTxtBox.setVisibleLength(60);
+    final AccountGroupSuggestOracle accountGroupOracle = new AccountGroupSuggestOracle();
     ownerTxt = new SuggestBox(new RPCSuggestOracle(
-        new AccountGroupSuggestOracle()), ownerTxtBox);
+        accountGroupOracle), ownerTxtBox);
     ownerTxt.setStyleName(Gerrit.RESOURCES.css().groupOwnerTextBox());
     ownerPanel.add(ownerTxt);
 
@@ -132,7 +131,9 @@
       public void onClick(final ClickEvent event) {
         final String newOwner = ownerTxt.getText().trim();
         if (newOwner.length() > 0) {
-          GroupApi.setGroupOwner(getGroupUUID(), newOwner,
+          AccountGroup.UUID ownerUuid = accountGroupOracle.getUUID(newOwner);
+          String ownerId = ownerUuid != null ? ownerUuid.get() : newOwner;
+          GroupApi.setGroupOwner(getGroupUUID(), ownerId,
               new GerritCallback<GroupInfo>() {
                 public void onSuccess(final GroupInfo result) {
                   updateOwnerGroup(result);
@@ -144,8 +145,6 @@
     });
     ownerPanel.add(saveOwner);
     add(ownerPanel);
-
-    new OnEditEnabler(saveOwner, ownerTxtBox);
   }
 
   private void initDescription() {
@@ -174,8 +173,6 @@
     });
     vp.add(saveDesc);
     add(vp);
-
-    new OnEditEnabler(saveDesc, descTxt);
   }
 
   private void initGroupOptions() {
@@ -225,5 +222,8 @@
     saveOwner.setVisible(canModify);
     saveDesc.setVisible(canModify);
     saveGroupOptions.setVisible(canModify);
+    new OnEditEnabler(saveDesc, descTxt);
+    new OnEditEnabler(saveName, groupNameTxt);
+    new OnEditEnabler(saveOwner, ownerTxtBox);
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
index 51ff978..7aaf04d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
@@ -58,6 +58,7 @@
   private Button delInclude;
 
   private FlowPanel noMembersInfo;
+  private AccountGroupSuggestOracle accountGroupSuggestOracle;
 
   public AccountGroupMembersScreen(final GroupInfo toShow, final String token) {
     super(toShow, token);
@@ -109,9 +110,10 @@
   }
 
   private void initIncludeList() {
+    accountGroupSuggestOracle = new AccountGroupSuggestOracle();
     addIncludeBox =
         new AddMemberBox(Util.C.buttonAddIncludedGroup(),
-            Util.C.defaultAccountGroupName(), new AccountGroupSuggestOracle());
+            Util.C.defaultAccountGroupName(), accountGroupSuggestOracle);
 
     addIncludeBox.addClickHandler(new ClickHandler() {
       @Override
@@ -187,13 +189,18 @@
   }
 
   void doAddNewInclude() {
-    final String groupName = addIncludeBox.getText();
+    String groupName = addIncludeBox.getText();
     if (groupName.length() == 0) {
       return;
     }
 
+    AccountGroup.UUID uuid = accountGroupSuggestOracle.getUUID(groupName);
+    if (uuid == null) {
+      return;
+    }
+
     addIncludeBox.setEnabled(false);
-    GroupApi.addIncludedGroup(getGroupUUID(), groupName,
+    GroupApi.addIncludedGroup(getGroupUUID(), uuid.get(),
         new GerritCallback<GroupInfo>() {
           public void onSuccess(final GroupInfo result) {
             addIncludeBox.setEnabled(true);
@@ -290,28 +297,12 @@
           return str == null ? "" : str;
         }
       };
-      int insertPosition = table.getRowCount();
-      int left = 1;
-      int right = table.getRowCount() - 1;
-      while (left <= right) {
-        int middle = (left + right) >>> 1; // (left+right)/2
-        AccountInfo i = getRowItem(middle);
-        int cmp = c.compare(i, info);
-
-        if (cmp < 0) {
-          left = middle + 1;
-        } else if (cmp > 0) {
-          right = middle - 1;
-        } else {
-          // group is already contained in the table
-          return;
-        }
+      int insertPos = getInsertRow(c, info);
+      if (insertPos >= 0) {
+        table.insertRow(insertPos);
+        applyDataRowStyle(insertPos);
+        populate(insertPos, info);
       }
-      insertPosition = left;
-
-      table.insertRow(insertPosition);
-      applyDataRowStyle(insertPosition);
-      populate(insertPosition, info);
     }
 
     void populate(final int row, final AccountInfo i) {
@@ -405,29 +396,12 @@
           return (str == null) ? "" : str;
         }
       };
-
-      int insertPosition = table.getRowCount();
-      int left = 1;
-      int right = table.getRowCount() - 1;
-      while (left <= right) {
-        int middle = (left + right) >>> 1; // (left+right)/2
-        GroupInfo i = getRowItem(middle);
-        int cmp = c.compare(i, info);
-
-        if (cmp < 0) {
-          left = middle + 1;
-        } else if (cmp > 0) {
-          right = middle - 1;
-        } else {
-          // group is already contained in the table
-          return;
-        }
+      int insertPos = getInsertRow(c, info);
+      if (insertPos >= 0) {
+        table.insertRow(insertPos);
+        applyDataRowStyle(insertPos);
+        populate(insertPos, info);
       }
-      insertPosition = left;
-
-      table.insertRow(insertPosition);
-      applyDataRowStyle(insertPosition);
-      populate(insertPosition, info);
     }
 
     void populate(final int row, final GroupInfo i) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index f4c0b55..7b0cd5f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -42,6 +42,7 @@
   String useContributorAgreements();
   String useSignedOffBy();
   String requireChangeID();
+  String headingMaxObjectSizeLimit();
   String headingGroupOptions();
   String isVisibleToAll();
   String buttonSaveGroupOptions();
@@ -89,7 +90,6 @@
   String initialRevision();
   String buttonAddBranch();
   String buttonDeleteBranch();
-  String branchDeletionOpenChanges();
 
   String groupItemHelp();
 
@@ -125,8 +125,6 @@
   String refErrorPrintable();
   String errorsMustBeFixed();
 
-  Map<String, String> capabilityNames();
-
   String sectionTypeReference();
   String sectionTypeSection();
   Map<String, String> sectionNames();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index ce27780..c767f84 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -24,6 +24,7 @@
 useContributorAgreements = Require a valid contributor agreement to upload
 useSignedOffBy = Require <a href="http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/user-signedoffby.html#Signed-off-by" target="_blank"><code>Signed-off-by</code></a> in commit message
 requireChangeID = Require <a href="http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/user-changeid.html" target="_blank"><code>Change-Id</code></a> in commit message
+headingMaxObjectSizeLimit = Maximum Git object size limit
 headingGroupOptions = Group Options
 isVisibleToAll = Make group visible to all registered users.
 buttonSaveGroupOptions = Save Group Options
@@ -68,8 +69,6 @@
 initialRevision = Initial Revision
 buttonAddBranch = Create Branch
 buttonDeleteBranch = Delete
-branchDeletionOpenChanges = The following branches were not deleted \
-because they have open changes:
 
 groupItemHelp = group
 
@@ -144,41 +143,6 @@
 refErrorPrintable = References may contain only printable characters
 errorsMustBeFixed = Errors must be fixed before committing changes.
 
-# Capability Names
-capabilityNames = \
-  accessDatabase, \
-  administrateServer, \
-  createAccount, \
-  createGroup, \
-  createProject, \
-  emailReviewers, \
-  flushCaches, \
-  killTask, \
-  priority, \
-  queryLimit, \
-  runGC, \
-  startReplication, \
-  streamEvents, \
-  viewCaches, \
-  viewConnections, \
-  viewQueue
-accessDatabase = Access Database
-administrateServer = Administrate Server
-createAccount = Create Account
-createGroup = Create Group
-createProject = Create Project
-emailReviewers = Email Reviewers
-flushCaches = Flush Caches
-killTask = Kill Task
-priority = Priority
-queryLimit = Query Limit
-runGC = Run Garbage Collection
-startReplication = Start Replication
-streamEvents = Stream Events
-viewCaches = View Caches
-viewConnections = View Connections
-viewQueue = View Queue
-
 # Section Names
 sectionTypeReference = Reference:
 sectionTypeSection = Section:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index 3b4d7d4..d0c52c6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -19,6 +19,7 @@
 public interface AdminMessages extends Messages {
   String group(String name);
   String label(String name);
+  String labelAs(String name);
   String project(String name);
   String deletedGroup(int id);
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
index 7f8cd56..28f3fcc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
@@ -1,5 +1,6 @@
 group = Group {0}
 label = Label {0}
+labelAs = Label {0} (On Behalf Of)
 project = Project {0}
 deletedGroup = Deleted Group {0}
 deletedReference = Reference {0} was deleted
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index dac0b6a..bed6b4a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.client.ui.FilteredUserInterface;
 import com.google.gerrit.client.ui.IgnoreOutdatedFilterResultsCallbackWrapper;
 import com.google.gerrit.common.PageLinks;
+import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.event.dom.client.KeyUpHandler;
 import com.google.gwt.http.client.URL;
@@ -55,10 +56,10 @@
   protected void onLoad() {
     super.onLoad();
     display();
-    refresh();
+    refresh(false);
   }
 
-  private void refresh() {
+  private void refresh(final boolean open) {
     setToken(subname == null || "".equals(subname) ? ADMIN_GROUPS
         : ADMIN_GROUPS + "?filter=" + URL.encodeQueryString(subname));
     GroupMap.match(subname,
@@ -66,8 +67,13 @@
             new GerritCallback<GroupMap>() {
               @Override
               public void onSuccess(GroupMap result) {
-                groups.display(result, subname);
-                groups.finishDisplay();
+                if (open && result.values().length() > 0) {
+                  Gerrit.display(PageLinks.toGroup(
+                      result.values().get(0).getGroupUUID()));
+                } else {
+                  groups.display(result, subname);
+                  groups.finishDisplay();
+                }
               }
             }));
   }
@@ -99,7 +105,7 @@
       @Override
       public void onKeyUp(KeyUpEvent event) {
         subname = filterTxt.getValue();
-        refresh();
+        refresh(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER);
       }
     });
     hp.add(filterTxt);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
index 9848c18..028fb87 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
@@ -107,17 +108,19 @@
   private PermissionRange.WithDefaults validRange;
   private boolean isDeleted;
 
-  public PermissionEditor(Project.NameKey projectName,
+  public PermissionEditor(ProjectAccess projectAccess,
       boolean readOnly,
       AccessSection section,
       LabelTypes labelTypes) {
     this.readOnly = readOnly;
     this.section = section;
-    this.projectName = projectName;
+    this.projectName = projectAccess.getProjectName();
     this.labelTypes = labelTypes;
 
-    normalName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
-    deletedName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
+    PermissionNameRenderer nameRenderer =
+        new PermissionNameRenderer(projectAccess.getCapabilities());
+    normalName = new ValueLabel<String>(nameRenderer);
+    deletedName = new ValueLabel<String>(nameRenderer);
 
     initWidget(uiBinder.createAndBindUi(this));
     groupToAdd.setProject(projectName);
@@ -262,7 +265,7 @@
   public void setValue(Permission value) {
     this.value = value;
 
-    if (value.isLabel()) {
+    if (Permission.hasRange(value.getName())) {
       LabelType lt = labelTypes.byLabel(value.getLabel());
       if (lt != null) {
         validRange = new PermissionRange.WithDefaults(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
index ad3473c..d8ee195 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
@@ -22,37 +22,54 @@
 import java.util.Map;
 
 class PermissionNameRenderer implements Renderer<String> {
-  static final PermissionNameRenderer INSTANCE = new PermissionNameRenderer();
-
-  private static final Map<String, String> all;
+  private static final Map<String, String> permissions;
 
   static {
-    all = new HashMap<String, String>();
-    for (Map.Entry<String, String> e : Util.C.capabilityNames().entrySet()) {
-      all.put(e.getKey(), e.getValue());
-      all.put(e.getKey().toLowerCase(), e.getValue());
-    }
+    permissions = new HashMap<String, String>();
     for (Map.Entry<String, String> e : Util.C.permissionNames().entrySet()) {
-      all.put(e.getKey(), e.getValue());
-      all.put(e.getKey().toLowerCase(), e.getValue());
+      permissions.put(e.getKey(), e.getValue());
+      permissions.put(e.getKey().toLowerCase(), e.getValue());
     }
   }
 
+  private final Map<String, String> fromServer;
+
+  PermissionNameRenderer(Map<String, String> allFromOutside) {
+    fromServer = allFromOutside;
+  }
+
   @Override
   public String render(String varName) {
-    if (Permission.isLabel(varName)) {
-      return Util.M.label(new Permission(varName).getLabel());
+    if (Permission.isLabelAs(varName)) {
+      return Util.M.labelAs(Permission.extractLabel(varName));
+    } else if (Permission.isLabel(varName)) {
+      return Util.M.label(Permission.extractLabel(varName));
     }
 
-    String desc = all.get(varName);
-    if (desc == null) {
-      desc = all.get(varName.toLowerCase());
+    String desc = permissions.get(varName);
+    if (desc != null) {
+      return desc;
     }
-    return desc != null ? desc : varName;
+
+    desc = fromServer.get(varName);
+    if (desc != null) {
+      return desc;
+    }
+
+    desc = permissions.get(varName.toLowerCase());
+    if (desc != null) {
+      return desc;
+    }
+
+    desc = fromServer.get(varName.toLowerCase());
+    if (desc != null) {
+      return desc;
+    }
+    return varName;
   }
 
   @Override
   public void render(String object, Appendable appendable) throws IOException {
     appendable.append(render(object));
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 96824f3..ef7f560 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -18,7 +18,12 @@
 import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.config.CapabilityInfo;
+import com.google.gerrit.client.config.ConfigServerApi;
+import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.AccessSection;
@@ -33,6 +38,7 @@
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Label;
@@ -41,8 +47,10 @@
 import com.google.gwtexpui.globalkey.client.NpTextArea;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class ProjectAccessScreen extends ProjectScreen {
@@ -90,6 +98,8 @@
 
   private ProjectAccess access;
 
+  private NativeMap<CapabilityInfo> capabilityMap;
+
   public ProjectAccessScreen(final Project.NameKey toShow) {
     super(toShow);
   }
@@ -107,18 +117,36 @@
   @Override
   protected void onLoad() {
     super.onLoad();
+    CallbackGroup cbs = new CallbackGroup();
+    ConfigServerApi.capabilities(
+        cbs.add(new AsyncCallback<NativeMap<CapabilityInfo>>() {
+          @Override
+          public void onSuccess(NativeMap<CapabilityInfo> result) {
+            capabilityMap = result;
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // Handled by ScreenLoadCallback.onFailure().
+          }
+        }));
     Util.PROJECT_SVC.projectAccess(getProjectKey(),
-        new ScreenLoadCallback<ProjectAccess>(this) {
+        cbs.addFinal(new ScreenLoadCallback<ProjectAccess>(this) {
           @Override
           public void preDisplay(ProjectAccess access) {
             displayReadOnly(access);
           }
-        });
+        }));
     savedPanel = ACCESS;
   }
 
   private void displayReadOnly(ProjectAccess access) {
     this.access = access;
+    Map<String, String> allCapabilities = new HashMap<String, String>();
+    for (CapabilityInfo c : Natives.asList(capabilityMap.values())) {
+      allCapabilities.put(c.id(), c.name());
+    }
+    this.access.setCapabilities(allCapabilities);
     accessEditor.setEditing(false);
     UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty() || access.canUpload());
     edit.setEnabled(!access.getOwnerOf().isEmpty() || access.canUpload());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index a7c6a23..04d407e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -19,16 +19,19 @@
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GitwebLink;
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.access.AccessMap;
+import com.google.gerrit.client.access.ProjectAccessInfo;
+import com.google.gerrit.client.projects.BranchInfo;
+import com.google.gerrit.client.projects.ProjectApi;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.client.ui.BranchLink;
 import com.google.gerrit.client.ui.FancyFlexTable;
 import com.google.gerrit.client.ui.HintTextBox;
-import com.google.gerrit.common.data.AddBranchResult;
-import com.google.gerrit.common.data.ListBranchesResult;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -36,23 +39,25 @@
 import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 public class ProjectBranchesScreen extends ProjectScreen {
-  private BranchesTable branches;
+  private BranchesTable branchTable;
   private Button delBranch;
   private Button addBranch;
   private HintTextBox nameTxtBox;
@@ -66,39 +71,41 @@
   @Override
   protected void onLoad() {
     super.onLoad();
-    Util.PROJECT_SVC.listBranches(getProjectKey(),
-        new ScreenLoadCallback<ListBranchesResult>(this) {
+    addPanel.setVisible(false);
+    AccessMap.get(getProjectKey(),
+        new GerritCallback<ProjectAccessInfo>() {
           @Override
-          public void preDisplay(final ListBranchesResult result) {
-            if (result.getNoRepository()) {
-              branches.setVisible(false);
-              addPanel.setVisible(false);
-              delBranch.setVisible(false);
-
-              Label no = new Label(Util.C.errorNoGitRepository());
-              no.setStyleName(Gerrit.RESOURCES.css().smallHeading());
-              add(no);
-
-            } else {
-              enableForm(true);
-              display(result.getBranches());
-              addPanel.setVisible(result.getCanAdd());
-            }
+          public void onSuccess(ProjectAccessInfo result) {
+            addPanel.setVisible(result.canAddRefs());
           }
         });
+    refreshBranches();
     savedPanel = BRANCH;
   }
 
-  private void display(final List<Branch> listBranches) {
-    branches.display(listBranches);
-    delBranch.setVisible(branches.hasBranchCanDelete());
+  private void refreshBranches() {
+    ProjectApi.getBranches(getProjectKey(),
+        new ScreenLoadCallback<JsArray<BranchInfo>>(this) {
+          @Override
+          public void preDisplay(final JsArray<BranchInfo> result) {
+            Set<String> checkedRefs = branchTable.getCheckedRefs();
+            display(Natives.asList(result));
+            branchTable.setChecked(checkedRefs);
+            updateForm();
+          }
+        });
   }
 
-  private void enableForm(final boolean on) {
-    delBranch.setEnabled(on);
-    addBranch.setEnabled(on);
-    nameTxtBox.setEnabled(on);
-    irevTxtBox.setEnabled(on);
+  private void display(final List<BranchInfo> branches) {
+    branchTable.display(branches);
+    delBranch.setVisible(branchTable.hasBranchCanDelete());
+  }
+
+  private void updateForm() {
+    branchTable.updateDeleteButton();
+    addBranch.setEnabled(true);
+    nameTxtBox.setEnabled(true);
+    irevTxtBox.setEnabled(true);
   }
 
   @Override
@@ -149,29 +156,29 @@
     addPanel.add(addGrid);
     addPanel.add(addBranch);
 
-    branches = new BranchesTable();
+    branchTable = new BranchesTable();
 
     delBranch = new Button(Util.C.buttonDeleteBranch());
     delBranch.addClickHandler(new ClickHandler() {
       @Override
       public void onClick(final ClickEvent event) {
-        branches.deleteChecked();
+        branchTable.deleteChecked();
       }
     });
 
-    add(branches);
+    add(branchTable);
     add(delBranch);
     add(addPanel);
   }
 
   private void doAddNewBranch() {
-    final String branchName = nameTxtBox.getText();
+    final String branchName = nameTxtBox.getText().trim();
     if ("".equals(branchName)) {
       nameTxtBox.setFocus(true);
       return;
     }
 
-    final String rev = irevTxtBox.getText();
+    final String rev = irevTxtBox.getText().trim();
     if ("".equals(rev)) {
       irevTxtBox.setText("HEAD");
       Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@@ -185,62 +192,23 @@
     }
 
     addBranch.setEnabled(false);
-    Util.PROJECT_SVC.addBranch(getProjectKey(), branchName, rev,
-        new GerritCallback<AddBranchResult>() {
-          public void onSuccess(final AddBranchResult result) {
-            addBranch.setEnabled(true);
-            if (!result.hasError()) {
-              nameTxtBox.setText("");
-              irevTxtBox.setText("");
-              display(result.getListBranchesResult().getBranches());
-            } else {
-              final AddBranchResult.Error error = result.getError();
-              final String msg;
-              switch (error.getType()) {
-                case INVALID_NAME:
-                  selectAllAndFocus(nameTxtBox);
-                  msg = Gerrit.M.invalidBranchName(branchName);
-                  break;
-
-                case INVALID_REVISION:
-                  selectAllAndFocus(irevTxtBox);
-                  msg = Gerrit.M.invalidRevision(rev);
-                  break;
-
-                case BRANCH_CREATION_NOT_ALLOWED_UNDER_REFNAME_PREFIX:
-                  selectAllAndFocus(nameTxtBox);
-                  msg =
-                      Gerrit.M.branchCreationNotAllowedUnderRefnamePrefix(error
-                          .getRefname());
-                  break;
-
-                case BRANCH_ALREADY_EXISTS:
-                  selectAllAndFocus(nameTxtBox);
-                  msg = Gerrit.M.branchAlreadyExists(error.getRefname());
-                  break;
-
-                case BRANCH_CREATION_CONFLICT:
-                  selectAllAndFocus(nameTxtBox);
-                  msg =
-                      Gerrit.M.branchCreationConflict(branchName,
-                          error.getRefname());
-                  break;
-
-                default:
-                  msg =
-                      Gerrit.M.branchCreationFailed(branchName,
-                          error.toString());
-              }
-              new ErrorDialog(msg).center();
-            }
-          }
-
+    ProjectApi.createBranch(getProjectKey(), branchName, rev,
+        new GerritCallback<BranchInfo>() {
           @Override
-          public void onFailure(final Throwable caught) {
+          public void onSuccess(BranchInfo branch) {
             addBranch.setEnabled(true);
-            super.onFailure(caught);
+            nameTxtBox.setText("");
+            irevTxtBox.setText("");
+            branchTable.insert(branch);
           }
-        });
+
+      @Override
+      public void onFailure(Throwable caught) {
+        addBranch.setEnabled(true);
+        selectAllAndFocus(nameTxtBox);
+        new ErrorDialog(caught.getMessage()).center();
+      }
+    });
   }
 
   private static void selectAllAndFocus(final TextBox textBox) {
@@ -248,7 +216,8 @@
     textBox.setFocus(true);
   }
 
-  private class BranchesTable extends FancyFlexTable<Branch> {
+  private class BranchesTable extends FancyFlexTable<BranchInfo> {
+    private ValueChangeHandler<Boolean> updateDeleteHandler;
     boolean canDelete;
 
     BranchesTable() {
@@ -263,90 +232,108 @@
       if (Gerrit.getGitwebLink() != null) {
         fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
       }
+
+      updateDeleteHandler = new ValueChangeHandler<Boolean>() {
+        @Override
+        public void onValueChange(ValueChangeEvent<Boolean> event) {
+          updateDeleteButton();
+        }
+      };
+    }
+
+    Set<String> getCheckedRefs() {
+      Set<String> refs = new HashSet<String>();
+      for (int row = 1; row < table.getRowCount(); row++) {
+        final BranchInfo k = getRowItem(row);
+        if (k != null && table.getWidget(row, 1) instanceof CheckBox
+            && ((CheckBox) table.getWidget(row, 1)).getValue()) {
+          refs.add(k.ref());
+        }
+      }
+      return refs;
+    }
+
+    void setChecked(Set<String> refs) {
+      for (int row = 1; row < table.getRowCount(); row++) {
+        final BranchInfo k = getRowItem(row);
+        if (k != null && refs.contains(k.ref()) &&
+            table.getWidget(row, 1) instanceof CheckBox) {
+          ((CheckBox) table.getWidget(row, 1)).setValue(true);
+        }
+      }
     }
 
     void deleteChecked() {
-      final SafeHtmlBuilder b = new SafeHtmlBuilder();
+      final Set<String> refs = getCheckedRefs();
+
+      SafeHtmlBuilder b = new SafeHtmlBuilder();
       b.openElement("b");
       b.append(Gerrit.C.branchDeletionConfirmationMessage());
       b.closeElement("b");
 
       b.openElement("p");
-      final HashSet<Branch.NameKey> ids = new HashSet<Branch.NameKey>();
-      for (int row = 1; row < table.getRowCount(); row++) {
-        final Branch k = getRowItem(row);
-        if (k != null && table.getWidget(row, 1) instanceof CheckBox
-            && ((CheckBox) table.getWidget(row, 1)).getValue()) {
-          if (!ids.isEmpty()) {
-            b.append(",").br();
-          }
-          b.append(k.getName());
-          ids.add(k.getNameKey());
+      boolean first = true;
+      for (String ref : refs) {
+        if (!first) {
+          b.append(",").br();
         }
+        b.append(ref);
+        first = false;
       }
       b.closeElement("p");
-      if (ids.isEmpty()) {
+
+      if (refs.isEmpty()) {
+        updateDeleteButton();
         return;
       }
 
+      delBranch.setEnabled(false);
       ConfirmationDialog confirmationDialog =
           new ConfirmationDialog(Gerrit.C.branchDeletionDialogTitle(),
               b.toSafeHtml(), new ConfirmationCallback() {
         @Override
         public void onOk() {
-          deleteBranches(ids);
+          deleteBranches(refs);
+        }
+
+        @Override
+        public void onCancel() {
+          branchTable.updateDeleteButton();
         }
       });
       confirmationDialog.center();
     }
 
-    private void deleteBranches(final Set<Branch.NameKey> branchIds) {
-      Util.PROJECT_SVC.deleteBranch(getProjectKey(), branchIds,
-          new GerritCallback<Set<Branch.NameKey>>() {
-            public void onSuccess(final Set<Branch.NameKey> deleted) {
-              if (!deleted.isEmpty()) {
-                for (int row = 1; row < table.getRowCount();) {
-                  final Branch k = getRowItem(row);
-                  if (k != null && deleted.contains(k.getNameKey())) {
-                    table.removeRow(row);
-                  } else {
-                    row++;
-                  }
+    private void deleteBranches(final Set<String> branches) {
+      ProjectApi.deleteBranches(getProjectKey(), branches,
+          new GerritCallback<VoidResult>() {
+            public void onSuccess(VoidResult result) {
+              for (int row = 1; row < table.getRowCount();) {
+                BranchInfo k = getRowItem(row);
+                if (k != null && branches.contains(k.ref())) {
+                  table.removeRow(row);
+                } else {
+                  row++;
                 }
               }
+              updateDeleteButton();
+            }
 
-              branchIds.removeAll(deleted);
-              if (!branchIds.isEmpty()) {
-                final VerticalPanel p = new VerticalPanel();
-                final ErrorDialog errorDialog = new ErrorDialog(p);
-                final Label l = new Label(Util.C.branchDeletionOpenChanges());
-                l.setStyleName(Gerrit.RESOURCES.css().errorDialogText());
-                p.add(l);
-                for (final Branch.NameKey branch : branchIds) {
-                  final BranchLink link =
-                      new BranchLink(branch.getParentKey(), Change.Status.NEW,
-                          branch.get(), null) {
-                    @Override
-                    public void go() {
-                      errorDialog.hide();
-                      super.go();
-                    };
-                  };
-                  p.add(link);
-                }
-                errorDialog.center();
-              }
+            @Override
+            public void onFailure(Throwable caught) {
+              refreshBranches();
+              super.onFailure(caught);
             }
           });
     }
 
-    void display(final List<Branch> result) {
+    void display(List<BranchInfo> branches) {
       canDelete = false;
 
       while (1 < table.getRowCount())
         table.removeRow(table.getRowCount() - 1);
 
-      for (final Branch k : result) {
+      for (final BranchInfo k : branches) {
         final int row = table.getRowCount();
         table.insertRow(row);
         applyDataRowStyle(row);
@@ -354,11 +341,28 @@
       }
     }
 
-    void populate(final int row, final Branch k) {
+    void insert(BranchInfo info) {
+      Comparator<BranchInfo> c = new Comparator<BranchInfo>() {
+        @Override
+        public int compare(BranchInfo a, BranchInfo b) {
+          return a.ref().compareTo(b.ref());
+        }
+      };
+      int insertPos = getInsertRow(c, info);
+      if (insertPos >= 0) {
+        table.insertRow(insertPos);
+        applyDataRowStyle(insertPos);
+        populate(insertPos, info);
+      }
+    }
+
+    void populate(int row, BranchInfo k) {
       final GitwebLink c = Gerrit.getGitwebLink();
 
-      if (k.getCanDelete()) {
-        table.setWidget(row, 1, new CheckBox());
+      if (k.canDelete()) {
+        CheckBox sel = new CheckBox();
+        sel.addValueChangeHandler(updateDeleteHandler);
+        table.setWidget(row, 1, sel);
         canDelete = true;
       } else {
         table.setText(row, 1, "");
@@ -366,15 +370,15 @@
 
       table.setText(row, 2, k.getShortName());
 
-      if (k.getRevision() != null) {
-        table.setText(row, 3, k.getRevision().get());
+      if (k.revision() != null) {
+        table.setText(row, 3, k.revision());
       } else {
         table.setText(row, 3, "");
       }
 
       if (c != null) {
-        table.setWidget(row, 4, new Anchor(c.getLinkName(), false, c.toBranch(k
-            .getNameKey())));
+        table.setWidget(row, 4, new Anchor(c.getLinkName(), false,
+            c.toBranch(new Branch.NameKey(getProjectKey(), k.ref()))));
       }
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
@@ -399,5 +403,20 @@
     boolean hasBranchCanDelete() {
       return canDelete;
     }
+
+    void updateDeleteButton() {
+      boolean on = false;
+      for (int row = 1; row < table.getRowCount(); row++) {
+        Widget w = table.getWidget(row, 1);
+        if (w != null && w instanceof CheckBox) {
+          CheckBox sel = (CheckBox) w;
+          if (sel.getValue()) {
+            on = true;
+            break;
+          }
+        }
+      }
+      delBranch.setEnabled(on);
+    }
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 40921309..ebdc995 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -36,10 +36,12 @@
 import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.NpTextArea;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
 
 public class ProjectInfoScreen extends ProjectScreen {
   private String projectName;
   private Project project;
+  private ProjectDetail projectDetail;
 
   private LabeledWidgetsGrid grid;
 
@@ -48,6 +50,7 @@
   private ListBox submitType;
   private ListBox state;
   private ListBox contentMerge;
+  private NpTextBox maxObjectSizeLimit;
 
   // Section: Contributor Agreements
   private ListBox contributorAgreements;
@@ -92,7 +95,8 @@
         new ScreenLoadCallback<ProjectDetail>(this) {
           public void preDisplay(final ProjectDetail result) {
             enableForm(result.canModifyAgreements,
-                result.canModifyDescription, result.canModifyMergeType, result.canModifyState);
+                result.canModifyDescription, result.canModifyMergeType, result.canModifyState,
+                result.canModifyMaxObjectSizeLimit);
             saveProject.setVisible(
                 result.canModifyAgreements ||
                 result.canModifyDescription ||
@@ -104,9 +108,19 @@
     savedPanel = INFO;
   }
 
+  private void enableForm() {
+    if (projectDetail != null) {
+      enableForm(projectDetail.canModifyAgreements,
+          projectDetail.canModifyDescription,
+          projectDetail.canModifyMergeType,
+          projectDetail.canModifyState,
+          projectDetail.canModifyMaxObjectSizeLimit);
+    }
+  }
+
   private void enableForm(final boolean canModifyAgreements,
       final boolean canModifyDescription, final boolean canModifyMergeType,
-      final boolean canModifyState) {
+      final boolean canModifyState, final boolean canModifyMaxObjectSizeLimit) {
     submitType.setEnabled(canModifyMergeType);
     state.setEnabled(canModifyState);
     contentMerge.setEnabled(canModifyMergeType);
@@ -114,6 +128,7 @@
     contributorAgreements.setEnabled(canModifyAgreements);
     signedOffBy.setEnabled(canModifyAgreements);
     requireChangeID.setEnabled(canModifyMergeType);
+    maxObjectSizeLimit.setEnabled(canModifyMaxObjectSizeLimit);
   }
 
   private void initDescription() {
@@ -160,6 +175,10 @@
     requireChangeID = newInheritedBooleanBox();
     saveEnabler.listenTo(requireChangeID);
     grid.addHtml(Util.C.requireChangeID(), requireChangeID);
+
+    maxObjectSizeLimit = new NpTextBox();
+    saveEnabler.listenTo(maxObjectSizeLimit);
+    grid.addHtml(Util.C.headingMaxObjectSizeLimit(), maxObjectSizeLimit);
   }
 
   private static ListBox newInheritedBooleanBox() {
@@ -277,8 +296,11 @@
     setBool(requireChangeID, result.requireChangeID);
     setSubmitType(project.getSubmitType());
     setState(project.getState());
+    maxObjectSizeLimit.setText(project.getMaxObjectSizeLimit());
 
     saveProject.setEnabled(false);
+
+    projectDetail = result;
   }
 
   private void doSave() {
@@ -287,6 +309,7 @@
     project.setUseSignedOffBy(getBool(signedOffBy));
     project.setUseContentMerge(getBool(contentMerge));
     project.setRequireChangeID(getBool(requireChangeID));
+    project.setMaxObjectSizeLimit(maxObjectSizeLimit.getText().trim());
     if (submitType.getSelectedIndex() >= 0) {
       project.setSubmitType(Project.SubmitType.valueOf(submitType
           .getValue(submitType.getSelectedIndex())));
@@ -296,15 +319,21 @@
           .getValue(state.getSelectedIndex())));
     }
 
-    enableForm(false, false, false, false);
+    enableForm(false, false, false, false, false);
 
     Util.PROJECT_SVC.changeProjectSettings(project,
         new GerritCallback<ProjectDetail>() {
           public void onSuccess(final ProjectDetail result) {
             enableForm(result.canModifyAgreements,
-                result.canModifyDescription, result.canModifyMergeType, result.canModifyState);
+                result.canModifyDescription, result.canModifyMergeType, result.canModifyState,
+                result.canModifyMaxObjectSizeLimit);
             display(result);
           }
+          @Override
+          public void onFailure(Throwable caught) {
+            super.onFailure(caught);
+            enableForm();
+          }
         });
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index ee58420..331afee 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.client.ui.ProjectsTable;
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.common.PageLinks;
+import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.event.dom.client.KeyUpHandler;
 import com.google.gwt.http.client.URL;
@@ -64,10 +65,10 @@
   protected void onLoad() {
     super.onLoad();
     display();
-    refresh();
+    refresh(false);
   }
 
-  private void refresh() {
+  private void refresh(final boolean open) {
     setToken(subname == null || "".equals(subname) ? ADMIN_PROJECTS
         : ADMIN_PROJECTS + "?filter=" + URL.encodeQueryString(subname));
     ProjectMap.match(subname,
@@ -75,7 +76,12 @@
             new GerritCallback<ProjectMap>() {
               @Override
               public void onSuccess(ProjectMap result) {
-                projects.display(result);
+                if (open && result.values().length() > 0) {
+                  Gerrit.display(PageLinks.toProject(
+                      result.values().get(0).name_key()));
+                } else {
+                  projects.display(result);
+                }
               }
             }));
   }
@@ -153,7 +159,7 @@
       @Override
       public void onKeyUp(KeyUpEvent event) {
         subname = filterTxt.getValue();
-        refresh();
+        refresh(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER);
       }
     });
     hp.add(filterTxt);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AbandonAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AbandonAction.java
new file mode 100644
index 0000000..d46edd3
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AbandonAction.java
@@ -0,0 +1,42 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.user.client.ui.Button;
+
+class AbandonAction extends ActionMessageBox {
+  private final Change.Id id;
+
+  AbandonAction(Button b, Change.Id id) {
+    super(b);
+    this.id = id;
+  }
+
+  void send(String message) {
+    ChangeApi.abandon(id.get(), message, new GerritCallback<ChangeInfo>() {
+      @Override
+      public void onSuccess(ChangeInfo result) {
+        Gerrit.display(PageLinks.toChange2(id));
+        hide();
+      }
+    });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionButton.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionButton.java
new file mode 100644
index 0000000..80949e5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionButton.java
@@ -0,0 +1,92 @@
+// 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.client.change;
+
+import com.google.gerrit.client.ErrorDialog;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo.ActionInfo;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+class ActionButton extends Button implements ClickHandler {
+  private final Change.Id changeId;
+  private final String revision;
+  private final ActionInfo action;
+
+  ActionButton(Change.Id changeId, ActionInfo action) {
+    this(changeId, null, action);
+  }
+
+  ActionButton(Change.Id changeId, String revision, ActionInfo action) {
+    super(new SafeHtmlBuilder()
+      .openDiv()
+      .append(action.label())
+      .closeDiv());
+    setStyleName("");
+    setTitle(action.title());
+    setEnabled(action.enabled());
+    addClickHandler(this);
+
+    this.changeId = changeId;
+    this.revision = revision;
+    this.action = action;
+  }
+
+  @Override
+  public void onClick(ClickEvent event) {
+    setEnabled(false);
+
+    AsyncCallback<NativeString> cb = new AsyncCallback<NativeString>() {
+      @Override
+      public void onFailure(Throwable caught) {
+        setEnabled(true);
+        new ErrorDialog(caught).center();
+      }
+
+      @Override
+      public void onSuccess(NativeString msg) {
+        setEnabled(true);
+        if (msg != null && !msg.asString().isEmpty()) {
+          // TODO Support better UI on UiAction results.
+          Window.alert(msg.asString());
+        }
+        Gerrit.display(PageLinks.toChange2(changeId));
+      }
+    };
+
+    RestApi api = revision != null
+        ? ChangeApi.revision(changeId.get(), revision)
+        : ChangeApi.change(changeId.get());
+    api.view(action.id());
+
+    if ("PUT".equalsIgnoreCase(action.method())) {
+      api.put(JavaScriptObject.createObject(), cb);
+    } else if ("DELETE".equalsIgnoreCase(action.method())) {
+      api.delete(cb);
+    } else {
+      api.post(JavaScriptObject.createObject(), cb);
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java
new file mode 100644
index 0000000..6824303
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.java
@@ -0,0 +1,104 @@
+// 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.client.change;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.globalkey.client.NpTextArea;
+import com.google.gwtexpui.user.client.PluginSafePopupPanel;
+
+abstract class ActionMessageBox extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, ActionMessageBox> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  static interface Style extends CssResource {
+    String popup();
+  }
+
+  private final Button activatingButton;
+  private PluginSafePopupPanel popup;
+
+  @UiField Style style;
+  @UiField NpTextArea message;
+  @UiField Button send;
+
+  ActionMessageBox(Button button) {
+    this.activatingButton = button;
+    initWidget(uiBinder.createAndBindUi(this));
+    send.setText(button.getText());
+  }
+
+  abstract void send(String message);
+
+  void show() {
+    if (popup != null) {
+      popup.hide();
+      popup = null;
+      return;
+    }
+
+    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    p.setStyleName(style.popup());
+    p.addAutoHidePartner(activatingButton.getElement());
+    p.addCloseHandler(new CloseHandler<PopupPanel>() {
+      @Override
+      public void onClose(CloseEvent<PopupPanel> event) {
+        if (popup == p) {
+          popup = null;
+        }
+      }
+    });
+    p.add(this);
+    p.showRelativeTo(activatingButton);
+    GlobalKey.dialog(p);
+    message.setFocus(true);
+    popup = p;
+  }
+
+  void hide() {
+    if (popup != null) {
+      popup.hide();
+      popup = null;
+    }
+  }
+
+  @UiHandler("message")
+  void onMessageKey(KeyPressEvent event) {
+    if ((event.getCharCode() == '\n' || event.getCharCode() == KeyCodes.KEY_ENTER)
+        && event.isControlKeyDown()) {
+      event.preventDefault();
+      event.stopPropagation();
+      onSend(null);
+    }
+  }
+
+  @UiHandler("send")
+  void onSend(ClickEvent e) {
+    send(message.getValue().trim());
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.ui.xml
new file mode 100644
index 0000000..d639150
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ActionMessageBox.ui.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'>
+  <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+  <ui:style type='com.google.gerrit.client.change.ActionMessageBox.Style'>
+    @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+
+    .popup { background-color: trimColor; }
+    .section {
+      padding: 5px 5px;
+      border-bottom: 1px solid #b8b8b8;
+    }
+  </ui:style>
+  <g:HTMLPanel>
+    <div class='{style.section}'>
+      <c:NpTextArea
+         visibleLines='3'
+         characterWidth='40'
+         ui:field='message'/>
+    </div>
+    <div class='{style.section}'>
+      <g:Button ui:field='send'
+          title='(Shortcut: Ctrl-Enter)'
+          styleName='{res.style.button}'>
+        <ui:attribute name='title'/>
+        <div><ui:msg>Send</ui:msg></div>
+      </g:Button>
+    </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
new file mode 100644
index 0000000..5fccc350
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
@@ -0,0 +1,171 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.ActionInfo;
+import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+
+import java.util.TreeSet;
+
+class Actions extends Composite {
+  private static final String[] CORE = {
+    "abandon", "restore", "revert", "topic",
+    "cherrypick", "submit", "rebase"};
+
+  interface Binder extends UiBinder<FlowPanel, Actions> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  @UiField Button cherrypick;
+  @UiField Button rebase;
+  @UiField Button revert;
+  @UiField Button submit;
+
+  @UiField Button abandon;
+  private AbandonAction abandonAction;
+
+  @UiField Button restore;
+  private RestoreAction restoreAction;
+
+  private Change.Id changeId;
+  private String revision;
+  private String project;
+  private String subject;
+  private String message;
+
+  Actions() {
+    initWidget(uiBinder.createAndBindUi(this));
+    getElement().setId("change_actions");
+  }
+
+  void display(ChangeInfo info, String revision, boolean canSubmit) {
+    this.revision = revision;
+
+    boolean hasUser = Gerrit.isSignedIn();
+    RevisionInfo revInfo = info.revision(revision);
+    CommitInfo commit = revInfo.commit();
+    changeId = info.legacy_id();
+    project = info.project();
+    subject = commit.subject();
+    message = commit.message();
+
+    initChangeActions(info, hasUser);
+    initRevisionActions(info, revInfo, canSubmit, hasUser);
+  }
+
+  private void initChangeActions(ChangeInfo info, boolean hasUser) {
+    NativeMap<ActionInfo> actions = info.has_actions()
+        ? info.actions()
+        : NativeMap.<ActionInfo> create();
+    actions.copyKeysIntoChildren("id");
+
+    abandon.setVisible(hasUser && actions.containsKey("abandon"));
+    restore.setVisible(hasUser && actions.containsKey("restore"));
+    revert.setVisible(hasUser && actions.containsKey("revert"));
+
+    if (hasUser) {
+      for (String id : filterNonCore(actions)) {
+        add(new ActionButton(changeId, actions.get(id)));
+      }
+    }
+  }
+
+  private void initRevisionActions(ChangeInfo info, RevisionInfo revInfo,
+      boolean canSubmit, boolean hasUser) {
+    boolean hasConflict = Gerrit.getConfig().testChangeMerge()
+        && !info.mergeable();
+
+    NativeMap<ActionInfo> actions = revInfo.has_actions()
+        ? revInfo.actions()
+        : NativeMap.<ActionInfo> create();
+    actions.copyKeysIntoChildren("id");
+
+    cherrypick.setVisible(hasUser && actions.containsKey("cherrypick"));
+    rebase.setVisible(hasUser && actions.containsKey("rebase"));
+    submit.setVisible(hasUser && !hasConflict
+        && canSubmit
+        && actions.containsKey("submit"));
+
+    if (hasUser) {
+      for (String id : filterNonCore(actions)) {
+        add(new ActionButton(changeId, revision, actions.get(id)));
+      }
+    }
+  }
+
+  private void add(ActionButton b) {
+    ((FlowPanel) getWidget()).add(b);
+  }
+
+  private static TreeSet<String> filterNonCore(NativeMap<ActionInfo> m) {
+    TreeSet<String> ids = new TreeSet<String>(m.keySet());
+    for (String id : CORE) {
+      ids.remove(id);
+    }
+    return ids;
+  }
+
+  boolean isSubmitEnabled() {
+    return submit.isVisible() && submit.isEnabled();
+  }
+
+  @UiHandler("abandon")
+  void onAbandon(ClickEvent e) {
+    if (abandonAction == null) {
+      abandonAction = new AbandonAction(abandon, changeId);
+    }
+    abandonAction.show();
+  }
+
+  @UiHandler("restore")
+  void onRestore(ClickEvent e) {
+    if (restoreAction == null) {
+      restoreAction = new RestoreAction(restore, changeId);
+    }
+    restoreAction.show();
+  }
+
+  @UiHandler("rebase")
+  void onRebase(ClickEvent e) {
+    RebaseAction.call(changeId, revision);
+  }
+
+  @UiHandler("submit")
+  void onSubmit(ClickEvent e) {
+    SubmitAction.call(changeId, revision);
+  }
+
+  @UiHandler("cherrypick")
+  void onCherryPick(ClickEvent e) {
+    CherryPickAction.call(cherrypick, changeId, revision, project, message);
+  }
+
+  @UiHandler("revert")
+  void onRevert(ClickEvent e) {
+    RevertAction.call(cherrypick, changeId, revision, project, subject);
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
new file mode 100644
index 0000000..b596359
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:style>
+    #change_actions {
+      padding-top: 2px;
+      padding-bottom: 20px;
+    }
+
+    #change_actions button {
+      margin: 6px 3px 0 0;
+      border-color: rgba(0, 0, 0, 0.1);
+      text-align: center;
+      font-size: 11px;
+      font-weight: bold;
+      border: 1px solid;
+      cursor: pointer;
+      color: #444;
+      background-color: #f5f5f5;
+      background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+      -webkit-border-radius: 2px;
+      -webkit-box-sizing: content-box;
+    }
+    #change_actions button div {
+      color: #444; 
+      height: 10px;
+      min-width: 54px;
+      line-height: 10px;
+      white-space: nowrap;
+    }
+
+    #change_actions button {
+      color: #444;
+      background-color: #f5f5f5;
+      background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+    }
+    #change_actions button div {color: #444;}
+
+    #change_actions button.red {
+      color: #d14836;
+      background-color: #d14836;
+      background-image: -webkit-linear-gradient(top, #d14836, #d14836);
+    }
+    #change_actions button.red div {color: #fff;}
+
+    #change_actions button.submit {
+      float: right;
+      color: white;
+      background-color: #4d90fe;
+      background-image: -webkit-linear-gradient(top, #4d90fe, #4d90fe);
+    }
+    #change_actions button.submit div {color: #fff;}
+  </ui:style>
+
+  <g:FlowPanel>
+    <g:Button ui:field='cherrypick' styleName=''>
+      <div><ui:msg>Cherry Pick</ui:msg></div>
+    </g:Button>
+    <g:Button ui:field='rebase' styleName=''>
+      <div><ui:msg>Rebase</ui:msg></div>
+    </g:Button>
+    <g:Button ui:field='revert' styleName=''>
+      <div><ui:msg>Revert</ui:msg></div>
+    </g:Button>
+
+    <g:Button ui:field='abandon' styleName='{style.red}'>
+      <div><ui:msg>Abandon</ui:msg></div>
+    </g:Button>
+    <g:Button ui:field='restore' styleName='{style.red}'>
+      <div><ui:msg>Restore</ui:msg></div>
+    </g:Button>
+
+    <g:Button ui:field='submit' styleName='{style.submit}'>
+      <div><ui:msg>Submit</ui:msg></div>
+    </g:Button>
+  </g:FlowPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
new file mode 100644
index 0000000..9327f7c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
@@ -0,0 +1,463 @@
+// 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.client.change;
+
+import com.google.gerrit.client.FormatUtil;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
+import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.changes.StarredChanges;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.diff.DiffApi;
+import com.google.gerrit.client.diff.FileInfo;
+import com.google.gerrit.client.projects.ConfigInfoCache;
+import com.google.gerrit.client.projects.ConfigInfoCache.Entry;
+import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.ChangeLink;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.client.ui.Screen;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.AnchorElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.ToggleButton;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.globalkey.client.KeyCommand;
+import com.google.gwtexpui.globalkey.client.KeyCommandSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ChangeScreen2 extends Screen {
+  interface Binder extends UiBinder<HTMLPanel, ChangeScreen2> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  interface Style extends CssResource {
+    String labelName();
+    String label_user();
+    String label_ok();
+    String label_reject();
+    String label_may();
+    String label_need();
+    String replyBox();
+  }
+
+  private final Change.Id changeId;
+  private String revision;
+  private CommentLinkProcessor commentLinkProcessor;
+
+  private KeyCommandSet keysNavigation;
+  private KeyCommandSet keysAction;
+  private List<HandlerRegistration> keys = new ArrayList<HandlerRegistration>(2);
+
+  @UiField Style style;
+  @UiField ToggleButton star;
+  @UiField Reload reload;
+  @UiField AnchorElement permalink;
+
+  @UiField Element reviewersText;
+  @UiField Element ccText;
+  @UiField Element changeIdText;
+  @UiField Element ownerText;
+  @UiField Element statusText;
+  @UiField Element projectText;
+  @UiField Element branchText;
+  @UiField Element submitActionText;
+  @UiField Element notMergeable;
+  @UiField CopyableLabel idText;
+  @UiField Topic topic;
+  @UiField Element actionText;
+  @UiField Element actionDate;
+
+  @UiField Actions actions;
+  @UiField Element revisionParent;
+  @UiField ListBox revisionList;
+  @UiField Labels labels;
+  @UiField CommitBox commit;
+  @UiField FileTable files;
+  @UiField FlowPanel history;
+
+  @UiField Button reply;
+  @UiField QuickApprove quickApprove;
+  private ReplyAction replyAction;
+
+  public ChangeScreen2(Change.Id changeId, String revision) {
+    this.changeId = changeId;
+    this.revision = revision != null && !revision.isEmpty() ? revision : null;
+    add(uiBinder.createAndBindUi(this));
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    ChangeApi.detail(changeId.get(),
+      EnumSet.of(
+          ListChangesOption.ALL_REVISIONS,
+          ListChangesOption.CURRENT_ACTIONS),
+      new GerritCallback<ChangeInfo>() {
+        @Override
+        public void onSuccess(ChangeInfo info) {
+          info.init();
+          loadConfigInfo(info);
+        }
+      });
+  }
+
+  @Override
+  protected void onUnload() {
+    for (HandlerRegistration h : keys) {
+      h.removeHandler();
+    }
+    keys.clear();
+    super.onUnload();
+  }
+
+  @Override
+  protected void onInitUI() {
+    super.onInitUI();
+    setHeaderVisible(false);
+    Resources.I.style().ensureInjected();
+    star.setVisible(Gerrit.isSignedIn());
+    labels.init(style, statusText);
+
+    keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
+    keysNavigation.add(new KeyCommand(0, 'u', Util.C.upToChangeList()) {
+      @Override
+      public void onKeyPress(final KeyPressEvent event) {
+        Gerrit.displayLastChangeList();
+      }
+    });
+    keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReload()) {
+      @Override
+      public void onKeyPress(final KeyPressEvent event) {
+        reload.reload();
+      }
+    });
+
+    keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
+    if (Gerrit.isSignedIn()) {
+      keysAction.add(new KeyCommand(0, 'r', Util.C.keyPublishComments()) {
+        @Override
+        public void onKeyPress(KeyPressEvent event) {
+          onReply(null);
+        }
+      });
+      keysAction.add(new KeyCommand(0, 's', Util.C.changeTableStar()) {
+        @Override
+        public void onKeyPress(KeyPressEvent event) {
+          star.setValue(!star.getValue(), true);
+        }
+      });
+    }
+  }
+
+  @Override
+  public void registerKeys() {
+    super.registerKeys();
+    keys.add(GlobalKey.add(this, keysNavigation));
+    keys.add(GlobalKey.add(this, keysAction));
+    files.registerKeys();
+  }
+
+  @UiHandler("star")
+  void onToggleStar(ValueChangeEvent<Boolean> e) {
+    StarredChanges.toggleStar(changeId, e.getValue());
+  }
+
+  @UiHandler("revisionList")
+  void onChangeRevision(ChangeEvent e) {
+    int idx = revisionList.getSelectedIndex();
+    if (0 <= idx) {
+      String n = revisionList.getValue(idx);
+      revisionList.setEnabled(false);
+      Gerrit.display(
+          PageLinks.toChange2(changeId, n),
+          new ChangeScreen2(changeId, n));
+    }
+  }
+
+  @UiHandler("reply")
+  void onReply(ClickEvent e) {
+    replyAction.onReply();
+  }
+
+  private void loadConfigInfo(final ChangeInfo info) {
+    info.revisions().copyKeysIntoChildren("name");
+    final RevisionInfo rev = resolveRevisionToDisplay(info);
+
+    CallbackGroup group = new CallbackGroup();
+    loadDiff(rev, group);
+    loadCommit(rev, group);
+    ConfigInfoCache.add(info);
+    ConfigInfoCache.get(info.project_name_key(),
+      group.add(new ScreenLoadCallback<ConfigInfoCache.Entry>(this) {
+        @Override
+        protected void preDisplay(Entry result) {
+          commentLinkProcessor = result.getCommentLinkProcessor();
+          setTheme(result.getTheme());
+          renderChangeInfo(info);
+        }
+      }));
+    group.done();
+
+    if (info.status().isOpen() && rev.name().equals(info.current_revision())) {
+      loadSubmitAction(rev);
+    }
+  }
+
+  private void loadDiff(final RevisionInfo rev, CallbackGroup group) {
+    DiffApi.list(changeId.get(),
+      rev.name(),
+      group.add(new AsyncCallback<NativeMap<FileInfo>>() {
+        @Override
+        public void onSuccess(NativeMap<FileInfo> m) {
+          files.setRevisions(null, new PatchSet.Id(changeId, rev._number()));
+          files.setValue(m);
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+        }
+      }));
+  }
+
+  private void loadCommit(final RevisionInfo rev, CallbackGroup group) {
+    ChangeApi.revision(changeId.get(), rev.name())
+      .view("commit")
+      .get(group.add(new AsyncCallback<CommitInfo>() {
+        @Override
+        public void onSuccess(CommitInfo info) {
+          rev.set_commit(info);
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+        }
+      }));
+  }
+
+  private void loadSubmitAction(final RevisionInfo rev) {
+    // Submit action is less important than other data.
+    // Defer so browser can start other requests first.
+    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+      @Override
+      public void execute() {
+        ChangeApi.revision(changeId.get(), rev.name())
+          .view("submit_type")
+          .get(new AsyncCallback<NativeString>() {
+            @Override
+            public void onSuccess(NativeString result) {
+              String action = result.asString();
+              try {
+                SubmitType type = Project.SubmitType.valueOf(action);
+                submitActionText.setInnerText(
+                    com.google.gerrit.client.admin.Util.toLongString(type));
+              } catch (IllegalArgumentException e) {
+                submitActionText.setInnerText(action);
+              }
+            }
+
+            @Override
+            public void onFailure(Throwable caught) {
+            }
+          });
+      }
+    });
+  }
+
+  private RevisionInfo resolveRevisionToDisplay(ChangeInfo info) {
+    if (revision == null) {
+      revision = info.current_revision();
+    } else if (!info.revisions().containsKey(revision)) {
+      JsArray<RevisionInfo> list = info.revisions().values();
+      for (int i = 0; i < list.length(); i++) {
+        RevisionInfo r = list.get(i);
+        if (revision.equals(String.valueOf(r._number()))) {
+          revision = r.name();
+          break;
+        }
+      }
+    }
+    return info.revision(revision);
+  }
+
+  private void renderChangeInfo(ChangeInfo info) {
+    statusText.setInnerText(Util.toLongString(info.status()));
+    boolean canSubmit = labels.set(info);
+
+    renderOwner(info);
+    renderReviewers(info);
+    renderActionTextDate(info);
+    renderRevisions(info);
+    renderHistory(info);
+    actions.display(info, revision, canSubmit);
+
+    star.setValue(info.starred());
+    permalink.setHref(ChangeLink.permalink(changeId));
+    changeIdText.setInnerText(String.valueOf(info.legacy_id()));
+    projectText.setInnerText(info.project());
+    branchText.setInnerText(info.branch());
+    idText.setText("Change-Id: " + info.change_id());
+    idText.setPreviewText(info.change_id());
+    reload.set(info);
+    topic.set(info);
+    commit.set(commentLinkProcessor, info, revision);
+    quickApprove.set(info, revision);
+
+    boolean hasConflict = Gerrit.getConfig().testChangeMerge() && !info.mergeable();
+    setVisible(notMergeable, info.status().isOpen() && hasConflict);
+
+    if (Gerrit.isSignedIn()) {
+      replyAction = new ReplyAction(info, revision, style, reply);
+      if (topic.canEdit()) {
+        keysAction.add(new KeyCommand(0, 't', Util.C.keyEditTopic()) {
+          @Override
+          public void onKeyPress(KeyPressEvent event) {
+            topic.onEdit();
+          }
+        });
+      }
+    }
+    reply.setVisible(replyAction != null);
+
+    if (canSubmit && !hasConflict && actions.isSubmitEnabled()) {
+      statusText.setInnerText(Util.C.readyToSubmit());
+    } else if (canSubmit && hasConflict) {
+      statusText.setInnerText(Util.C.mergeConflict());
+    }
+
+    StringBuilder sb = new StringBuilder();
+    sb.append(Util.M.changeScreenTitleId(info.id_abbreviated()));
+    if (info.subject() != null) {
+      sb.append(": ");
+      sb.append(info.subject());
+    }
+    setWindowTitle(sb.toString());
+  }
+
+  private void renderReviewers(ChangeInfo info) {
+    // TODO Fix approximation of reviewers and CC list(s).
+    Map<Integer, AccountInfo> r = new HashMap<Integer, AccountInfo>();
+    Map<Integer, AccountInfo> cc = new HashMap<Integer, AccountInfo>();
+    for (LabelInfo label : Natives.asList(info.all_labels().values())) {
+      if (label.all() != null) {
+        for (ApprovalInfo ai : Natives.asList(label.all())) {
+          (ai.value() != 0 ? r : cc).put(ai._account_id(), ai);
+        }
+      }
+    }
+    for (Integer i : r.keySet()) {
+      cc.remove(i);
+    }
+    reviewersText.setInnerSafeHtml(labels.formatUserList(r.values()));
+    ccText.setInnerSafeHtml(labels.formatUserList(cc.values()));
+  }
+
+  private void renderRevisions(ChangeInfo info) {
+    if (info.revisions().size() == 1) {
+      UIObject.setVisible(revisionParent, false);
+      return;
+    }
+
+    JsArray<RevisionInfo> list = info.revisions().values();
+    Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() {
+      @Override
+      public int compare(RevisionInfo a, RevisionInfo b) {
+        return a._number() - b._number();
+      }
+    });
+
+    int selected = -1;
+    for (int i = 0; i < list.length(); i++) {
+      RevisionInfo r = list.get(i);
+      revisionList.addItem(
+          r._number() + ": " + r.name().substring(0, 6),
+          "" + r._number());
+      if (revision.equals(r.name())) {
+        selected = i;
+      }
+    }
+    if (0 <= selected) {
+      revisionList.setSelectedIndex(selected);
+    }
+  }
+
+  private void renderOwner(ChangeInfo info) {
+    // TODO info card hover
+    ownerText.setInnerText(info.owner().name() != null
+        ? info.owner().name()
+        : Gerrit.getConfig().getAnonymousCowardName());
+  }
+
+  private void renderActionTextDate(ChangeInfo info) {
+    String action;
+    if (info.created().equals(info.updated())) {
+      action = Util.C.changeInfoBlockUploaded();
+    } else {
+      action = Util.C.changeInfoBlockUpdated();
+    }
+    actionText.setInnerText(action);
+    actionDate.setInnerText(FormatUtil.relativeFormat(info.updated()));
+  }
+
+  private void renderHistory(ChangeInfo info) {
+    JsArray<MessageInfo> messages = info.messages();
+    if (messages != null) {
+      for (int i = 0; i < messages.length(); i++) {
+        history.add(new Message(commentLinkProcessor, messages.get(i)));
+      }
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
new file mode 100644
index 0000000..e7d7554
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
@@ -0,0 +1,283 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:c='urn:import:com.google.gerrit.client.change'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:clippy='urn:import:com.google.gwtexpui.clippy.client'>
+  <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+  <ui:style type='com.google.gerrit.client.change.ChangeScreen2.Style'>
+    @eval textColor com.google.gerrit.client.Gerrit.getTheme().textColor;
+    @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+
+    @def INFO_WIDTH 450px;
+    @def HEADER_HEIGHT 29px;
+
+    .headerLine {
+      position: relative;
+      background-color: trimColor;
+      height: HEADER_HEIGHT;
+    }
+
+    .sectionHeader {
+      background-color: trimColor;
+      font-weight: bold;
+      color: textColor;
+      padding: 7px 10px;
+    }
+    .historyHeader {
+      width: 1150px;
+    }
+
+    .idBlock {
+      position: relative;
+      width: INFO_WIDTH;
+      height: HEADER_HEIGHT;
+      background-color: trimColor;
+      color: textColor;
+      font-family: sans-serif;
+      font-weight: bold;
+    }
+    .star {
+      cursor: pointer;
+      outline: none;
+      position: absolute;
+      left: 5px;
+      top: 5px;
+    }
+    .idLine, .idStatus {
+      line-height: HEADER_HEIGHT;
+    }
+    .idLine {
+      position: absolute;
+      top: 0;
+      left: 29px;
+    }
+    .idStatus {
+      position: absolute;
+      top: 0;
+      right: 26px;
+    }
+    .reload {
+      display: block;
+      position: absolute;
+      top: 7px;
+      right: 5px;
+      cursor: pointer;
+    }
+
+    .headerButtons {
+      position: absolute;
+      top: 0;
+      left: INFO_WIDTH;
+      height: HEADER_HEIGHT;
+      padding-left: 5px;
+    }
+
+    .revisionList {
+      position: absolute;
+      top: 2px;
+      right: 10px;
+    }
+
+    .headerTable {
+      border-spacing: 0;
+      width: 100%;
+    }
+
+    .headerTable th {
+      width: 60px;
+      color: #444;
+      font-weight: normal;
+      vertical-align: top;
+      text-align: left;
+      padding-right: 5px;
+    }
+
+    .clippy div {
+      float: right;
+    }
+
+    .infoColumn {
+      width: 440px;
+      padding-right: 10px;
+      vertical-align: top;
+    }
+
+    #change_infoTable {
+      border-spacing: 0;
+      width: 100%;
+      margin-left: 2px;
+      margin-right: 5px;
+    }
+
+    .notMergeable {
+      float: right;
+      font-weight: bold;
+      color: red;
+    }
+
+    .commitColumn {
+      padding: 0;
+      vertical-align: top;
+    }
+
+    .labels {
+      border-spacing: 0;
+      padding: 0;
+    }
+    .labelName {
+      color: #444;
+      vertical-align: top;
+      text-align: left;
+      padding-right: 5px;
+      white-space: nowrap;
+    }
+    .label_user {white-space: nowrap;}
+    .label_ok {color: #060;}
+    .label_reject {color: #d14836;}
+    .label_need {color: rgb(189, 189, 67);}
+    .label_may {color: #fff;}
+
+    .headerButtons button {
+      margin: 6px 3px 0 0;
+      border-color: rgba(0, 0, 0, 0.1);
+      text-align: center;
+      font-size: 11px;
+      font-weight: bold;
+      border: 1px solid;
+      cursor: pointer;
+      color: #444;
+      background-color: #f5f5f5;
+      background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+      -webkit-border-radius: 2px;
+      -webkit-box-sizing: content-box;
+    }
+    .headerButtons button div {
+      color: #444; 
+      height: 10px;
+      min-width: 54px;
+      line-height: 10px;
+      white-space: nowrap;
+    }
+    button.quickApprove {
+      color: #d14836;
+      background-color: #d14836;
+      background-image: -webkit-linear-gradient(top, #d14836, #d14836);
+    }
+    button.quickApprove div { color: #fff; }
+
+    .replyBox {
+      background-color: trimColor;
+    }
+  </ui:style>
+
+  <g:HTMLPanel>
+    <div class='{style.headerLine}'>
+      <div class='{style.idBlock}'>
+        <c:StarIcon ui:field='star' styleName='{style.star}'/>
+        <div class='{style.idLine}'>
+          <ui:msg>Change <span ui:field='changeIdText'/> by <span ui:field='ownerText'/></ui:msg>
+        </div>
+        <div ui:field='statusText' class='{style.idStatus}'/>
+        <a ui:field='permalink' class='{style.reload}'>
+          <c:Reload ui:field='reload'
+              title='Reload the change (Shortcut: R)'>
+            <ui:attribute name='title'/>
+          </c:Reload>
+        </a>
+      </div>
+      <div class='{style.headerButtons}'>
+        <g:Button ui:field='reply'
+            styleName=''
+            title='Reply and score (Shortcut: r)'>
+          <ui:attribute name='title'/>
+          <div><ui:msg>Reply&#8230;</ui:msg></div>
+        </g:Button>
+        <c:QuickApprove ui:field='quickApprove'
+            styleName='{style.quickApprove}'
+            title='Apply score with one click'>
+          <ui:attribute name='title'/>
+        </c:QuickApprove>
+      </div>
+      <div class='{style.revisionList}' ui:field='revisionParent'>
+        <ui:msg>Revision <g:ListBox ui:field='revisionList'/></ui:msg>
+      </div>
+    </div>
+
+    <table class='{style.headerTable}'>
+      <tr>
+        <td class='{style.infoColumn}'>
+          <table id='change_infoTable'>
+            <tr>
+              <th><ui:msg>Reviewers</ui:msg></th>
+              <td ui:field='reviewersText'/>
+            </tr>
+            <tr>
+              <th><ui:msg>CC</ui:msg></th>
+              <td ui:field='ccText'/>
+            </tr>
+            <tr>
+              <th><ui:msg>Project</ui:msg></th>
+              <td ui:field='projectText'/>
+            </tr>
+            <tr>
+              <th><ui:msg>Branch</ui:msg></th>
+              <td ui:field='branchText'/>
+            </tr>
+            <tr>
+              <th><ui:msg>Strategy</ui:msg></th>
+              <td>
+                <span ui:field='submitActionText'/>
+                <div ui:field='notMergeable' class='{style.notMergeable}'>
+                  <ui:msg>Cannot Merge</ui:msg>
+                </div>
+              </td>
+            </tr>
+            <tr><td colspan='2'><c:Actions ui:field='actions'/></td></tr>
+            <tr>
+              <th ui:field='actionText'/>
+              <td ui:field='actionDate'/>
+            </tr>
+            <tr>
+              <th><ui:msg>Id</ui:msg></th>
+              <td><clippy:CopyableLabel styleName='{style.clippy}' ui:field='idText'/></td>
+            </tr>
+            <tr>
+              <th><ui:msg>Topic</ui:msg></th>
+              <td><c:Topic ui:field='topic'/></td>
+            </tr>
+          </table>
+          <hr/>
+          <c:Labels ui:field='labels' styleName='{style.labels}'/>
+        </td>
+
+        <td class='{style.commitColumn}'>
+          <c:CommitBox ui:field='commit'/>
+        </td>
+      </tr>
+    </table>
+
+    <div class='{style.sectionHeader}'
+      ><ui:msg>Files</ui:msg></div>
+    <c:FileTable ui:field='files'/>
+
+    <div class='{style.sectionHeader} {style.historyHeader}'
+      ><ui:msg>History</ui:msg></div>
+    <g:FlowPanel ui:field='history'/>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CherryPickAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CherryPickAction.java
new file mode 100644
index 0000000..76c7029
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CherryPickAction.java
@@ -0,0 +1,63 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.CherryPickDialog;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.user.client.ui.Button;
+
+class CherryPickAction {
+  static void call(Button b, final Change.Id id, final String revision,
+      String project, final String commitMessage) {
+    // TODO Replace CherryPickDialog with a nicer looking display.
+    b.setEnabled(false);
+    new CherryPickDialog(b, new Project.NameKey(project)) {
+      {
+        sendButton.setText(Util.C.buttonCherryPickChangeSend());
+        message.setText(Util.M.cherryPickedChangeDefaultMessage(
+            commitMessage.trim(),
+            revision));
+      }
+
+      @Override
+      public void onSend() {
+        ChangeApi.cherrypick(id.get(), revision,
+            getDestinationBranch(),
+            getMessageText(),
+            new GerritCallback<ChangeInfo>() {
+              @Override
+              public void onSuccess(ChangeInfo result) {
+                sent = true;
+                hide();
+                Gerrit.display(PageLinks.toChange2(result.legacy_id()));
+              }
+
+              @Override
+              public void onFailure(Throwable caught) {
+                enableButtons(true);
+                super.onFailure(caught);
+              }
+            });
+      }
+    }.center();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
new file mode 100644
index 0000000..820bf49
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
@@ -0,0 +1,76 @@
+// 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.client.change;
+
+import com.google.gerrit.client.FormatUtil;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.GitwebLink;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.AnchorElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+class CommitBox extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, CommitBox> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  @UiField Element commitName;
+  @UiField AnchorElement browserLink;
+  @UiField Element authorNameEmail;
+  @UiField Element authorDate;
+  @UiField Element committerNameEmail;
+  @UiField Element committerDate;
+  @UiField Element commitMessageText;
+
+  CommitBox() {
+    initWidget(uiBinder.createAndBindUi(this));
+  }
+
+  void set(CommentLinkProcessor commentLinkProcessor,
+      ChangeInfo change,
+      String revision) {
+    RevisionInfo revInfo = change.revision(revision);
+    CommitInfo commit = revInfo.commit();
+
+    commitName.setInnerText(revision);
+    format(commit.author(), authorNameEmail, authorDate);
+    format(commit.committer(), committerNameEmail, committerDate);
+    commitMessageText.setInnerSafeHtml(commentLinkProcessor.apply(
+        new SafeHtmlBuilder().append(commit.message()).linkify()));
+
+    GitwebLink gw = Gerrit.getGitwebLink();
+    if (gw != null && gw.canLink(revInfo)) {
+      browserLink.setInnerText(gw.getLinkName());
+      browserLink.setHref(gw.toRevision(change.project(), revision));
+    } else {
+      UIObject.setVisible(browserLink, false);
+    }
+  }
+
+  private void format(GitPerson person, Element name, Element date) {
+    name.setInnerText(person.name() + " <" + person.email() + ">");
+    date.setInnerText(FormatUtil.mediumFormat(person.date()));
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
new file mode 100644
index 0000000..c1a6d24
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:style>
+    .commitHeader {
+      border-spacing: 0;
+      padding: 0;
+      width: 564px;
+    }
+
+    .commitHeader th { width: 70px; }
+    .commitHeader td { white-space: pre; }
+
+    .commitMessageBox { margin: 2px; }
+    .commitMessage {
+      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+      border: 1px solid white;
+      background-color: white;
+      font-family: monospace;
+      white-space: pre;
+      width: 47em;
+    }
+  </ui:style>
+  <g:HTMLPanel>
+    <table class='{style.commitHeader}'>
+      <tr>
+        <th><ui:msg>Commit</ui:msg></th>
+        <td ui:field='commitName'/>
+        <td><a ui:field='browserLink' href=""/></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Author</ui:msg></th>
+        <td ui:field='authorNameEmail'/>
+        <td ui:field='authorDate'/>
+      </tr>
+      <tr>
+        <th><ui:msg>Committer</ui:msg></th>
+        <td ui:field='committerNameEmail'/>
+        <td ui:field='committerDate'/>
+      </tr>
+    </table>
+
+    <div class='{style.commitMessageBox}'>
+      <div class='{style.commitMessage}' ui:field='commitMessageText'/>
+    </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
new file mode 100644
index 0000000..6e545cf
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -0,0 +1,354 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.diff.FileInfo;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwtexpui.progress.client.ProgressBar;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+import com.google.gwtorm.client.KeyUtil;
+
+import java.util.Collections;
+import java.util.Comparator;
+
+class FileTable extends FlowPanel {
+  private static final FileTableResources R = GWT
+      .create(FileTableResources.class);
+
+  interface FileTableResources extends ClientBundle {
+    @Source("file_table.css")
+    FileTableCss css();
+  }
+
+  interface FileTableCss extends CssResource {
+    String pointer();
+    String pathColumn();
+    String deltaColumn1();
+    String deltaColumn2();
+    String inserted();
+    String deleted();
+  }
+
+  private PatchSet.Id base;
+  private PatchSet.Id curr;
+  private MyTable table;
+  private boolean register;
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    R.css().ensureInjected();
+  }
+
+  void setRevisions(PatchSet.Id base, PatchSet.Id curr) {
+    this.base = base;
+    this.curr = curr;
+  }
+
+  void setValue(NativeMap<FileInfo> fileMap) {
+    JsArray<FileInfo> list = fileMap.values();
+    Collections.sort(Natives.asList(list), new Comparator<FileInfo>() {
+      @Override
+      public int compare(FileInfo a, FileInfo b) {
+        if (Patch.COMMIT_MSG.equals(a.path())) {
+          return -1;
+        } else if (Patch.COMMIT_MSG.equals(b.path())) {
+          return 1;
+        }
+        return a.path().compareTo(b.path());
+      }
+    });
+
+    DisplayCommand cmd = new DisplayCommand(list);
+    if (cmd.execute()) {
+      cmd.showProgressBar();
+      Scheduler.get().scheduleIncremental(cmd);
+    }
+  }
+
+  void registerKeys() {
+    register = true;
+
+    if (table != null) {
+      table.setRegisterKeys(true);
+    }
+  }
+
+  private void setTable(MyTable table) {
+    clear();
+    add(table);
+    this.table = table;
+    if (register) {
+      table.setRegisterKeys(true);
+    }
+  }
+
+  private String url(FileInfo info) {
+    // TODO(sop): Switch to Dispatcher.toPatchSideBySide.
+    Change.Id c = curr.getParentKey();
+    StringBuilder p = new StringBuilder();
+    p.append("/c/").append(c).append('/');
+    if (base != null) {
+      p.append(base.get()).append("..");
+    }
+    p.append(curr.get()).append('/').append(KeyUtil.encode(info.path()));
+    p.append(info.binary() ? ",unified" : "cm");
+    return p.toString();
+  }
+
+  private final class MyTable extends NavigationTable<FileInfo> {
+    private final JsArray<FileInfo> list;
+
+    MyTable(JsArray<FileInfo> list) {
+      this.list = list;
+      table.setWidth("");
+
+      keysNavigation.add(new PrevKeyCommand(0, 'k', Util.C.patchTablePrev()));
+      keysNavigation.add(new NextKeyCommand(0, 'j', Util.C.patchTableNext()));
+      keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.patchTableOpenDiff()));
+      keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER,
+          Util.C.patchTableOpenDiff()));
+
+      setSavePointerId(
+          (base != null ? base.toString() + ".." : "")
+          + curr.toString());
+    }
+
+    @Override
+    protected Object getRowItemKey(FileInfo item) {
+      return item.path();
+    }
+
+    @Override
+    protected FileInfo getRowItem(int row) {
+      if (1 <= row && row <= list.length()) {
+        return list.get(row - 1);
+      }
+      return null;
+    }
+
+    @Override
+    protected void onOpenRow(int row) {
+      if (1 <= row && row <= list.length()) {
+        Gerrit.display(url(list.get(row - 1)));
+      }
+    }
+  }
+
+  private final class DisplayCommand implements RepeatingCommand {
+    private final SafeHtmlBuilder sb = new SafeHtmlBuilder();
+    private final MyTable table;
+    private final JsArray<FileInfo> list;
+    private boolean attached;
+    private int row;
+    private double start;
+    private ProgressBar meter;
+
+    private int inserted;
+    private int deleted;
+
+    private DisplayCommand(JsArray<FileInfo> list) {
+      this.table = new MyTable(list);
+      this.list = list;
+    }
+
+    public boolean execute() {
+      boolean attachedNow = isAttached();
+      if (!attached && attachedNow) {
+        // Remember that we have been attached at least once. If
+        // later we find we aren't attached we should stop running.
+        attached = true;
+      } else if (attached && !attachedNow) {
+        // If the user navigated away, we aren't in the DOM anymore.
+        // Don't continue to render.
+        return false;
+      }
+
+      start = System.currentTimeMillis();
+      if (row == 0) {
+        header(sb);
+        computeInsertedDeleted();
+      }
+      while (row < list.length()) {
+        render(sb, list.get(row));
+        if ((++row % 10) == 0 && longRunning()) {
+          updateMeter();
+          return true;
+        }
+      }
+      footer(sb);
+      table.resetHtml(sb);
+      table.finishDisplay();
+      setTable(table);
+      return false;
+    }
+
+    private void computeInsertedDeleted() {
+      inserted = 0;
+      deleted = 0;
+      for (int i = 0; i < list.length(); i++) {
+        FileInfo info = list.get(i);
+        if (!Patch.COMMIT_MSG.equals(info.path()) && !info.binary()) {
+          inserted += info.lines_inserted();
+          deleted += info.lines_deleted();
+        }
+      }
+    }
+
+    void showProgressBar() {
+      if (meter == null) {
+        meter = new ProgressBar(Util.M.loadingPatchSet(curr.get()));
+        FileTable.this.clear();
+        FileTable.this.add(meter);
+      }
+      updateMeter();
+    }
+
+    void updateMeter() {
+      if (meter != null) {
+        int n = list.length();
+        meter.setValue((100 * row) / n);
+      }
+    }
+
+    private boolean longRunning() {
+      return System.currentTimeMillis() - start > 200;
+    }
+
+    private void header(SafeHtmlBuilder sb) {
+      sb.openTr();
+      sb.openTh().setStyleName(Gerrit.RESOURCES.css().iconCell()).closeTh();
+      sb.openTh().append(Util.C.patchTableColumnName()).closeTh();
+      sb.openTh()
+        .setAttribute("colspan", 2)
+        .append(Util.C.patchTableColumnSize())
+        .closeTh();
+      sb.closeTr();
+    }
+
+    private void render(SafeHtmlBuilder sb, FileInfo info) {
+      sb.openTr();
+      sb.openTd().setStyleName(R.css().pointer()).closeTd();
+      columnPath(sb, info);
+      columnDelta1(sb, info);
+      columnDelta2(sb, info);
+      sb.closeTr();
+    }
+
+    private void columnPath(SafeHtmlBuilder sb, FileInfo info) {
+      // TODO(sop): Use JS to link, avoiding early URL update.
+      sb.openTd()
+        .setStyleName(R.css().pathColumn())
+        .openAnchor()
+        .setAttribute("href", "#" + url(info))
+        .append(Patch.COMMIT_MSG.equals(info.path())
+            ? Util.C.commitMessage()
+            : info.path())
+        .closeAnchor()
+        .closeTd();
+    }
+
+    private void columnDelta1(SafeHtmlBuilder sb, FileInfo info) {
+      sb.openTd().setStyleName(R.css().deltaColumn1());
+      if (!Patch.COMMIT_MSG.equals(info.path()) && !info.binary()) {
+        sb.append(info.lines_inserted() + info.lines_deleted());
+      }
+      sb.closeTd();
+    }
+
+    private void columnDelta2(SafeHtmlBuilder sb, FileInfo info) {
+      sb.openTd().setStyleName(R.css().deltaColumn2());
+      if (!Patch.COMMIT_MSG.equals(info.path()) && !info.binary()
+          && (info.lines_inserted() != 0 || info.lines_deleted() != 0)) {
+        int w = 80;
+        int t = inserted + deleted;
+        int i = Math.max(5, (int) (((double) w) * info.lines_inserted() / t));
+        int d = Math.max(5, (int) (((double) w) * info.lines_deleted() / t));
+
+        sb.setAttribute(
+            "title",
+            Util.M.patchTableSize_LongModify(info.lines_inserted(),
+                info.lines_deleted()));
+
+        if (0 < info.lines_inserted()) {
+          sb.openDiv()
+            .setStyleName(R.css().inserted())
+            .setAttribute("style", "width:" + i + "px")
+            .closeDiv();
+        }
+        if (0 < info.lines_deleted()) {
+          sb.openDiv()
+            .setStyleName(R.css().deleted())
+            .setAttribute("style", "width:" + d + "px")
+            .closeDiv();
+        }
+      }
+      sb.closeTd();
+    }
+
+    private void footer(SafeHtmlBuilder sb) {
+      sb.openTr();
+      sb.openTd().setStyleName(Gerrit.RESOURCES.css().iconCell()).closeTd();
+      sb.openTd().closeTd(); // path
+
+      // delta1
+      sb.openTh().setStyleName(R.css().deltaColumn1())
+        .append(Util.M.patchTableSize_Modify(inserted, deleted))
+        .closeTh();
+
+      // delta2
+      sb.openTh().setStyleName(R.css().deltaColumn2());
+      int w = 80;
+      int t = inserted + deleted;
+      int i = Math.max(1, (int) (((double) w) * inserted / t));
+      int d = Math.max(1, (int) (((double) w) * deleted / t));
+      if (i + d > w && i > d) {
+        i = w - d;
+      } else if (i + d > w && d > i) {
+        d = w - i;
+      }
+      if (0 < inserted) {
+        sb.openDiv()
+        .setStyleName(R.css().inserted())
+        .setAttribute("style", "width:" + i + "px")
+        .closeDiv();
+      }
+      if (0 < deleted) {
+        sb.openDiv()
+          .setStyleName(R.css().deleted())
+          .setAttribute("style", "width:" + d + "px")
+          .closeDiv();
+      }
+      sb.closeTh();
+
+      sb.closeTr();
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
new file mode 100644
index 0000000..4df1efa
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
@@ -0,0 +1,211 @@
+// 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.client.change;
+
+import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.safehtml.client.SafeHtml;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Displays a table of label and reviewer scores. */
+class Labels extends Grid {
+  private ChangeScreen2.Style style;
+  private Element statusText;
+
+  void init(ChangeScreen2.Style style, Element statusText) {
+    this.style = style;
+    this.statusText = statusText;
+  }
+
+  boolean set(ChangeInfo info) {
+    List<String> names = new ArrayList<String>(info.labels());
+    Collections.sort(names);
+
+    boolean canSubmit = info.status().isOpen();
+    resize(names.size(), 2);
+
+    for (int row = 0; row < names.size(); row++) {
+      String name = names.get(row);
+      LabelInfo label = info.label(name);
+      setText(row, 0, name);
+      if (label.all() != null) {
+        setWidget(row, 1, renderUsers(label));
+      }
+      getCellFormatter().setStyleName(row, 0, style.labelName());
+      getCellFormatter().addStyleName(row, 0, getStyleForLabel(label));
+
+      if (canSubmit && info.status() == Change.Status.NEW) {
+        switch (label.status()) {
+          case NEED:
+            statusText.setInnerText("Needs " + name);
+            canSubmit = false;
+            break;
+          case REJECT:
+          case IMPOSSIBLE:
+            statusText.setInnerText("Not " + name);
+            canSubmit = false;
+            break;
+          default:
+            break;
+          }
+      }
+    }
+    return canSubmit;
+  }
+
+  private Widget renderUsers(LabelInfo label) {
+    Map<Integer, List<ApprovalInfo>> m = new HashMap<Integer, List<ApprovalInfo>>(4);
+    int approved = 0, rejected = 0;
+
+    for (ApprovalInfo ai : Natives.asList(label.all())) {
+      if (ai.value() != 0) {
+        List<ApprovalInfo> l = m.get(Integer.valueOf(ai.value()));
+        if (l == null) {
+          l = new ArrayList<ApprovalInfo>(label.all().length());
+          m.put(Integer.valueOf(ai.value()), l);
+        }
+        l.add(ai);
+
+        if (isRejected(label, ai)) {
+          rejected = ai.value();
+        } else if (isApproved(label, ai)) {
+          approved = ai.value();
+        }
+      }
+    }
+
+    SafeHtmlBuilder html = new SafeHtmlBuilder();
+    for (Integer v : sort(m.keySet(), approved, rejected)) {
+      if (!html.isEmpty()) {
+        html.append("; ");
+      }
+
+      String val = LabelValue.formatValue(v.shortValue());
+      html.openSpan();
+      html.setAttribute("title", label.value_text(val));
+      if (v.intValue() == approved) {
+        html.setStyleName(style.label_ok());
+      } else if (v.intValue() == rejected) {
+        html.setStyleName(style.label_reject());
+      }
+      html.append(val).append(" ");
+      html.append(formatUserList(m.get(v)));
+      html.closeSpan();
+    }
+    return html.toBlockWidget();
+  }
+
+  private static List<Integer> sort(Set<Integer> keySet, int a, int b) {
+    List<Integer> r = new ArrayList<Integer>(keySet);
+    Collections.sort(r);
+    if (keySet.contains(a)) {
+      r.remove(Integer.valueOf(a));
+      r.add(0, a);
+    } else if (keySet.contains(b)) {
+      r.remove(Integer.valueOf(b));
+      r.add(0, b);
+    }
+    return r;
+  }
+
+  private static boolean isApproved(LabelInfo label, ApprovalInfo ai) {
+    return label.approved() != null
+        && label.approved()._account_id() == ai._account_id();
+  }
+
+  private static boolean isRejected(LabelInfo label, ApprovalInfo ai) {
+    return label.rejected() != null
+        && label.rejected()._account_id() == ai._account_id();
+  }
+
+  private String getStyleForLabel(LabelInfo label) {
+    switch (label.status()) {
+      case OK:
+        return style.label_ok();
+      case NEED:
+        return style.label_need();
+      case REJECT:
+      case IMPOSSIBLE:
+        return style.label_reject();
+      default:
+      case MAY:
+        return style.label_may();
+    }
+  }
+
+  SafeHtml formatUserList(Collection<? extends AccountInfo> in) {
+    List<AccountInfo> users = new ArrayList<AccountInfo>(in);
+    Collections.sort(users, new Comparator<AccountInfo>() {
+      @Override
+      public int compare(AccountInfo a, AccountInfo b) {
+        String as = name(a);
+        String bs = name(b);
+        if (as.isEmpty()) {
+          return 1;
+        } else if (bs.isEmpty()) {
+          return -1;
+        }
+        return as.compareTo(bs);
+      }
+
+      private String name(AccountInfo a) {
+        if (a.name() != null) {
+          return a.name();
+        } else if (a.email() != null) {
+          return a.email();
+        }
+        return "";
+      }
+    });
+
+    SafeHtmlBuilder html = new SafeHtmlBuilder();
+    Iterator<? extends AccountInfo> itr = users.iterator();
+    while (itr.hasNext()) {
+      AccountInfo ai = itr.next();
+      html.openSpan();
+      html.setStyleName(style.label_user());
+      if (ai.name() != null) {
+        html.append(ai.name());
+      } else if (ai.email() != null) {
+        html.append(ai.email());
+      } else {
+        html.append(ai._account_id());
+      }
+      html.closeSpan();
+      if (itr.hasNext()) {
+        html.append(", ");
+      }
+    }
+    return html;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
new file mode 100644
index 0000000..3052739
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
@@ -0,0 +1,101 @@
+// 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.client.change;
+
+import com.google.gerrit.client.AvatarImage;
+import com.google.gerrit.client.FormatUtil;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+class Message extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, Message> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  static interface Style extends CssResource {
+    String closed();
+  }
+
+  @UiField Style style;
+  @UiField Element name;
+  @UiField Element summary;
+  @UiField Element date;
+  @UiField Element message;
+
+  @UiField(provided = true)
+  AvatarImage avatar;
+
+  Message(CommentLinkProcessor clp, MessageInfo info) {
+    if (info.author() != null) {
+      avatar = new AvatarImage(info.author(), 26);
+      avatar.setSize("", "");
+    } else {
+      avatar = new AvatarImage();
+    }
+
+    initWidget(uiBinder.createAndBindUi(this));
+    addDomHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        setOpen(!isOpen());
+      }
+    }, ClickEvent.getType());
+
+    name.setInnerText(authorName(info));
+    date.setInnerText(FormatUtil.shortFormatDayTime(info.date()));
+    if (info.message() != null) {
+      String msg = info.message().trim();
+      summary.setInnerText(msg);
+      message.setInnerSafeHtml(clp.apply(
+          new SafeHtmlBuilder().append(msg).wikify()));
+    }
+  }
+
+  private boolean isOpen() {
+    return UIObject.isVisible(message);
+  }
+
+  private void setOpen(boolean open) {
+    UIObject.setVisible(summary, !open);
+    UIObject.setVisible(message, open);
+    if (open) {
+      removeStyleName(style.closed());
+    } else {
+      addStyleName(style.closed());
+    }
+  }
+
+  private static String authorName(MessageInfo info) {
+    if (info.author() != null) {
+      if (info.author().name() != null) {
+        return info.author().name();
+      }
+      return Gerrit.getConfig().getAnonymousCowardName();
+    }
+    return Util.C.messageNoAuthor();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml
new file mode 100644
index 0000000..e506340
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:c='urn:import:com.google.gerrit.client'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:style type='com.google.gerrit.client.change.Message.Style'>
+    .messageBox {
+      position: relative;
+      width: 1168px;
+      border-left: 1px solid #e3e9ff;
+      border-right: 1px solid #e3e9ff;
+      border-bottom: 1px solid #e3e9ff;
+      -webkit-border-bottom-left-radius: 8px;
+      -webkit-border-bottom-right-radius: 8px;
+    }
+
+    .avatar {
+      position: absolute;
+      width: 26px;
+      height: 26px;
+    }
+    .closed .avatar {
+      position: absolute;
+      width: 16px;
+      height: 16px;
+    }
+
+    .contents {
+      margin-left: 28px;
+      position: relative;
+    }
+
+    .contents p {
+      -webkit-margin-before: 0;
+      -webkit-margin-after: 0.3em;
+    }
+
+    .name {
+      white-space: nowrap;
+      font-weight: bold;
+    }
+    .closed .name {
+      width: 120px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      font-weight: normal;
+    }
+
+    .summary {
+      color: #777;
+      position: absolute;
+      top: 0;
+      left: 120px;
+      width: 915px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .date {
+      white-space: nowrap;
+      position: absolute;
+      top: 0;
+      right: 5px;
+    }
+  </ui:style>
+
+  <g:HTMLPanel
+      styleName='{style.messageBox}'
+      addStyleNames='{style.closed}'>
+    <c:AvatarImage ui:field='avatar' styleName='{style.avatar}'/>
+    <div class='{style.contents}'>
+      <div class='{style.name}' ui:field='name'/>
+      <div ui:field='summary' class='{style.summary}'/>
+      <div class='{style.date}' ui:field='date'/>
+      <div ui:field='message'
+           aria-hidden='true'
+           style='display: NONE'/>
+    </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java
new file mode 100644
index 0000000..7836de5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java
@@ -0,0 +1,119 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.changes.ReviewInput;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+/** Applies a label with one mouse click. */
+class QuickApprove extends Button implements ClickHandler {
+  private Change.Id changeId;
+  private String revision;
+  private ReviewInput input;
+
+  QuickApprove() {
+    addClickHandler(this);
+  }
+
+  void set(ChangeInfo info, String commit) {
+    if (!info.has_permitted_labels() || !info.status().isOpen()) {
+      // Quick approve needs at least one label on an open change.
+      setVisible(false);
+      return;
+    }
+
+    String qName = null;
+    String qValueStr = null;
+    short qValue = 0;
+
+    for (LabelInfo label : Natives.asList(info.all_labels().values())) {
+      if (!info.permitted_labels().containsKey(label.name())) {
+        continue;
+      }
+
+      JsArrayString values = info.permitted_values(label.name());
+      if (values.length() == 0) {
+        continue;
+      }
+
+      switch (label.status()) {
+        case NEED: // Label is required for submit.
+          break;
+
+        case OK: // Label already applied.
+        case MAY: // Label is not required.
+          continue;
+
+        case REJECT: // Submit cannot happen, do not quick approve.
+        case IMPOSSIBLE:
+          setVisible(false);
+          return;
+      }
+
+      String s = values.get(values.length() - 1);
+      short v = LabelInfo.parseValue(s);
+      if (v > 0 && s.equals(label.max_value())) {
+        if (qName != null) {
+          // Quick approve is available for one label only.
+          setVisible(false);
+          return;
+        }
+
+        qName = label.name();
+        qValueStr = s;
+        qValue = v;
+      }
+    }
+
+    if (qName != null)  {
+      changeId = info.legacy_id();
+      revision = commit;
+      input = ReviewInput.create();
+      input.label(qName, qValue);
+      setText(qName + qValueStr);
+    } else {
+      setVisible(false);
+    }
+  }
+
+  @Override
+  public void setText(String text) {
+    setHTML(new SafeHtmlBuilder().openDiv().append(text).closeDiv());
+  }
+
+  @Override
+  public void onClick(ClickEvent event) {
+    ChangeApi.revision(changeId.get(), revision)
+      .view("review")
+      .post(input, new GerritCallback<ReviewInput>() {
+        @Override
+        public void onSuccess(ReviewInput result) {
+          Gerrit.display(PageLinks.toChange2(changeId));
+        }
+      });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java
new file mode 100644
index 0000000..0dd6072
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java
@@ -0,0 +1,33 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+
+class RebaseAction {
+  static void call(final Change.Id id, String revision) {
+    ChangeApi.rebase(id.get(), revision,
+      new GerritCallback<ChangeInfo>() {
+        public void onSuccess(ChangeInfo result) {
+          Gerrit.display(PageLinks.toChange2(id));
+        }
+      });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reload.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reload.java
new file mode 100644
index 0000000..5b633b3
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reload.java
@@ -0,0 +1,56 @@
+package com.google.gerrit.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.user.client.ui.Image;
+
+class Reload extends Image implements ClickHandler,
+    MouseOverHandler, MouseOutHandler {
+  private Change.Id changeId;
+  private boolean in;
+
+  Reload() {
+    setResource(Resources.I.reload_black());
+    addClickHandler(this);
+    addMouseOverHandler(this);
+    addMouseOutHandler(this);
+  }
+
+  void set(ChangeInfo info) {
+    changeId = info.legacy_id();
+  }
+
+  void reload() {
+    Gerrit.display(PageLinks.toChange2(changeId));
+  }
+
+  @Override
+  public void onMouseOver(MouseOverEvent event) {
+    if (!in) {
+      in = true;
+      setResource(Resources.I.reload_white());
+    }
+  }
+
+  @Override
+  public void onMouseOut(MouseOutEvent event) {
+    if (in) {
+      in = false;
+      setResource(Resources.I.reload_black());
+    }
+  }
+
+  @Override
+  public void onClick(ClickEvent e) {
+    e.preventDefault();
+    e.stopPropagation();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
new file mode 100644
index 0000000..97146e1
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
@@ -0,0 +1,90 @@
+// 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.client.change;
+
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.user.client.PluginSafePopupPanel;
+
+class ReplyAction {
+  private final Change.Id changeId;
+  private final String revision;
+  private final ChangeScreen2.Style style;
+  private final Widget replyButton;
+
+  private NativeMap<LabelInfo> allLabels;
+  private NativeMap<JsArrayString> permittedLabels;
+
+  private ReplyBox replyBox;
+  private PopupPanel popup;
+
+  ReplyAction(
+      ChangeInfo info,
+      String revision,
+      ChangeScreen2.Style style,
+      Widget replyButton) {
+    this.changeId = info.legacy_id();
+    this.revision = revision;
+    this.style = style;
+    this.replyButton = replyButton;
+
+    allLabels = info.all_labels();
+    permittedLabels = info.has_permitted_labels()
+        ? info.permitted_labels()
+        : NativeMap.<JsArrayString> create();
+  }
+
+  void onReply() {
+    if (popup != null) {
+      popup.hide();
+      return;
+    }
+
+    if (replyBox == null) {
+      replyBox = new ReplyBox(
+          changeId,
+          revision,
+          allLabels,
+          permittedLabels);
+      allLabels = null;
+      permittedLabels = null;
+    }
+
+    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    p.setStyleName(style.replyBox());
+    p.addAutoHidePartner(replyButton.getElement());
+    p.addCloseHandler(new CloseHandler<PopupPanel>() {
+      @Override
+      public void onClose(CloseEvent<PopupPanel> event) {
+        if (popup == p) {
+          popup = null;
+        }
+      }
+    });
+    p.add(replyBox);
+    p.showRelativeTo(replyButton);
+    GlobalKey.dialog(p);
+    popup = p;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
new file mode 100644
index 0000000..8b10641
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -0,0 +1,277 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.changes.ReviewInput;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.NpTextArea;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+class ReplyBox extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, ReplyBox> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  interface Styles extends CssResource {
+    String label_name();
+    String label_value();
+  }
+
+  private final Change.Id changeId;
+  private final String revision;
+  private ReviewInput in = ReviewInput.create();
+  private List<Runnable> lgtm;
+
+  @UiField Styles style;
+  @UiField NpTextArea message;
+  @UiField Element labelsParent;
+  @UiField Grid labelsTable;
+  @UiField Button send;
+  @UiField CheckBox email;
+
+  ReplyBox(
+      Change.Id changeId,
+      String revision,
+      NativeMap<LabelInfo> all,
+      NativeMap<JsArrayString> permitted) {
+    this.changeId = changeId;
+    this.revision = revision;
+    initWidget(uiBinder.createAndBindUi(this));
+
+    List<String> names = new ArrayList<String>(permitted.keySet());
+    if (names.isEmpty()) {
+      UIObject.setVisible(labelsParent, false);
+    } else {
+      Collections.sort(names);
+      lgtm = new ArrayList<Runnable>(names.size());
+      renderLabels(names, all, permitted);
+    }
+  }
+
+  @Override
+  protected void onLoad() {
+    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+      @Override
+      public void execute() {
+        message.setFocus(true);
+      }});
+  }
+
+  @UiHandler("message")
+  void onMessageKey(KeyPressEvent event) {
+    if ((event.getCharCode() == '\n' || event.getCharCode() == KeyCodes.KEY_ENTER)
+        && event.isControlKeyDown()) {
+      event.preventDefault();
+      event.stopPropagation();
+      onSend(null);
+    } else if (lgtm != null
+        && event.getCharCode() == 'M'
+        && message.getValue().equals("LGT")) {
+      Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+        @Override
+        public void execute() {
+          if (message.getValue().startsWith("LGTM")) {
+            for (Runnable r : lgtm) {
+              r.run();
+            }
+          }
+        }
+      });
+    }
+  }
+
+  @UiHandler("email")
+  void onEmail(ValueChangeEvent<Boolean> e) {
+    if (e.getValue()) {
+      in.notify(ReviewInput.NotifyHandling.ALL);
+    } else {
+      in.notify(ReviewInput.NotifyHandling.NONE);
+    }
+  }
+
+  @UiHandler("send")
+  void onSend(ClickEvent e) {
+    in.message(message.getText().trim());
+    ChangeApi.revision(changeId.get(), revision)
+      .view("review")
+      .post(in, new GerritCallback<ReviewInput>() {
+        @Override
+        public void onSuccess(ReviewInput result) {
+          Gerrit.display(PageLinks.toChange2(changeId));
+        }
+      });
+    hide();
+  }
+
+  private void hide() {
+    for (Widget w = getParent(); w != null; w = w.getParent()) {
+      if (w instanceof PopupPanel) {
+        ((PopupPanel) w).hide();
+        break;
+      }
+    }
+  }
+
+  private void renderLabels(
+      List<String> names,
+      NativeMap<LabelInfo> all,
+      NativeMap<JsArrayString> permitted) {
+    TreeSet<Short> values = new TreeSet<Short>();
+    for (String id : names) {
+      JsArrayString p = permitted.get(id);
+      if (p != null) {
+        for (int i = 0; i < p.length(); i++) {
+          values.add(LabelInfo.parseValue(p.get(i)));
+        }
+      }
+    }
+    List<Short> columns = new ArrayList<Short>(values);
+
+    labelsTable.resize(1 + permitted.size(), 1 + values.size());
+    for (int c = 0; c < columns.size(); c++) {
+      labelsTable.setText(0, 1 + c, LabelValue.formatValue(columns.get(c)));
+      labelsTable.getCellFormatter().setStyleName(0, 1 + c, style.label_value());
+    }
+
+    List<String> checkboxes = new ArrayList<String>(permitted.size());
+    int row = 1;
+    for (String id : names) {
+      Set<Short> vals = all.get(id).value_set();
+      if (isCheckBox(vals)) {
+        checkboxes.add(id);
+      } else {
+        renderRadio(row++, id, columns, vals, all.get(id));
+      }
+    }
+    for (String id : checkboxes) {
+      renderCheckBox(row++, id, all.get(id));
+    }
+  }
+
+  private void renderRadio(int row, final String id,
+      List<Short> columns,
+      Set<Short> values,
+      LabelInfo info) {
+    labelsTable.setText(row, 0, id);
+    labelsTable.getCellFormatter().setStyleName(row, 0, style.label_name());
+
+    ApprovalInfo self = Gerrit.isSignedIn()
+        ? info.for_user(Gerrit.getUserAccount().getId().get())
+        : null;
+
+    final List<RadioButton> group = new ArrayList<RadioButton>(values.size());
+    for (int i = 0; i < columns.size(); i++) {
+      final Short v = columns.get(i);
+      if (values.contains(v)) {
+        RadioButton b = new RadioButton(id);
+        b.setTitle(info.value_text(LabelValue.formatValue(v)));
+        if ((self != null && v == self.value()) || (self == null && v == 0)) {
+          b.setValue(true);
+        }
+        b.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+          @Override
+          public void onValueChange(ValueChangeEvent<Boolean> event) {
+            if (event.getValue()) {
+              in.label(id, v);
+            }
+          }
+        });
+        group.add(b);
+        labelsTable.setWidget(row, 1 + i, b);
+      }
+    }
+
+    if (!group.isEmpty()) {
+      lgtm.add(new Runnable() {
+        @Override
+        public void run() {
+          for (int i = 0; i < group.size() - 1; i++) {
+            group.get(i).setValue(false, false);
+          }
+          group.get(group.size() - 1).setValue(true, true);
+        }
+      });
+    }
+  }
+
+  private void renderCheckBox(int row, final String id, LabelInfo info) {
+    ApprovalInfo self = Gerrit.isSignedIn()
+        ? info.for_user(Gerrit.getUserAccount().getId().get())
+        : null;
+
+    final CheckBox b = new CheckBox();
+    b.setText(id);
+    b.setTitle(info.value_text("+1"));
+    if (self != null && self.value() == 1) {
+      b.setValue(true);
+    }
+    b.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+      @Override
+      public void onValueChange(ValueChangeEvent<Boolean> event) {
+        in.label(id, event.getValue() ? (short) 1 : (short) 0);
+      }
+    });
+    b.setStyleName(style.label_name());
+    labelsTable.setWidget(row, 0, b);
+
+    lgtm.add(new Runnable() {
+      @Override
+      public void run() {
+        b.setValue(true, true);
+      }
+    });
+  }
+
+  private static boolean isCheckBox(Set<Short> values) {
+    return values.size() == 2
+        && values.contains((short) 0)
+        && values.contains((short) 1);
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
new file mode 100644
index 0000000..d17f48d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'>
+  <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+  <ui:style type='com.google.gerrit.client.change.ReplyBox.Styles'>
+    @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+
+    .replyBox {
+      max-height: 260px;
+    }
+
+    .section {
+      padding: 5px 5px;
+      border-bottom: 1px solid #b8b8b8;
+    }
+
+    .label_name {
+      font-weight: bold;
+      text-align: left;
+    }
+    .label_name input { margin-left: 0; }
+
+    .label_value {
+      text-align: center;
+    }
+    .email {
+      display: inline-block;
+      margin-left: 2em;
+    }
+  </ui:style>
+  <g:HTMLPanel styleName='{style.replyBox}'>
+    <div class='{style.section}'>
+      <c:NpTextArea
+         visibleLines='5'
+         characterWidth='70'
+         ui:field='message'/>
+    </div>
+    <div class='{style.section}' ui:field='labelsParent'>
+      <g:Grid ui:field='labelsTable'/>
+    </div>
+    <div class='{style.section}'>
+      <g:Button ui:field='send'
+          title='Send reply (Shortcut: Ctrl-Enter)'
+          styleName='{res.style.button}'>
+        <ui:attribute name='title'/>
+        <div><ui:msg>Send</ui:msg></div>
+      </g:Button>
+
+      <div class='{style.email}'>
+        <ui:msg>and <g:CheckBox ui:field='email' value='true'>send email</g:CheckBox></ui:msg>
+      </div>
+    </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java
new file mode 100644
index 0000000..9a6fd22
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java
@@ -0,0 +1,34 @@
+// 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.client.change;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+interface Resources extends ClientBundle {
+  static final Resources I = GWT.create(Resources.class);
+
+  @Source("star_open.png") ImageResource star_open();
+  @Source("star_filled.png") ImageResource star_filled();
+  @Source("reload_black.png") ImageResource reload_black();
+  @Source("reload_white.png") ImageResource reload_white();
+  @Source("common.css") Style style();
+
+  interface Style extends CssResource {
+    String button();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestoreAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestoreAction.java
new file mode 100644
index 0000000..82f9b0c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestoreAction.java
@@ -0,0 +1,42 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.user.client.ui.Button;
+
+class RestoreAction extends ActionMessageBox {
+  private final Change.Id id;
+
+  RestoreAction(Button b, Change.Id id) {
+    super(b);
+    this.id = id;
+  }
+
+  void send(String message) {
+    ChangeApi.restore(id.get(), message, new GerritCallback<ChangeInfo>() {
+      @Override
+      public void onSuccess(ChangeInfo result) {
+        Gerrit.display(PageLinks.toChange2(id));
+        hide();
+      }
+    });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevertAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevertAction.java
new file mode 100644
index 0000000..3190d79
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevertAction.java
@@ -0,0 +1,61 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.ActionDialog;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.user.client.ui.Button;
+
+class RevertAction {
+  static void call(Button b, final Change.Id id, final String revision,
+      String project, final String commitSubject) {
+    // TODO Replace ActionDialog with a nicer looking display.
+    b.setEnabled(false);
+    new ActionDialog(b, false,
+        Util.C.revertChangeTitle(),
+        Util.C.headingRevertMessage()) {
+      {
+        sendButton.setText(Util.C.buttonRevertChangeSend());
+        message.setText(Util.M.revertChangeDefaultMessage(
+            commitSubject, revision));
+      }
+
+      @Override
+      public void onSend() {
+        ChangeApi.revert(id.get(),
+            getMessageText(), new GerritCallback<ChangeInfo>() {
+              @Override
+              public void onSuccess(ChangeInfo result) {
+                sent = true;
+                hide();
+                Gerrit.display(PageLinks.toChange2(result.legacy_id()));
+              }
+
+              @Override
+              public void onFailure(Throwable caught) {
+                enableButtons(true);
+                super.onFailure(caught);
+              }
+            });
+      }
+    }.center();
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/StarIcon.java
similarity index 67%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/change/StarIcon.java
index b2d62b3..fccca27 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/PutInput.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/StarIcon.java
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.client.change;
 
-import java.io.IOException;
-import java.io.InputStream;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.ToggleButton;
 
-
-/** Raw data stream supplied by the body of a PUT. */
-public interface PutInput {
-  String getContentType();
-  long getContentLength();
-  InputStream getInputStream() throws IOException;
+class StarIcon extends ToggleButton {
+  StarIcon() {
+    super(
+      new Image(Resources.I.star_open()),
+      new Image(Resources.I.star_filled()));
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java
new file mode 100644
index 0000000..3008830
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java
@@ -0,0 +1,47 @@
+// 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.SubmitFailureDialog;
+import com.google.gerrit.client.changes.SubmitInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.Change;
+
+class SubmitAction {
+  static void call(final Change.Id id, String revision) {
+    ChangeApi.submit(id.get(), revision,
+      new GerritCallback<SubmitInfo>() {
+        public void onSuccess(SubmitInfo result) {
+          redisplay();
+        }
+
+        public void onFailure(Throwable err) {
+          if (SubmitFailureDialog.isConflict(err)) {
+            new SubmitFailureDialog(err.getMessage()).center();
+            redisplay();
+          } else {
+            super.onFailure(err);
+          }
+        }
+
+        private void redisplay() {
+          Gerrit.display(PageLinks.toChange2(id));
+        }
+      });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java
new file mode 100644
index 0000000..e74dfec
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java
@@ -0,0 +1,140 @@
+// 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.client.change;
+
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.globalkey.client.NpTextArea;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+
+/** Displays (and edits) the change topic string. */
+class Topic extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, Topic> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  private int changeId;
+  private boolean canEdit;
+
+  @UiField FlowPanel show;
+  @UiField InlineLabel text;
+  @UiField Image editIcon;
+
+  @UiField Element form;
+  @UiField NpTextBox input;
+  @UiField NpTextArea message;
+  @UiField Button save;
+  @UiField Button cancel;
+
+  Topic() {
+    initWidget(uiBinder.createAndBindUi(this));
+    show.addDomHandler(
+      new ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          onEdit();
+        }
+      },
+      ClickEvent.getType());
+  }
+
+  void set(ChangeInfo info) {
+    canEdit = info.has_actions()
+        && info.actions().containsKey("topic")
+        && info.actions().get("topic").enabled();
+
+    changeId = info.legacy_id().get();
+    text.setText(info.topic());
+    editIcon.setVisible(canEdit);
+    if (!canEdit) {
+      show.setTitle(null);
+    }
+  }
+
+  boolean canEdit() {
+    return canEdit;
+  }
+
+  void onEdit() {
+    if (canEdit) {
+      show.setVisible(false);
+      UIObject.setVisible(form, true);
+
+      input.setText(text.getText());
+      input.setFocus(true);
+    }
+  }
+
+  @UiHandler("cancel")
+  void onCancel(ClickEvent e) {
+    input.setFocus(false);
+    show.setVisible(true);
+    UIObject.setVisible(form, false);
+  }
+
+  @UiHandler("input")
+  void onKeyDownInput(KeyDownEvent e) {
+    if (e.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+      onCancel(null);
+    } else if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
+      onSave(null);
+    }
+  }
+
+  @UiHandler("message")
+  void onKeyDownMessage(KeyDownEvent e) {
+    if (e.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+      onCancel(null);
+    } else if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER
+        && e.isControlKeyDown()) {
+      onSave(null);
+    }
+  }
+
+  @UiHandler("save")
+  void onSave(ClickEvent e) {
+    ChangeApi.topic(
+        changeId,
+        input.getValue().trim(),
+        message.getValue().trim(),
+        new GerritCallback<String>() {
+          @Override
+          public void onSuccess(String result) {
+            // Cheat and just patch the UI with the current topic.
+            // This saves a full redraw of the change screen but
+            // will cause the message to be missed in the History.
+            text.setText(result);
+            onCancel(null);
+          }
+        });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.ui.xml
new file mode 100644
index 0000000..2f4751c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.ui.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
+  <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+  <ui:style>
+    .show { cursor: pointer; }
+    .edit, .cancel { float: right; }
+  </ui:style>
+  <g:HTMLPanel>
+    <g:FlowPanel ui:field='show'
+        styleName='{style.show}'
+        title='Click to edit topic (Shortcut: t)'>
+      <ui:attribute name='title'/>
+      <g:InlineLabel ui:field='text'/>
+      <g:Image ui:field='editIcon'
+          resource='{ico.edit}'
+          styleName='{style.edit}'/>
+    </g:FlowPanel>
+
+    <div ui:field='form' style='display: none' aria-hidden='true'>
+      <div>
+        <c:NpTextBox ui:field='input' visibleLength='55'/>
+      </div>
+      <div>
+        <c:NpTextArea ui:field='message'
+            visibleLines='3'
+            characterWidth='45'/>
+      </div>
+      <div>
+        <g:Button ui:field='save' styleName='{res.style.button}'>
+          <div>Update</div>
+        </g:Button>
+        <g:Button ui:field='cancel'
+            styleName='{res.style.button}'
+            addStyleNames='{style.cancel}'>
+          <div>Cancel</div>
+        </g:Button>
+      </div>
+    </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css
new file mode 100644
index 0000000..842a106
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css
@@ -0,0 +1,37 @@
+/* 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.
+ */
+
+.button {
+  margin: 0 3px 0 0;
+  border-color: rgba(0, 0, 0, 0.1);
+  text-align: center;
+  font-size: 11px;
+  font-weight: bold;
+  border: 1px solid;
+  cursor: pointer;
+  color: #fff;
+  background-color: #4d90fe;
+  background-image: -webkit-linear-gradient(top, #4d90fe, #4d90fe);
+  -webkit-border-radius: 2px;
+  -webkit-box-sizing: content-box;
+}
+
+.button div {
+  width: 54px;
+  white-space: nowrap;
+  color: #fff;
+  height: 10px;
+  line-height: 10px;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
new file mode 100644
index 0000000..1abacb9
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
@@ -0,0 +1,47 @@
+/* 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.
+ */
+
+.pointer {
+  width: 1px;
+  padding: 0px;
+  vertical-align: top;
+}
+
+.pathColumn {
+  white-space: nowrap;
+}
+
+.deltaColumn1 {
+  white-space: nowrap;
+  text-align: right;
+}
+
+.deltaColumn2 {
+  padding-left: 5px;
+  white-space: nowrap;
+  text-align: right;
+}
+
+.inserted {
+  height: 10px;
+  display: inline-block;
+  background-color: #4d4;
+}
+
+.deleted {
+  height: 10px;
+  display: inline-block;
+  background-color: #d44;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_black.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_black.png
new file mode 100644
index 0000000..13f3a5d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_black.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_white.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_white.png
new file mode 100644
index 0000000..8f5a311
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_white.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png
new file mode 100644
index 0000000..39bddb1
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_open.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_open.png
new file mode 100644
index 0000000..6c955de
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_open.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
index 08877d9..93c9505 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
@@ -52,6 +52,7 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -105,6 +106,13 @@
     setStyleName(Gerrit.RESOURCES.css().approvalTable());
   }
 
+  /**
+   * Sets the header row
+   *
+   * @param labels The list of labels to display in the header. This list does
+   *    not get resorted, so be sure that the list's elements are in the same
+   *    order as the list of labels passed to the {@code displayRow} method.
+   */
   private void displayHeader(Collection<String> labels) {
     table.resizeColumns(2 + labels.size());
 
@@ -144,12 +152,14 @@
     if (byUser.isEmpty()) {
       table.setVisible(false);
     } else {
-      displayHeader(change.labels());
+      List<String> labels = new ArrayList<String>(change.labels());
+      Collections.sort(labels);
+      displayHeader(labels);
       table.resizeRows(1 + byUser.size());
       int i = 1;
       for (ApprovalDetail ad : ApprovalDetail.sort(
           byUser.values(), change.owner()._account_id())) {
-        displayRow(i++, ad, change, accounts.get(ad.getAccount().get()));
+        displayRow(i++, ad, labels, accounts.get(ad.getAccount().get()));
       }
       table.setVisible(true);
     }
@@ -324,8 +334,18 @@
         });
   }
 
-  private void displayRow(int row, final ApprovalDetail ad, ChangeInfo change,
-      AccountInfo account) {
+  /**
+   * Sets the reviewer data for a row.
+   *
+   * @param row The number of the row on which to set the reviewer.
+   * @param ad The details for this reviewer's approval.
+   * @param labels The list of labels to show. This list does not get resorted,
+   *    so be sure that the list's elements are in the same order as the list
+   *    of labels passed to the {@code displayHeader} method.
+   * @param account The account information for the approval.
+   */
+  private void displayRow(int row, final ApprovalDetail ad,
+      List<String> labels, AccountInfo account) {
     final CellFormatter fmt = table.getCellFormatter();
     int col = 0;
 
@@ -351,7 +371,7 @@
     }
     fmt.setStyleName(row, col++, Gerrit.RESOURCES.css().removeReviewerCell());
 
-    for (String labelName : change.labels()) {
+    for (String labelName : labels) {
       fmt.setStyleName(row, col, Gerrit.RESOURCES.css().approvalscore());
       if (!ad.canVote(labelName)) {
         fmt.addStyleName(row, col, Gerrit.RESOURCES.css().notVotable());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index abd94c9..f1446d7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -16,10 +16,13 @@
 
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.common.changes.ListChangesOption;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
+import java.util.EnumSet;
+
 /**
  * A collection of static methods which work on the Gerrit REST API for specific
  * changes.
@@ -62,7 +65,24 @@
   }
 
   public static void detail(int id, AsyncCallback<ChangeInfo> cb) {
-    call(id, "detail").get(cb);
+    detail(id).get(cb);
+  }
+
+  public static void detail(int id, EnumSet<ListChangesOption> options,
+      AsyncCallback<ChangeInfo> cb) {
+    RestApi call = detail(id);
+    if (!options.isEmpty()) {
+      ChangeList.addOptions(call, options);
+    }
+    call.get(cb);
+  }
+
+  private static RestApi detail(int id) {
+    return call(id, "detail");
+  }
+
+  public static RestApi revision(int id, String revision) {
+    return change(id).view("revisions").id(revision);
   }
 
   public static RestApi revision(PatchSet.Id id) {
@@ -82,12 +102,26 @@
   }
 
   /** Submit a specific revision of a change. */
+  public static void cherrypick(int id, String commit, String destination, String message, AsyncCallback<ChangeInfo> cb) {
+    CherryPickInput cherryPickInput = CherryPickInput.create();
+    cherryPickInput.setMessage(message);
+    cherryPickInput.setDestination(destination);
+    call(id, commit, "cherrypick").post(cherryPickInput, cb);
+  }
+
+  /** Submit a specific revision of a change. */
   public static void submit(int id, String commit, AsyncCallback<SubmitInfo> cb) {
     SubmitInput in = SubmitInput.create();
     in.wait_for_merge(true);
     call(id, commit, "submit").post(in, cb);
   }
 
+  /** Rebase a revision onto the branch tip. */
+  public static void rebase(int id, String commit, AsyncCallback<ChangeInfo> cb) {
+    JavaScriptObject in = JavaScriptObject.createObject();
+    call(id, commit, "rebase").post(in, cb);
+  }
+
   private static class Input extends JavaScriptObject {
     final native void topic(String t) /*-{ if(t)this.topic=t; }-*/;
     final native void message(String m) /*-{ if(m)this.message=m; }-*/;
@@ -100,6 +134,17 @@
     }
   }
 
+  private static class CherryPickInput extends JavaScriptObject {
+    static CherryPickInput create() {
+      return (CherryPickInput) createObject();
+    }
+    final native void setDestination(String d) /*-{ this.destination = d; }-*/;
+    final native void setMessage(String m) /*-{ this.message = m; }-*/;
+
+    protected CherryPickInput() {
+    }
+  };
+
   private static class SubmitInput extends JavaScriptObject {
     final native void wait_for_merge(boolean b) /*-{ this.wait_for_merge=b; }-*/;
 
@@ -119,7 +164,7 @@
     return change(id).view("revisions").id(commit).view(action);
   }
 
-  private static RestApi change(int id) {
+  public static RestApi change(int id) {
     // TODO Switch to triplet project~branch~id format in URI.
     return new RestApi("/changes/").id(String.valueOf(id));
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 968c726..d1fcf60 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -22,6 +22,8 @@
   String statusLongMerged();
   String statusLongAbandoned();
   String statusLongDraft();
+  String readyToSubmit();
+  String mergeConflict();
 
   String myDashboardTitle();
   String unknownDashboardTitle();
@@ -37,6 +39,7 @@
   String allMergedChanges();
 
   String changeTableColumnSubject();
+  String changeTableColumnStatus();
   String changeTableColumnOwner();
   String changeTableColumnReviewers();
   String changeTableColumnProject();
@@ -52,7 +55,9 @@
   String expandCollapseDependencies();
   String previousPatchSet();
   String nextPatchSet();
+  String keyReload();
   String keyPublishComments();
+  String keyEditTopic();
 
   String patchTableColumnName();
   String patchTableColumnComments();
@@ -62,6 +67,7 @@
   String patchTableDiffUnified();
   String patchTableDownloadPreImage();
   String patchTableDownloadPostImage();
+  String patchTableBinary();
   String commitMessage();
   String fileCommentHeader();
 
@@ -137,6 +143,12 @@
   String editCommitMessageToolTip();
   String titleEditCommitMessage();
 
+  String buttonCherryPickChangeBegin();
+  String buttonCherryPickChangeSend();
+  String headingCherryPickBranch();
+  String cherryPickCommitMessage();
+  String cherryPickTitle();
+
   String buttonAbandonChangeBegin();
   String buttonAbandonChangeSend();
   String headingAbandonMessage();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 4c12378..eed019b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -3,6 +3,8 @@
 statusLongMerged = Merged
 statusLongAbandoned = Abandoned
 statusLongDraft = Draft
+readyToSubmit = Ready to Submit
+mergeConflict = Merge Conflict
 
 starredHeading = Starred Changes
 watchedHeading = Open Changes of Watched Projects
@@ -17,6 +19,7 @@
 allMergedChanges = All merged changes
 
 changeTableColumnSubject = Subject
+changeTableColumnStatus = Status
 changeTableColumnOwner = Owner
 changeTableColumnReviewers = Reviewers
 changeTableColumnProject = Project
@@ -32,7 +35,9 @@
 expandCollapseDependencies = Expands / Collapses dependencies section
 previousPatchSet = Previous patch set
 nextPatchSet = Next patch set
+keyReload = Reload change
 keyPublishComments = Review and publish comments
+keyEditTopic = Edit change topic
 
 patchTableColumnName = File Path
 patchTableColumnComments = Comments
@@ -42,6 +47,7 @@
 patchTableDiffUnified = Unified
 patchTableDownloadPreImage = old
 patchTableDownloadPostImage = new
+patchTableBinary = Binary
 commitMessage = Commit Message
 fileCommentHeader = File Comment:
 
@@ -122,6 +128,12 @@
 editCommitMessageToolTip = Edit Commit Message
 titleEditCommitMessage = Create New Patch Set
 
+buttonCherryPickChangeBegin = Cherry Pick To
+buttonCherryPickChangeSend = Cherry Pick Change
+headingCherryPickBranch = Cherry Pick to Branch:
+cherryPickCommitMessage = Cherry Pick Commit Message:
+cherryPickTitle = Code Review - Cherry Pick Change to Another Branch
+
 buttonRestoreChangeBegin = Restore Change
 restoreChangeTitle = Code Review - Restore Change
 headingRestoreMessage = Restore Message:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 00693d9..cf73d79 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -15,9 +15,11 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.diff.FileInfo;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.common.data.LabelValue;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
@@ -28,11 +30,13 @@
 
 import java.sql.Timestamp;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
 public class ChangeInfo extends JavaScriptObject {
   public final void init() {
-    if (labels0() != null) {
-      labels0().copyKeysIntoChildren("_name");
+    if (all_labels() != null) {
+      all_labels().copyKeysIntoChildren("_name");
     }
   }
 
@@ -69,7 +73,7 @@
   }
 
   public final Set<String> labels() {
-    return labels0().keySet();
+    return all_labels().keySet();
   }
 
   public final native String id() /*-{ return this.id; }-*/;
@@ -86,22 +90,26 @@
   public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
   public final native boolean reviewed() /*-{ return this.reviewed ? true : false; }-*/;
   public final native String _sortkey() /*-{ return this._sortkey; }-*/;
-  private final native NativeMap<LabelInfo> labels0() /*-{ return this.labels; }-*/;
+  public final native NativeMap<LabelInfo> all_labels() /*-{ return this.labels; }-*/;
   public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
+  public final native String current_revision() /*-{ return this.current_revision; }-*/;
+  public final native NativeMap<RevisionInfo> revisions() /*-{ return this.revisions; }-*/;
+  public final native RevisionInfo revision(String n) /*-{ return this.revisions[n]; }-*/;
+  public final native JsArray<MessageInfo> messages() /*-{ return this.messages; }-*/;
 
   public final native boolean has_permitted_labels()
   /*-{ return this.hasOwnProperty('permitted_labels') }-*/;
-  private final native NativeMap<JavaScriptObject> _permitted_labels()
+  public final native NativeMap<JsArrayString> permitted_labels()
   /*-{ return this.permitted_labels; }-*/;
-  public final Set<String> permitted_labels() {
-    return Natives.keys(_permitted_labels());
-  }
   public final native JsArrayString permitted_values(String n)
   /*-{ return this.permitted_labels[n]; }-*/;
 
   public final native JsArray<AccountInfo> removable_reviewers()
   /*-{ return this.removable_reviewers; }-*/;
 
+  public final native boolean has_actions() /*-{ return this.hasOwnProperty('actions') }-*/;
+  public final native NativeMap<ActionInfo> actions() /*-{ return this.actions; }-*/;
+
   final native int _number() /*-{ return this._number; }-*/;
   final native boolean _more_changes()
   /*-{ return this._more_changes ? true : false; }-*/;
@@ -130,9 +138,17 @@
     public final native AccountInfo disliked() /*-{ return this.disliked; }-*/;
 
     public final native JsArray<ApprovalInfo> all() /*-{ return this.all; }-*/;
+    public final ApprovalInfo for_user(int user) {
+      JsArray<ApprovalInfo> all = all();
+      for (int i = 0; all != null && i < all.length(); i++) {
+        if (all.get(i)._account_id() == user) {
+          return all.get(i);
+        }
+      }
+      return null;
+    }
 
     private final native NativeMap<NativeString> _values() /*-{ return this.values; }-*/;
-
     public final Set<String> values() {
       return Natives.keys(_values());
     }
@@ -147,15 +163,101 @@
       return 0;
     }-*/;
 
+    public final String max_value() {
+      return LabelValue.formatValue(value_set().last());
+    }
+
+    public final SortedSet<Short> value_set() {
+      SortedSet<Short> values = new TreeSet<Short>();
+      for (String v : values()) {
+        values.add(parseValue(v));
+      }
+      return values;
+    }
+
+    public static final short parseValue(String formatted) {
+      if (formatted.startsWith("+")) {
+        formatted = formatted.substring(1);
+      } else if (formatted.startsWith(" ")) {
+        formatted = formatted.trim();
+      }
+      return Short.parseShort(formatted);
+    }
+
     protected LabelInfo() {
     }
   }
 
   public static class ApprovalInfo extends AccountInfo {
     public final native boolean has_value() /*-{ return this.hasOwnProperty('value'); }-*/;
-    public final native short value() /*-{ return this.value; }-*/;
+    public final native short value() /*-{ return this.value || 0; }-*/;
 
     protected ApprovalInfo() {
     }
   }
+
+  public static class RevisionInfo extends JavaScriptObject {
+    public final native int _number() /*-{ return this._number; }-*/;
+    public final native String name() /*-{ return this.name; }-*/;
+    public final native boolean draft() /*-{ return this.draft || false; }-*/;
+    public final native CommitInfo commit() /*-{ return this.commit; }-*/;
+    public final native void set_commit(CommitInfo c) /*-{ this.commit = c; }-*/;
+
+    public final native boolean has_files() /*-{ return this.hasOwnProperty('files') }-*/;
+    public final native NativeMap<FileInfo> files() /*-{ return this.files; }-*/;
+
+    public final native boolean has_actions() /*-{ return this.hasOwnProperty('actions') }-*/;
+    public final native NativeMap<ActionInfo> actions() /*-{ return this.actions; }-*/;
+
+    protected RevisionInfo () {
+    }
+  }
+
+  public static class CommitInfo extends JavaScriptObject {
+    public final native String commit() /*-{ return this.commit; }-*/;
+    public final native GitPerson author() /*-{ return this.author; }-*/;
+    public final native GitPerson committer() /*-{ return this.committer; }-*/;
+    public final native String subject() /*-{ return this.subject; }-*/;
+    public final native String message() /*-{ return this.message; }-*/;
+
+    protected CommitInfo() {
+    }
+  }
+
+  public static class GitPerson extends JavaScriptObject {
+    public final native String name() /*-{ return this.name; }-*/;
+    public final native String email() /*-{ return this.email; }-*/;
+    private final native String dateRaw() /*-{ return this.date; }-*/;
+
+    public final Timestamp date() {
+      return JavaSqlTimestamp_JsonSerializer.parseTimestamp(dateRaw());
+    }
+
+    protected GitPerson() {
+    }
+  }
+
+  public static class ActionInfo extends JavaScriptObject {
+    public final native String id() /*-{ return this.id; }-*/;
+    public final native String method() /*-{ return this.method; }-*/;
+    public final native String label() /*-{ return this.label; }-*/;
+    public final native String title() /*-{ return this.title; }-*/;
+    public final native boolean enabled() /*-{ return this.enabled || false; }-*/;
+
+    protected ActionInfo() {
+    }
+  }
+
+  public static class MessageInfo extends JavaScriptObject {
+    public final native AccountInfo author() /*-{ return this.author; }-*/;
+    public final native String message() /*-{ return this.message; }-*/;
+    private final native String dateRaw() /*-{ return this.date; }-*/;
+
+    public final Timestamp date() {
+      return JavaSqlTimestamp_JsonSerializer.parseTimestamp(dateRaw());
+    }
+
+    protected MessageInfo() {
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
index 7c41ea1..fea117f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
@@ -34,7 +34,7 @@
     for (String q : queries) {
       call.addParameterRaw("q", KeyUtil.encode(q));
     }
-    addOptions(call, ListChangesOption.LABELS);
+    addOptions(call, EnumSet.of(ListChangesOption.LABELS));
     call.get(callback);
   }
 
@@ -45,7 +45,7 @@
     if (limit > 0) {
       call.addParameter("n", limit);
     }
-    addOptions(call, ListChangesOption.LABELS);
+    addOptions(call, EnumSet.of(ListChangesOption.LABELS));
     if (!PagedSingleListScreen.MIN_SORTKEY.equals(sortkey)) {
       call.addParameter("P", sortkey);
     }
@@ -59,16 +59,14 @@
     if (limit > 0) {
       call.addParameter("n", limit);
     }
-    addOptions(call, ListChangesOption.LABELS);
+    addOptions(call, EnumSet.of(ListChangesOption.LABELS));
     if (!PagedSingleListScreen.MAX_SORTKEY.equals(sortkey)) {
       call.addParameter("N", sortkey);
     }
     call.get(callback);
   }
 
-  private static void addOptions(
-      RestApi call, ListChangesOption option1, ListChangesOption... options) {
-    EnumSet<ListChangesOption> s = EnumSet.of(option1, options);
+  public static void addOptions(RestApi call, EnumSet<ListChangesOption> s) {
     call.addParameterRaw("O", Integer.toHexString(ListChangesOption.toBits(s)));
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
index 098fe07..4d1ec7e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
@@ -24,6 +24,7 @@
 
   String revertChangeDefaultMessage(String commitMsg, String commitId);
 
+  String cherryPickedChangeDefaultMessage(String commitMsg, String commitId);
   String changeScreenTitleId(String changeId);
   String outdatedHeader(int outdated);
   String patchSetHeader(int id);
@@ -33,6 +34,7 @@
   String patchTableComments(@PluralCount int count);
   String patchTableDrafts(@PluralCount int count);
   String patchTableSize_Modify(int insertions, int deletions);
+  String patchTableSize_LongModify(int insertions, int deletions);
   String patchTableSize_Lines(@PluralCount int insertions);
 
   String removeReviewer(String fullName);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
index e02c27c..f7dee85 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
@@ -6,6 +6,7 @@
 changesAbandonedInProject = Abandoned Changes In {0}
 
 revertChangeDefaultMessage = Revert \"{0}\"\n\nThis reverts commit {1}.
+cherryPickedChangeDefaultMessage = {0}\n(cherry picked from commit {1})
 
 changeScreenTitleId = Change {0}
 outdatedHeader = Change depends on {0} outdated change(s) and should be rebased on the latest patch sets.
@@ -16,6 +17,7 @@
 patchTableComments = {0} comments
 patchTableDrafts = {0} drafts
 patchTableSize_Modify = +{0}, -{1}
+patchTableSize_LongModify = {0} inserted, {1} deleted
 patchTableSize_Lines = {0} lines
 
 removeReviewer = Remove reviewer {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index c26afaa..0a53b6d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -288,7 +288,7 @@
               // Handled by last callback's onFailure.
             }
           }));
-      ChangeApi.detail(event.getValue().getChange().getId().get(), cbs.add(
+      ChangeApi.detail(event.getValue().getChange().getId().get(), cbs.addFinal(
           new GerritCallback<com.google.gerrit.client.changes.ChangeInfo>() {
             @Override
             public void onSuccess(
@@ -422,7 +422,7 @@
     final Timestamp aged = new Timestamp(System.currentTimeMillis() - AGE);
 
     CommentVisibilityStrategy commentVisibilityStrategy =
-        CommentVisibilityStrategy.EXPAND_MOST_RECENT;
+        CommentVisibilityStrategy.EXPAND_RECENT;
     if (Gerrit.isSignedIn()) {
       commentVisibilityStrategy = Gerrit.getUserAccount()
           .getGeneralPreferences().getCommentVisibilityStrategy();
@@ -457,16 +457,16 @@
       switch (commentVisibilityStrategy) {
         case COLLAPSE_ALL:
           break;
-        case EXPAND_RECENT:
-          isOpen = isRecent;
-          break;
         case EXPAND_ALL:
           isOpen = true;
           break;
         case EXPAND_MOST_RECENT:
-        default:
           isOpen = i == msgList.size() - 1;
           break;
+        case EXPAND_RECENT:
+        default:
+          isOpen = isRecent;
+          break;
       }
       cp.setOpen(isOpen);
       comments.add(cp);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
index 4694272..f993ad9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
@@ -48,11 +48,12 @@
 public class ChangeTable2 extends NavigationTable<ChangeInfo> {
   private static final int C_STAR = 1;
   private static final int C_SUBJECT = 2;
-  private static final int C_OWNER = 3;
-  private static final int C_PROJECT = 4;
-  private static final int C_BRANCH = 5;
-  private static final int C_LAST_UPDATE = 6;
-  private static final int BASE_COLUMNS = 7;
+  private static final int C_STATUS = 3;
+  private static final int C_OWNER = 4;
+  private static final int C_PROJECT = 5;
+  private static final int C_BRANCH = 6;
+  private static final int C_LAST_UPDATE = 7;
+  private static final int BASE_COLUMNS = 8;
 
   private final List<Section> sections;
   private int columns;
@@ -70,6 +71,7 @@
     sections = new ArrayList<Section>();
     table.setText(0, C_STAR, "");
     table.setText(0, C_SUBJECT, Util.C.changeTableColumnSubject());
+    table.setText(0, C_STATUS, Util.C.changeTableColumnStatus());
     table.setText(0, C_OWNER, Util.C.changeTableColumnOwner());
     table.setText(0, C_PROJECT, Util.C.changeTableColumnProject());
     table.setText(0, C_BRANCH, Util.C.changeTableColumnBranch());
@@ -90,6 +92,8 @@
         }
         if (cell.getCellIndex() == C_STAR) {
           // Don't do anything (handled by star itself).
+        } else if (cell.getCellIndex() == C_STATUS) {
+          // Don't do anything.
         } else if (cell.getCellIndex() == C_OWNER) {
           // Don't do anything.
         } else if (getRowItem(cell.getRowIndex()) != null) {
@@ -108,7 +112,7 @@
   protected void onOpenRow(final int row) {
     final ChangeInfo c = getRowItem(row);
     final Change.Id id = c.legacy_id();
-    Gerrit.display(PageLinks.toChange(id), new ChangeScreen(id));
+    Gerrit.display(PageLinks.toChange(id));
   }
 
   private void insertNoneRow(final int row) {
@@ -191,11 +195,12 @@
     }
 
     String subject = Util.cropSubject(c.subject());
+    table.setWidget(row, C_SUBJECT, new TableChangeLink(subject, c));
+
     Change.Status status = c.status();
     if (status != Change.Status.NEW) {
-      subject += " (" + Util.toLongString(status) + ")";
+      table.setText(row, C_STATUS, Util.toLongString(status));
     }
-    table.setWidget(row, C_SUBJECT, new TableChangeLink(subject, c));
 
     if (c.owner() != null) {
       table.setWidget(row, C_OWNER, new AccountLinkPanel(c.owner(), status));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentApi.java
new file mode 100644
index 0000000..d87cb5e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentApi.java
@@ -0,0 +1,67 @@
+// 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.client.changes;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public class CommentApi {
+
+  public static void comments(PatchSet.Id id,
+      AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
+    revision(id, "comments").get(cb);
+  }
+
+  public static void comment(PatchSet.Id id, String commentId,
+      AsyncCallback<CommentInfo> cb) {
+    revision(id, "comments").id(commentId).get(cb);
+  }
+
+  public static void drafts(PatchSet.Id id,
+      AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
+    revision(id, "drafts").get(cb);
+  }
+
+  public static void draft(PatchSet.Id id, String draftId,
+      AsyncCallback<CommentInfo> cb) {
+    revision(id, "drafts").id(draftId).get(cb);
+  }
+
+  public static void createDraft(PatchSet.Id id, CommentInput content,
+      AsyncCallback<CommentInfo> cb) {
+    revision(id, "drafts").put(content, cb);
+  }
+
+  public static void updateDraft(PatchSet.Id id, String draftId,
+      CommentInput content, AsyncCallback<CommentInfo> cb) {
+    revision(id, "drafts").id(draftId).put(content, cb);
+  }
+
+  public static void deleteDraft(PatchSet.Id id, String draftId,
+      AsyncCallback<JavaScriptObject> cb) {
+    revision(id, "drafts").id(draftId).delete(cb);
+  }
+
+  private static RestApi revision(PatchSet.Id id, String type) {
+    return ChangeApi.revision(id).view(type);
+  }
+
+  private CommentApi() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
new file mode 100644
index 0000000..b1f3a2a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
@@ -0,0 +1,81 @@
+// 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.client.changes;
+
+import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.common.changes.Side;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
+
+import java.sql.Timestamp;
+
+public class CommentInfo extends JavaScriptObject {
+  public static CommentInfo create(String path, Side side, int line,
+      String in_reply_to, String message) {
+    CommentInfo info = createObject().cast();
+    info.setPath(path);
+    info.setSide(side);
+    info.setLine(line);
+    info.setInReplyTo(in_reply_to);
+    info.setMessage(message);
+    return info;
+  }
+
+  private final native void setId(String id) /*-{ this.id = id; }-*/;
+  private final native void setPath(String path) /*-{ this.path = path; }-*/;
+
+  private final void setSide(Side side) {
+    setSideRaw(side.toString());
+  }
+  private final native void setSideRaw(String side) /*-{ this.side = side; }-*/;
+
+  private final native void setLine(int line) /*-{ this.line = line; }-*/;
+
+  private final native void setInReplyTo(String in_reply_to) /*-{
+    this.in_reply_to = in_reply_to;
+  }-*/;
+
+  private final native void setMessage(String message) /*-{ this.message = message; }-*/;
+
+  public final native String id() /*-{ return this.id; }-*/;
+  public final native String path() /*-{ return this.path; }-*/;
+
+  public final Side side() {
+    String s = sideRaw();
+    return s != null
+        ? Side.valueOf(s)
+        : Side.REVISION;
+  }
+  private final native String sideRaw() /*-{ return this.side }-*/;
+
+  public final native int line() /*-{ return this.line; }-*/;
+  public final native String in_reply_to() /*-{ return this.in_reply_to; }-*/;
+  public final native String message() /*-{ return this.message; }-*/;
+
+  public final Timestamp updated() {
+    String updatedRaw = updatedRaw();
+    return updatedRaw == null
+        ? null
+        : JavaSqlTimestamp_JsonSerializer.parseTimestamp(updatedRaw());
+  }
+  private final native String updatedRaw() /*-{ return this.updated; }-*/;
+
+  public final native AccountInfo author() /*-{ return this.author; }-*/;
+
+  public final native boolean has_line() /*-{ return this.hasOwnProperty('line'); }-*/;
+
+  protected CommentInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java
new file mode 100644
index 0000000..592e087
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java
@@ -0,0 +1,76 @@
+// 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.client.changes;
+
+import com.google.gerrit.common.changes.Side;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
+
+import java.sql.Timestamp;
+
+public class CommentInput extends JavaScriptObject {
+  public static CommentInput create(CommentInfo original) {
+    CommentInput input = createObject().cast();
+    input.setId(original.id());
+    input.setPath(original.path());
+    input.setSide(original.side());
+    if (original.has_line()) {
+      input.setLine(original.line());
+    }
+    input.setInReplyTo(original.in_reply_to());
+    input.setMessage(original.message());
+    return input;
+  }
+
+  public final native void setId(String id) /*-{ this.id = id; }-*/;
+  public final native void setPath(String path) /*-{ this.path = path; }-*/;
+
+  public final void setSide(Side side) {
+    setSideRaw(side.toString());
+  }
+  private final native void setSideRaw(String side) /*-{ this.side = side; }-*/;
+
+  public final native void setLine(int line) /*-{ this.line = line; }-*/;
+
+  public final native void setInReplyTo(String in_reply_to) /*-{
+    this.in_reply_to = in_reply_to;
+  }-*/;
+
+  public final native void setMessage(String message) /*-{ this.message = message; }-*/;
+  public final native String id() /*-{ return this.id; }-*/;
+  public final native String path() /*-{ return this.path; }-*/;
+
+  public final Side side() {
+    String s = sideRaw();
+    return s != null
+        ? Side.valueOf(s)
+        : Side.REVISION;
+  }
+  private final native String sideRaw() /*-{ return this.side }-*/;
+
+  public final native int line() /*-{ return this.line; }-*/;
+  public final native String in_reply_to() /*-{ return this.in_reply_to; }-*/;
+  public final native String message() /*-{ return this.message; }-*/;
+
+  public final Timestamp updated() {
+    return JavaSqlTimestamp_JsonSerializer.parseTimestamp(updatedRaw());
+  }
+  private final native String updatedRaw() /*-{ return this.updated; }-*/;
+
+  public final native boolean has_line() /*-{ return this.hasOwnProperty('line'); }-*/;
+
+  protected CommentInput() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index ca326cd..dded70b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -13,21 +13,25 @@
 // limitations under the License.
 
 package com.google.gerrit.client.changes;
-
 import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GitwebLink;
 import com.google.gerrit.client.download.DownloadPanel;
 import com.google.gerrit.client.patches.PatchUtil;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.ui.AccountLinkPanel;
-import com.google.gerrit.client.ui.CommentedActionDialog;
+import com.google.gerrit.client.ui.ActionDialog;
+import com.google.gerrit.client.ui.CherryPickDialog;
 import com.google.gerrit.client.ui.ComplexDisclosurePanel;
 import com.google.gerrit.client.ui.ListenableAccountDiffPreference;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.PatchSetDetail;
+import com.google.gerrit.common.data.UiCommandDetail;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
@@ -35,18 +39,20 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.UserIdentity;
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.logical.shared.OpenEvent;
 import com.google.gwt.event.logical.shared.OpenHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.DisclosurePanel;
 import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.FocusWidget;
 import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwt.user.client.ui.Panel;
 import com.google.gwtjsonrpc.common.VoidResult;
@@ -174,6 +180,7 @@
           if (changeDetail.isCurrentPatchSet(detail)) {
             populateActions(detail);
           }
+          populateCommands(detail);
         }
         if (detail.getPatchSet().isDraft()) {
           if (changeDetail.canPublish()) {
@@ -383,6 +390,48 @@
       actionsPanel.add(b);
     }
 
+    if (changeDetail.canCherryPick()) {
+      final Button b = new Button(Util.C.buttonCherryPickChangeBegin());
+      b.addClickHandler(new ClickHandler() {
+        @Override
+        public void onClick(final ClickEvent event) {
+          b.setEnabled(false);
+          new CherryPickDialog(b, changeDetail.getChange().getProject()) {
+            {
+              sendButton.setText(Util.C.buttonCherryPickChangeSend());
+              message.setText(Util.M.cherryPickedChangeDefaultMessage(
+                  detail.getInfo().getMessage().trim(),
+                  detail.getPatchSet().getRevision().get()));
+            }
+
+            @Override
+            public void onSend() {
+              ChangeApi.cherrypick(changeDetail.getChange().getChangeId(),
+                  patchSet.getRevision().get(),
+                  getDestinationBranch(),
+                  getMessageText(),
+                  new GerritCallback<ChangeInfo>() {
+                    @Override
+                    public void onSuccess(ChangeInfo result) {
+                      sent = true;
+                      Gerrit.display(PageLinks.toChange(new Change.Id(result
+                          ._number())));
+                      hide();
+                    }
+
+                    @Override
+                    public void onFailure(Throwable caught) {
+                      enableButtons(true);
+                      super.onFailure(caught);
+                    }
+                  });
+            }
+          }.center();
+        }
+      });
+      actionsPanel.add(b);
+    }
+
     if (changeDetail.canAbandon()) {
       final Button b = new Button(Util.C.buttonAbandonChangeBegin());
       b.addClickHandler(new ClickHandler() {
@@ -498,6 +547,47 @@
     }
   }
 
+  private void populateCommands(final PatchSetDetail detail) {
+    for (final UiCommandDetail cmd : detail.getCommands()) {
+      final Button b = new Button();
+      b.setText(cmd.label);
+      b.setEnabled(cmd.enabled);
+      b.setTitle(cmd.title);
+      b.addClickHandler(new ClickHandler() {
+        @Override
+        public void onClick(final ClickEvent event) {
+          b.setEnabled(false);
+          AsyncCallback<NativeString> cb =
+              new AsyncCallback<NativeString>() {
+                @Override
+                public void onFailure(Throwable caught) {
+                  b.setEnabled(true);
+                  new ErrorDialog(caught).center();
+                }
+
+                @Override
+                public void onSuccess(NativeString msg) {
+                  b.setEnabled(true);
+                  if (msg != null && !msg.asString().isEmpty()) {
+                    Window.alert(msg.asString());
+                  }
+                  Gerrit.display(PageLinks.toChange(patchSet.getId()));
+                }
+              };
+          RestApi api = ChangeApi.revision(patchSet.getId()).view(cmd.id);
+          if ("PUT".equalsIgnoreCase(cmd.method)) {
+            api.put(JavaScriptObject.createObject(), cb);
+          } else if ("DELETE".equalsIgnoreCase(cmd.method)) {
+            api.delete(cb);
+          } else {
+            api.post(JavaScriptObject.createObject(), cb);
+          }
+        }
+      });
+      actionsPanel.add(b);
+    }
+  }
+
   private void populateReviewAction() {
     final Button b = new Button(Util.C.buttonReview());
     b.addClickHandler(new ClickHandler() {
@@ -633,25 +723,4 @@
       patchTable.setActive(active);
     }
   }
-
-  private abstract class ActionDialog extends CommentedActionDialog<ChangeDetail> {
-    public ActionDialog(final FocusWidget enableOnFailure, final boolean redirect,
-        String dialogTitle, String dialogHeading) {
-      super(dialogTitle, dialogHeading, new ChangeDetailCache.IgnoreErrorCallback() {
-          @Override
-          public void onSuccess(ChangeDetail result) {
-            if (redirect) {
-              Gerrit.display(PageLinks.toChange(result.getChange().getId()));
-            } else {
-              super.onSuccess(result);
-            }
-          }
-
-          @Override
-          public void onFailure(Throwable caught) {
-            enableOnFailure.setEnabled(true);
-          }
-        });
-    }
-  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
index 4fa76dd..608a2c7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
@@ -36,7 +36,6 @@
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
@@ -163,7 +162,7 @@
             // Handled by ScreenLoadCallback.onFailure().
           }
         }));
-    Util.DETAIL_SVC.patchSetPublishDetail(patchSetId, cbs.addGwtjsonrpc(
+    Util.DETAIL_SVC.patchSetPublishDetail(patchSetId, cbs.addFinal(
         new ScreenLoadCallback<PatchSetPublishDetail>(this) {
           @Override
           protected void preDisplay(final PatchSetPublishDetail result) {
@@ -402,7 +401,6 @@
   private void onSend2(final boolean submit) {
     ReviewInput data = ReviewInput.create();
     data.message(ChangeApi.emptyToNull(message.getText().trim()));
-    data.init();
     for (final ValueRadioButton b : approvalButtons) {
       if (b.getValue()) {
         data.label(b.label.name(), b.parseValue());
@@ -432,23 +430,6 @@
         });
   }
 
-  private static class ReviewInput extends JavaScriptObject {
-    static ReviewInput create() {
-      return (ReviewInput) createObject();
-    }
-
-    final native void message(String m) /*-{ if(m)this.message=m; }-*/;
-    final native void label(String n, short v) /*-{ this.labels[n]=v; }-*/;
-    final native void init() /*-{
-      this.labels = {};
-      this.strict_labels = true;
-      this.drafts = 'PUBLISH';
-    }-*/;
-
-    protected ReviewInput() {
-    }
-  }
-
   private void submit() {
     ChangeApi.submit(patchSetId.getParentKey().get(), revision,
       new GerritCallback<SubmitInfo>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
index 01e294f..12bcf63 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
@@ -55,7 +55,7 @@
           if (result.length() == 1 && isSingleQuery(query)) {
             ChangeInfo c = result.get(0);
             Change.Id id = c.legacy_id();
-            Gerrit.display(PageLinks.toChange(id), new ChangeScreen(id));
+            Gerrit.display(PageLinks.toChange(id));
           } else {
             display(result);
             QueryScreen.this.display();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInfo.java
similarity index 66%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInfo.java
index b2fb901..3508c3d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInfo.java
@@ -12,12 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.client.changes;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gwt.core.client.JavaScriptObject;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class ReviewInfo extends JavaScriptObject {
+
+  public final native NativeMap<?> labels() /*-{ return this.labels }-*/;
+
+  protected ReviewInfo() {
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java
new file mode 100644
index 0000000..fa3d784
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java
@@ -0,0 +1,46 @@
+// 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.client.changes;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ReviewInput extends JavaScriptObject {
+  public static enum NotifyHandling {
+    NONE, OWNER, OWNER_REVIEWERS, ALL;
+  }
+
+  public static ReviewInput create() {
+    ReviewInput r = createObject().cast();
+    r.init();
+    return r;
+  }
+
+  public final native void message(String m) /*-{ if(m)this.message=m; }-*/;
+  public final native void label(String n, short v) /*-{ this.labels[n]=v; }-*/;
+
+  public final void notify(NotifyHandling e) {
+    _notify(e.name());
+  }
+  private final native void _notify(String n) /*-{ this.notify=n; }-*/;
+
+  private final native void init() /*-{
+    this.labels = {};
+    this.strict_labels = true;
+    this.drafts = 'PUBLISH';
+  }-*/;
+
+  protected ReviewInput() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java
index 70bf4b6..bd97790 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitFailureDialog.java
@@ -18,13 +18,13 @@
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 import com.google.gwtjsonrpc.client.RemoteJsonException;
 
-class SubmitFailureDialog extends ErrorDialog {
-  static boolean isConflict(Throwable err) {
+public class SubmitFailureDialog extends ErrorDialog {
+  public static boolean isConflict(Throwable err) {
     return err instanceof RemoteJsonException
         && 409 == ((RemoteJsonException) err).getCode();
   }
 
-  SubmitFailureDialog(String msg) {
+  public SubmitFailureDialog(String msg) {
     super(new SafeHtmlBuilder().append(msg.trim()).wikify());
     setText(Util.C.submitFailed());
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitInfo.java
index a9206b7..6d8bc30 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/SubmitInfo.java
@@ -17,7 +17,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.core.client.JavaScriptObject;
 
-class SubmitInfo extends JavaScriptObject {
+public class SubmitInfo extends JavaScriptObject {
   final Change.Status status() {
     return Change.Status.valueOf(statusRaw());
   }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
similarity index 65%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
index b2fb901..45abbd6 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
@@ -12,12 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.client.config;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gwt.core.client.JavaScriptObject;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class CapabilityInfo extends JavaScriptObject {
+  public final native String id() /*-{ return this.id; }-*/;
+  public final native String name() /*-{ return this.name; }-*/;
+
+  protected CapabilityInfo() {
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
new file mode 100644
index 0000000..b283a0d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
@@ -0,0 +1,30 @@
+// 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.client.config;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * A collection of static methods which work on the Gerrit REST API for server
+ * configuration.
+ */
+public class ConfigServerApi {
+  /** map of the server wide capabilities (core & plugins). */
+  public static void capabilities(AsyncCallback<NativeMap<CapabilityInfo>> cb) {
+    new RestApi("/config/server/capabilities/").get(cb);
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java
new file mode 100644
index 0000000..f6a62bc
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java
@@ -0,0 +1,194 @@
+//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.client.diff;
+
+import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.safehtml.client.SafeHtml;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.LineWidget;
+
+import java.sql.Timestamp;
+
+/** An HtmlPanel for displaying a comment */
+abstract class CommentBox extends Composite {
+  interface CommentBoxStyle extends CssResource {
+    String open();
+    String close();
+  }
+
+  private CommentLinkProcessor commentLinkProcessor;
+  private CommentInfo original;
+  private PatchSet.Id patchSetId;
+  private PaddingManager widgetManager;
+  private SideBySide2 diffView;
+  private boolean draft;
+  private LineWidget selfWidget;
+  private CodeMirror cm;
+  private HandlerRegistration regClick;
+  private ClickHandler clickFocusHandler;
+
+  @UiField(provided=true)
+  CommentBoxHeader header;
+
+  @UiField
+  HTML contentPanelMessage;
+
+  @UiField
+  CommentBoxResources res;
+
+  CommentBox(
+      SideBySide2 host,
+      CodeMirror cmInstance,
+      UiBinder<? extends Widget, CommentBox> binder,
+      PatchSet.Id id, CommentInfo info, CommentLinkProcessor linkProcessor,
+      boolean isDraft) {
+    diffView = host;
+    cm = cmInstance;
+    commentLinkProcessor = linkProcessor;
+    original = info;
+    patchSetId = id;
+    draft = isDraft;
+    header = new CommentBoxHeader(info.author(), info.updated(), isDraft);
+    initWidget(binder.createAndBindUi(this));
+    clickFocusHandler = new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        cm.focus();
+      }
+    };
+    enableClickFocusHandler();
+    res.style().ensureInjected();
+    setMessageText(info.message());
+  }
+
+  void resizePaddingWidget() {
+    Scheduler.get().scheduleDeferred(new ScheduledCommand(){
+      public void execute() {
+        if (selfWidget == null || widgetManager == null) {
+          throw new IllegalStateException(
+              "resizePaddingWidget() called before setting up widgets");
+        }
+        selfWidget.changed();
+        widgetManager.resizePaddingWidget();
+      }
+    });
+  }
+
+  void setMessageText(String message) {
+    if (message == null) {
+      message = "";
+    } else {
+      message = message.trim();
+    }
+    header.setSummaryText(message);
+    SafeHtml buf = new SafeHtmlBuilder().append(message).wikify();
+    buf = commentLinkProcessor.apply(buf);
+    SafeHtml.set(contentPanelMessage, buf);
+  }
+
+  void setDate(Timestamp when) {
+    header.setDate(when);
+  }
+
+  void setOpen(boolean open) {
+    if (open) {
+      removeStyleName(res.style().close());
+      addStyleName(res.style().open());
+    } else {
+      removeStyleName(res.style().open());
+      addStyleName(res.style().close());
+    }
+    resizePaddingWidget();
+  }
+
+  boolean isOpen() {
+    return getStyleName().contains(res.style().open());
+  }
+
+  SideBySide2 getDiffView() {
+    return diffView;
+  }
+
+  PatchSet.Id getPatchSetId() {
+    return patchSetId;
+  }
+
+  CommentInfo getOriginal() {
+    return original;
+  }
+
+  void updateOriginal(CommentInfo newInfo) {
+    original = newInfo;
+  }
+
+  PaddingManager getPaddingManager() {
+    return widgetManager;
+  }
+
+  boolean isDraft() {
+    return draft;
+  }
+
+  void setPaddingManager(PaddingManager manager) {
+    widgetManager = manager;
+  }
+
+  void setSelfWidget(LineWidget widget) {
+    selfWidget = widget;
+  }
+
+  LineWidget getSelfWidget() {
+    return selfWidget;
+  }
+
+  CodeMirror getCm() {
+    return cm;
+  }
+
+  @UiHandler("header")
+  void onHeaderClick(ClickEvent e) {
+    setOpen(!isOpen());
+    cm.focus();
+  }
+
+  void enableClickFocusHandler() {
+    if (regClick == null) {
+      regClick = addDomHandler(clickFocusHandler, ClickEvent.getType());
+    }
+  }
+
+  void disableClickFocusHandler() {
+    if (regClick != null) {
+      regClick.removeHandler();
+      regClick = null;
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxHeader.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxHeader.java
new file mode 100644
index 0000000..70a1612
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxHeader.java
@@ -0,0 +1,111 @@
+//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.client.diff;
+
+import com.google.gerrit.client.AvatarImage;
+import com.google.gerrit.client.FormatUtil;
+import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.patches.PatchUtil;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.UIObject;
+
+import java.sql.Timestamp;
+
+/**
+ * An HtmlPanel representing the header of a CommentBox, displaying
+ * the author's avatar (if applicable), the author's name, the summary,
+ * and the date.
+ */
+class CommentBoxHeader extends Composite implements HasClickHandlers {
+  interface Binder extends UiBinder<HTMLPanel, CommentBoxHeader> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  interface CommentBoxHeaderStyle extends CssResource {
+    String name();
+    String summary();
+    String date();
+  }
+
+  private boolean draft;
+
+  @UiField
+  Element avatarCell;
+
+  @UiField(provided=true)
+  AvatarImage avatar;
+
+  @UiField
+  Element name;
+
+  @UiField
+  Element summary;
+
+  @UiField
+  Element date;
+
+  @UiField
+  CommentBoxHeaderStyle headerStyle;
+
+  CommentBoxHeader(AccountInfo author, Timestamp when, boolean isDraft) {
+    if (author != null) {
+      avatar = new AvatarImage(author, 26);
+      avatar.setSize("", "");
+    } else {
+      avatar = new AvatarImage();
+    }
+    initWidget(uiBinder.createAndBindUi(this));
+    if (author == null) {
+      UIObject.setVisible(avatarCell, false);
+    }
+    draft = isDraft;
+    if (when != null) {
+      setDate(when);
+    }
+    if (isDraft) {
+      name.setInnerText(PatchUtil.C.draft());
+    } else {
+      name.setInnerText(FormatUtil.name(author));
+      name.setTitle(FormatUtil.nameEmail(author));
+      date.setTitle(FormatUtil.mediumFormat(when));
+    }
+  }
+
+  void setDate(Timestamp when) {
+    if (draft) {
+      date.setInnerText(PatchUtil.M.draftSaved(when));
+    } else {
+      date.setInnerText(FormatUtil.shortFormatDayTime(when));
+    }
+  }
+
+  void setSummaryText(String message) {
+    summary.setInnerText(message);
+  }
+
+  @Override
+  public HandlerRegistration addClickHandler(ClickHandler handler) {
+    return addDomHandler(handler, ClickEvent.getType());
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxHeader.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxHeader.ui.xml
new file mode 100644
index 0000000..b7f1820
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxHeader.ui.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:c='urn:import:com.google.gerrit.client'>
+  <ui:with field='res' type='com.google.gerrit.client.diff.CommentBoxResources' />
+  <ui:style field='headerStyle'
+    type='com.google.gerrit.client.diff.CommentBoxHeader.CommentBoxHeaderStyle'>
+    .avatarCell {
+      width: 26px;
+      padding-right: 5px;
+    }
+    .name {
+      width: 15%;
+      font-weight: bold;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    .summary {
+      width: 60%;
+    }
+    .date {
+      width: 15%;
+      text-align: right;
+    }
+  </ui:style>
+    <g:HTMLPanel>
+    <table class='{res.style.table}'>
+      <tr>
+        <td ui:field='avatarCell' class='{headerStyle.avatarCell}'>
+          <c:AvatarImage ui:field='avatar' />
+        </td>
+        <td ui:field='name' class='{headerStyle.name}'></td>
+        <td class='{headerStyle.summary}'>
+          <div ui:field='summary' class='{res.style.summaryText}'></div>
+        </td>
+        <td ui:field='date' class='{headerStyle.date}'></td>
+      </tr>
+    </table>
+    </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxResources.java
new file mode 100644
index 0000000..c770fb8
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxResources.java
@@ -0,0 +1,37 @@
+// 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.client.diff;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+
+/**
+ * Resources used by diff.
+ */
+interface CommentBoxResources extends ClientBundle {
+  @Source("CommentBoxUi.css")
+  Style style();
+
+  interface Style extends CssResource {
+    String open();
+    String close();
+    String commentBox();
+    String table();
+    String summaryText();
+    String contentPanel();
+    String message();
+    String button();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
new file mode 100644
index 0000000..24ec2b7
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
@@ -0,0 +1,45 @@
+.commentBox {
+  background-color: #e5ecf9;
+  border: 1px solid black;
+  -webkit-box-shadow: 3px 3px 3px #888888;
+  -moz-box-shadow: 3px 3px 3px #888888;
+  box-shadow: 3px 3px 3px #888888;
+  margin-bottom: 5px;
+  margin-right: 5px;
+  overflow: visible;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+}
+
+.table {
+  width: 100%;
+  cursor: pointer;
+  table-layout: fixed;
+}
+
+.summaryText {
+  color: #777;
+  height: 1em;
+  overflow: hidden;
+  padding-bottom: 2px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.open .summaryText {
+  display: none;
+}
+
+.close .contentPanel {
+  display: none;
+}
+
+.message {
+  margin-left: 5px;
+  margin-right: 5px;
+}
+
+.button {
+  margin-left: 5px;
+  margin-bottom: 5px;
+}
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java
new file mode 100644
index 0000000..da0a450
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java
@@ -0,0 +1,79 @@
+// 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.client.diff;
+
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public class DiffApi {
+  public enum IgnoreWhitespace {
+    NONE, TRAILING, CHANGED, ALL;
+  };
+
+  public static void list(int id, String revision,
+      AsyncCallback<NativeMap<FileInfo>> cb) {
+    ChangeApi.revision(id, revision)
+      .view("files")
+      .get(NativeMap.copyKeysIntoChildren("path", cb));
+  }
+
+  public static DiffApi diff(PatchSet.Id id, String path) {
+    return new DiffApi(ChangeApi.revision(id)
+        .view("files").id(path)
+        .view("diff"));
+  }
+
+  private final RestApi call;
+
+  private DiffApi(RestApi call) {
+    this.call = call;
+  }
+
+  public DiffApi base(PatchSet.Id id) {
+    if (id != null) {
+      call.addParameter("base", id.get());
+    }
+    return this;
+  }
+
+  public DiffApi ignoreWhitespace(IgnoreWhitespace w) {
+    if (w != null && w != IgnoreWhitespace.NONE) {
+      call.addParameter("ignore-whitespace", w);
+    }
+    return this;
+  }
+
+  public DiffApi intraline() {
+    call.addParameterTrue("intraline");
+    return this;
+  }
+
+  public DiffApi wholeFile() {
+    call.addParameter("context", "ALL");
+    return this;
+  }
+
+  public DiffApi context(int lines) {
+    call.addParameter("context", lines);
+    return this;
+  }
+
+  public void get(AsyncCallback<DiffInfo> cb) {
+    call.get(cb);
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
new file mode 100644
index 0000000..f833509
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
@@ -0,0 +1,127 @@
+// 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.client.diff;
+
+import com.google.gerrit.reviewdb.client.Patch.ChangeType;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+
+public class DiffInfo extends JavaScriptObject {
+  public static final String GITLINK = "x-git/gitlink";
+  public static final String SYMLINK = "x-git/symlink";
+
+  public final native FileMeta meta_a() /*-{ return this.meta_a; }-*/;
+  public final native FileMeta meta_b() /*-{ return this.meta_b; }-*/;
+  public final native JsArrayString diff_header() /*-{ return this.diff_header; }-*/;
+  public final native JsArray<Region> content() /*-{ return this.content; }-*/;
+
+  public final ChangeType change_type() {
+    return ChangeType.valueOf(change_typeRaw());
+  }
+  private final native String change_typeRaw()
+  /*-{ return this.change_type }-*/;
+
+  public final IntraLineStatus intraline_status() {
+    String s = intraline_statusRaw();
+    return s != null
+        ? IntraLineStatus.valueOf(s)
+        : IntraLineStatus.OFF;
+  }
+  private final native String intraline_statusRaw()
+  /*-{ return this.intraline_status }-*/;
+
+  public final boolean has_skip() {
+    JsArray<Region> c = content();
+    for (int i = 0; i < c.length(); i++) {
+      if (c.get(i).skip() != 0) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public final String text_a() {
+    StringBuilder s = new StringBuilder();
+    JsArray<Region> c = content();
+    for (int i = 0; i < c.length(); i++) {
+      Region r = c.get(i);
+      if (r.ab() != null) {
+        append(s, r.ab());
+      } else if (r.a() != null) {
+        append(s, r.a());
+      }
+      // TODO skip may need to be handled
+    }
+    return s.toString();
+  }
+
+  public final String text_b() {
+    StringBuilder s = new StringBuilder();
+    JsArray<Region> c = content();
+    for (int i = 0; i < c.length(); i++) {
+      Region r = c.get(i);
+      if (r.ab() != null) {
+        append(s, r.ab());
+      } else if (r.b() != null) {
+        append(s, r.b());
+      }
+      // TODO skip may need to be handled
+    }
+    return s.toString();
+  }
+
+  private static void append(StringBuilder s, JsArrayString lines) {
+    for (int i = 0; i < lines.length(); i++) {
+      s.append(lines.get(i)).append('\n');
+    }
+  }
+
+  protected DiffInfo() {
+  }
+
+  public enum IntraLineStatus {
+    OFF, OK, TIMEOUT, FAILURE;
+  }
+
+  public static class FileMeta extends JavaScriptObject {
+    public final native String name() /*-{ return this.name; }-*/;
+    public final native String content_type() /*-{ return this.content_type; }-*/;
+
+    protected FileMeta() {
+    }
+  }
+
+  public static class Region extends JavaScriptObject {
+    public final native JsArrayString ab() /*-{ return this.ab; }-*/;
+    public final native JsArrayString a() /*-{ return this.a; }-*/;
+    public final native JsArrayString b() /*-{ return this.b; }-*/;
+    public final native int skip() /*-{ return this.skip || 0; }-*/;
+
+    public final native JsArray<Span> edit_a() /*-{ return this.edit_a }-*/;
+    public final native JsArray<Span> edit_b() /*-{ return this.edit_b }-*/;
+
+    protected Region() {
+    }
+  }
+
+  public static class Span extends JavaScriptObject {
+    public final native int skip() /*-{ return this[0]; }-*/;
+    public final native int mark() /*-{ return this[1]; }-*/;
+
+    protected Span() {
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
new file mode 100644
index 0000000..8085b62
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -0,0 +1,59 @@
+//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.client.diff;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * A table with one row and two columns to hold the two CodeMirrors displaying
+ * the files to be diffed.
+ */
+class DiffTable extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, DiffTable> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  interface LineStyle extends CssResource {
+    String intralineBg();
+    String diff();
+    String padding();
+    String activeLine();
+    String activeLineBg();
+    String hideNumber();
+  }
+
+  @UiField
+  Element cmA;
+
+  @UiField
+  Element cmB;
+
+  @UiField
+  static LineStyle style;
+
+  DiffTable() {
+    initWidget(uiBinder.createAndBindUi(this));
+  }
+
+  void add(Widget widget) {
+    ((HTMLPanel) getWidget()).add(widget);
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
new file mode 100644
index 0000000..91f73cc
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:style type='com.google.gerrit.client.diff.DiffTable.LineStyle'>
+    @external .CodeMirror, .CodeMirror-selectedtext;
+    @external .CodeMirror-linenumber, .CodeMirror-vscrollbar;
+    @external .CodeMirror-hscrollbar;
+    @external .cm-keymap-fat-cursor, CodeMirror-cursor;
+    @external .cm-searching, .cm-trailingspace;
+    .difftable .CodeMirror pre {
+      padding: 0;
+      padding-bottom: 0.1em;
+      overflow: hidden;
+    }
+    .difftable .CodeMirror pre span {
+      padding-bottom: 0.1em;
+    }
+    .table {
+      width: 100%;
+      table-layout: fixed;
+    }
+    .a, .b {
+      width: 50%;
+    }
+    .a .intralineBg,
+    .a .intralineBg .CodeMirror-linenumber {
+      background-color: #fee;
+    }
+    .b .intralineBg,
+    .b .intralineBg .CodeMirror-linenumber {
+      background-color: #dfd;
+    }
+    .a .diff,
+    .a .diff .CodeMirror-linenumber {
+      background-color: #faa;
+    }
+    .b .diff,
+    .b .diff .CodeMirror-linenumber {
+      background-color: #9f9;
+    }
+    .padding {
+      background-color: #eee;
+    }
+    .CodeMirror-hscrollbar {
+      display: none !important;
+    }
+    .a .CodeMirror-vscrollbar {
+      display: none !important;
+    }
+    .activeLine .CodeMirror-linenumber,
+    .activeLine .diff, .activeLine .intralineBg {
+      background-color: #E0FFFF !important;
+    }
+    .activeLineBg {
+      background-color: #E0FFFF !important;
+    }
+    .cm-searching {
+      background-color: #ffa !important;
+    }
+    .cm-trailingspace {
+      background-color: red !important;
+    }
+    .CodeMirror-selectedtext {
+      background-color: inherit !important;
+    }
+    .difftable .CodeMirror-linenumber {
+      height: 1.1em;
+    }
+    .hideNumber .CodeMirror-linenumber {
+      color: transparent;
+      background-color: #def;
+      padding-right: 20px;
+      height: 1.3em;
+    }
+    .difftable .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+      opacity: 0.8;
+      z-index: 2;
+    }
+  </ui:style>
+  <g:HTMLPanel styleName='{style.difftable}'>
+    <table class='{style.table}'>
+      <tr>
+        <td ui:field='cmA' class='{style.a}'></td>
+        <td ui:field='cmB' class='{style.b}'></td>
+      </tr>
+    </table>
+  </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
new file mode 100644
index 0000000..acd2b54
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
@@ -0,0 +1,256 @@
+//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.client.diff;
+
+import com.google.gerrit.client.changes.CommentApi;
+import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.changes.CommentInput;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwtexpui.globalkey.client.NpTextArea;
+
+import net.codemirror.lib.CodeMirror;
+
+/** An HtmlPanel for displaying and editing a draft */
+class DraftBox extends CommentBox {
+  interface Binder extends UiBinder<HTMLPanel, DraftBox> {}
+  private static UiBinder<HTMLPanel, CommentBox> uiBinder =
+      GWT.create(Binder.class);
+
+  interface DraftBoxStyle extends CssResource {
+    String edit();
+    String view();
+    String newDraft();
+  }
+
+  @UiField
+  NpTextArea editArea;
+
+  @UiField
+  Button edit;
+
+  @UiField
+  Button save;
+
+  @UiField
+  Button cancel;
+
+  @UiField
+  Button discard;
+
+  @UiField
+  DraftBoxStyle draftStyle;
+
+  private static final int INITIAL_COLS = 60;
+  private static final int INITIAL_LINES = 5;
+  private static final int MAX_LINES = 30;
+
+  private boolean isNew;
+  private PublishedBox replyToBox;
+  private Timer expandTimer;
+
+  DraftBox(
+      SideBySide2 host,
+      CodeMirror cm,
+      PatchSet.Id id,
+      CommentInfo info,
+      CommentLinkProcessor linkProcessor,
+      boolean isNewDraft,
+      boolean saveOnInit) {
+    super(host, cm, uiBinder, id, info, linkProcessor, true);
+
+    isNew = isNewDraft;
+    editArea.setText(info.message());
+    editArea.setCharacterWidth(INITIAL_COLS);
+    editArea.setVisibleLines(INITIAL_LINES);
+    editArea.setSpellCheck(true);
+    expandTimer = new Timer() {
+      @Override
+      public void run() {
+        expandText();
+      }
+    };
+    if (saveOnInit) {
+      onSave(null);
+    }
+    if (isNew) {
+      addStyleName(draftStyle.newDraft());
+    }
+    addDomHandler(new MouseMoveHandler() {
+      @Override
+      public void onMouseMove(MouseMoveEvent event) {
+        resizePaddingWidget();
+      }
+    }, MouseMoveEvent.getType());
+  }
+
+  private void expandText() {
+    double cols = editArea.getCharacterWidth();
+    int rows = 2;
+    for (String line : editArea.getText().split("\n")) {
+      rows += Math.ceil((1.0 + line.length()) / cols);
+    }
+    rows = Math.max(INITIAL_LINES, Math.min(rows, MAX_LINES));
+    if (editArea.getVisibleLines() != rows) {
+      editArea.setVisibleLines(rows);
+    }
+    resizePaddingWidget();
+  }
+
+  void setEdit(boolean edit) {
+    if (edit) {
+      setOpen(true);
+      removeStyleName(draftStyle.view());
+      addStyleName(draftStyle.edit());
+      editArea.setText(getOriginal().message());
+      expandText();
+      editArea.setReadOnly(false);
+      editArea.setFocus(true);
+      disableClickFocusHandler();
+    } else {
+      expandTimer.cancel();
+      editArea.setReadOnly(true);
+      removeStyleName(draftStyle.edit());
+      addStyleName(draftStyle.view());
+      enableClickFocusHandler();
+    }
+    resizePaddingWidget();
+  }
+
+  void registerReplyToBox(PublishedBox box) {
+    replyToBox = box;
+  }
+
+  private void removeUI() {
+    setEdit(false);
+    expandTimer.cancel();
+    if (replyToBox != null) {
+      replyToBox.unregisterReplyBox();
+    }
+    CommentInfo info = getOriginal();
+    getDiffView().removeDraft(info.side(), info.line() - 1);
+    removeFromParent();
+    getSelfWidget().clear();
+    PaddingManager manager = getPaddingManager();
+    manager.remove(this);
+    manager.resizePaddingWidget();
+    getCm().focus();
+  }
+
+  @UiHandler("contentPanelMessage")
+  void onDoubleClick(DoubleClickEvent e) {
+    setEdit(true);
+  }
+
+  @UiHandler("edit")
+  void onEdit(ClickEvent e) {
+    setEdit(true);
+  }
+
+  @UiHandler("save")
+  void onSave(ClickEvent e) {
+    final String message = editArea.getText();
+    if (message.equals("")) {
+      return;
+    }
+    CommentInfo original = getOriginal();
+    CommentInput input = CommentInput.create(original);
+    input.setMessage(message);
+    setEdit(false);
+    GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() {
+      @Override
+      public void onSuccess(CommentInfo result) {
+        updateOriginal(result);
+        setMessageText(message);
+        setDate(result.updated());
+        if (isNew) {
+          removeStyleName(draftStyle.newDraft());
+          isNew = false;
+        }
+      }
+    };
+    if (isNew) {
+      CommentApi.createDraft(getPatchSetId(), input, cb);
+    } else {
+      CommentApi.updateDraft(getPatchSetId(), original.id(), input, cb);
+    }
+    getCm().focus();
+  }
+
+  @UiHandler("cancel")
+  void onCancel(ClickEvent e) {
+    setEdit(false);
+    getCm().focus();
+  }
+
+  @UiHandler("discard")
+  void onDiscard(ClickEvent e) {
+    if (isNew) {
+      removeUI();
+    } else {
+      setEdit(false);
+      CommentApi.deleteDraft(getPatchSetId(), getOriginal().id(),
+          new GerritCallback<JavaScriptObject>() {
+        @Override
+        public void onSuccess(JavaScriptObject result) {
+          removeUI();
+        }
+      });
+    }
+  }
+
+  @UiHandler("editArea")
+  void onCtrlS(KeyDownEvent e) {
+    if ((e.isControlKeyDown() || e.isMetaKeyDown())
+        && !e.isAltKeyDown() && !e.isShiftKeyDown()) {
+      switch (e.getNativeKeyCode()) {
+        case 's':
+        case 'S':
+          e.preventDefault();
+          onSave(null);
+          return;
+      }
+    }
+    expandTimer.schedule(250);
+  }
+
+  /** TODO: Unused now. Re-enable this after implementing auto-save */
+  void onEsc(KeyDownEvent e) {
+    if (e.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+      if (isNew) {
+        removeUI();
+      } else {
+        onCancel(null);
+      }
+      e.preventDefault();
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml
new file mode 100644
index 0000000..26cd9cc
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:d='urn:import:com.google.gerrit.client.diff'
+    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'>
+  <ui:with field='res' type='com.google.gerrit.client.diff.CommentBoxResources' />
+  <ui:style field='draftStyle' type='com.google.gerrit.client.diff.DraftBox.DraftBoxStyle'>
+    .edit .messagePanel {
+      display: none;
+    }
+    textarea.editArea {
+      margin-left: 5px;
+      margin-bottom: 2px;
+    }
+    .view .editArea {
+      display: none;
+    }
+    .newDraft .cancel {
+      display: none;
+    }
+  </ui:style>
+  <g:HTMLPanel addStyleNames='{res.style.commentBox}'>
+    <d:CommentBoxHeader ui:field='header' />
+    <div class='{res.style.contentPanel}'>
+      <c:NpTextArea ui:field='editArea' addStyleNames='{draftStyle.editArea}'/>
+      <div class='{res.style.button}'>
+        <g:Button ui:field='save' addStyleNames='{draftStyle.editArea}'>
+          <ui:msg>Save</ui:msg>
+        </g:Button>
+        <g:Button ui:field='cancel'
+            addStyleNames='{draftStyle.editArea} {draftStyle.cancel}'>
+          <ui:msg>Cancel</ui:msg>
+        </g:Button>
+        <g:Button ui:field='discard' addStyleNames='{draftStyle.editArea}'>
+          <ui:msg>Discard</ui:msg>
+        </g:Button>
+      </div>
+    </div>
+    <div class='{res.style.contentPanel}'>
+      <g:HTML ui:field='contentPanelMessage'
+          addStyleNames='{res.style.message} {draftStyle.messagePanel}'></g:HTML>
+      <g:Button ui:field='edit' addStyleNames='{draftStyle.messagePanel} {res.style.button}'>
+        <ui:msg>Edit</ui:msg>
+      </g:Button>
+    </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java
new file mode 100644
index 0000000..cc73a94
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java
@@ -0,0 +1,28 @@
+// 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.client.diff;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class FileInfo extends JavaScriptObject {
+  public final native String path() /*-{ return this.path; }-*/;
+  public final native String old_path() /*-{ return this.old_path; }-*/;
+  public final native int lines_inserted() /*-{ return this.lines_inserted || 0; }-*/;
+  public final native int lines_deleted() /*-{ return this.lines_deleted || 0; }-*/;
+  public final native boolean binary() /*-{ return this.binary || false; }-*/;
+
+  protected FileInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java
new file mode 100644
index 0000000..aed0813
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java
@@ -0,0 +1,186 @@
+// 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.client.diff;
+
+import com.google.gerrit.common.changes.Side;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Helper class to handle calculations involving line gaps. */
+class LineMapper {
+  private int lineA;
+  private int lineB;
+  private List<LineGap> lineMapAtoB;
+  private List<LineGap> lineMapBtoA;
+
+  LineMapper() {
+    lineMapAtoB = new ArrayList<LineGap>();
+    lineMapBtoA = new ArrayList<LineGap>();
+  }
+
+  int getLineA() {
+    return lineA;
+  }
+
+  int getLineB() {
+    return lineB;
+  }
+
+  void appendCommon(int numLines) {
+    lineA += numLines;
+    lineB += numLines;
+  }
+
+  void appendInsert(int numLines) {
+    int origLineB = lineB;
+    lineB += numLines;
+    int bAheadOfA = lineB - lineA;
+    lineMapAtoB.add(new LineGap(lineA, -1, bAheadOfA));
+    lineMapBtoA.add(new LineGap(origLineB, lineB - 1, -bAheadOfA));
+  }
+
+  void appendDelete(int numLines) {
+    int origLineA = lineA;
+    lineA += numLines;
+    int aAheadOfB = lineA - lineB;
+    lineMapAtoB.add(new LineGap(origLineA, lineA - 1, -aAheadOfB));
+    lineMapBtoA.add(new LineGap(lineB, -1, aAheadOfB));
+  }
+
+  /**
+   * Helper method to retrieve the line number on the other side.
+   *
+   * Given a line number on one side, performs a binary search in the lineMap
+   * to find the corresponding LineGap record.
+   *
+   * A LineGap records gap information from the start of an actual gap up to
+   * the start of the next gap. In the following example,
+   * lineMapAtoB will have LineGap: {start: 1, end: -1, delta: 3}
+   * (end set to -1 to represent a dummy gap of length zero. The binary search
+   * only looks at start so setting it to -1 has no effect here.)
+   * lineMapBtoA will have LineGap: {start: 1, end: 3, delta: -3}
+   * These LineGaps control lines between 1 and 5.
+   *
+   * The "delta" is computed as the number to add on our side to get the line
+   * number on the other side given a line after the actual gap, so the result
+   * will be (line + delta). All lines within the actual gap (1 to 3) are
+   * considered corresponding to the last line above the region on the other
+   * side, which is 0 in this case. For these lines, we do (end + delta).
+   *
+   * For example, to get the line number on the left corresponding to 1 on the
+   * right (lineOnOther(REVISION, 1)), the method looks up in lineMapBtoA,
+   * finds the "delta" to be -3, and returns 3 + (-3) = 0 since 1 falls in the
+   * actual gap. On the other hand, the line corresponding to 5 on the right
+   * will be 5 + (-3) = 2, since 5 is in the region after the gap (but still
+   * controlled by the current LineGap).
+   *
+   * PARENT REVISION
+   *   0   |   0
+   *   -   |   1 \                      \
+   *   -   |   2 | Actual insertion gap |
+   *   -   |   3 /                      | Region controlled by one LineGap
+   *   1   |   4   <- delta = 4 - 1 = 3 |
+   *   2   |   5                        /
+   *   -   |   6
+   *      ...
+   */
+  LineOnOtherInfo lineOnOther(Side mySide, int line) {
+    List<LineGap> lineGaps = mySide == Side.PARENT ? lineMapAtoB : lineMapBtoA;
+    // Create a dummy LineGap for the search.
+    int ret = Collections.binarySearch(lineGaps, new LineGap(line));
+    if (ret == -1) {
+      return new LineOnOtherInfo(line, true);
+    } else {
+      LineGap lookup = lineGaps.get(0 <= ret ? ret : -ret - 2);
+      int start = lookup.start;
+      int end = lookup.end;
+      int delta = lookup.delta;
+      if (start <= line && line <= end && end != -1) { // Line falls within gap
+        return new LineOnOtherInfo(end + delta, false);
+      } else { // Line after gap
+        return new LineOnOtherInfo(line + delta, true);
+      }
+    }
+  }
+
+  /**
+   * @field line The line number on the other side.
+   * @field aligned Whether the two lines are at the same height when displayed.
+   */
+  static class LineOnOtherInfo {
+    private int line;
+    private boolean aligned;
+
+    LineOnOtherInfo(int line, boolean aligned) {
+      this.line = line;
+      this.aligned = aligned;
+    }
+
+    int getLine() {
+      return line;
+    }
+
+    boolean isAligned() {
+      return aligned;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj instanceof LineOnOtherInfo) {
+        LineOnOtherInfo other = (LineOnOtherInfo) obj;
+        return aligned == other.aligned && line == other.line;
+      }
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return line + " " + aligned;
+    }
+  }
+
+  /**
+   * Helper class to record line gap info and assist in calculation of line
+   * number on the other side.
+   *
+   * For a mapping from A to B, where A is the side with an insertion:
+   * @field start The start line of the insertion in A.
+   * @field end The exclusive end line of the insertion in A.
+   * @field delta The offset added to A to get the line number in B calculated
+   *              from end.
+   */
+  private static class LineGap implements Comparable<LineGap> {
+    private final int start;
+    private final int end;
+    private final int delta;
+
+    private LineGap(int start, int end, int delta) {
+      this.start = start;
+      this.end = end;
+      this.delta = delta;
+    }
+
+    private LineGap(int line) {
+      this(line, 0, 0);
+    }
+
+    @Override
+    public int compareTo(LineGap o) {
+      return start - o.start;
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PaddingManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PaddingManager.java
new file mode 100644
index 0000000..e03efcf
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PaddingManager.java
@@ -0,0 +1,138 @@
+// 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.client.diff;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+
+import net.codemirror.lib.LineWidget;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages paddings for CommentBoxes. Each line that may need to be padded owns
+ * a PaddingManager instance, which maintains a padding widget whose height
+ * changes as necessary. PaddingManager calculates padding by taking the
+ * difference of the sum of CommentBox heights on the two sides.
+ *
+ * Note that in the case of an insertion or deletion gap, A PaddingManager
+ * can map to a list of managers on the other side. The padding needed is then
+ * calculated from the sum of all their heights.
+ *
+ * TODO: Let PaddingManager also take care of the paddings introduced by
+ * insertions and deletions.
+ */
+class PaddingManager {
+  private List<CommentBox> comments;
+  private LineWidgetElementPair padding;
+  private List<PaddingManager> others;
+
+  PaddingManager(LineWidgetElementPair padding) {
+    comments = new ArrayList<CommentBox>();
+    others = new ArrayList<PaddingManager>();
+    this.padding = padding;
+  }
+
+  static void link(PaddingManager a, PaddingManager b) {
+    if (!a.others.contains(b)) {
+      a.others.add(b);
+    }
+    if (!b.others.contains(a)) {
+      b.others.add(a);
+    }
+  }
+
+  private int getMyTotalHeight() {
+    int total = 0;
+    for (CommentBox box : comments) {
+      total += box.getOffsetHeight() + 5; // 5px for shadow margin
+    }
+    return total;
+  }
+
+  /**
+   * If this instance is on the insertion side, its counterpart on the other
+   * side will map to a group of PaddingManagers on this side, so we calculate
+   * the group's total height instead of an individual one's.
+   */
+  private int getGroupTotalHeight() {
+    if (others.size() > 1) {
+      return getMyTotalHeight();
+    } else {
+      return others.get(0).getOthersTotalHeight();
+    }
+  }
+
+  private int getOthersTotalHeight() {
+    int total = 0;
+    for (PaddingManager manager : others) {
+      total += manager.getMyTotalHeight();
+    }
+    return total;
+  }
+
+  private void setPaddingHeight(int height) {
+    padding.element.getStyle().setHeight(height, Unit.PX);
+    padding.widget.changed();
+  }
+
+  void resizePaddingWidget() {
+    if (others.isEmpty()) {
+      throw new IllegalStateException("resizePaddingWidget() called before linking");
+    }
+    int myHeight = getGroupTotalHeight();
+    int othersHeight = getOthersTotalHeight();
+    int paddingNeeded = othersHeight - myHeight;
+    if (paddingNeeded < 0) {
+      for (PaddingManager manager : others.get(0).others) {
+        manager.setPaddingHeight(0);
+      }
+      others.get(others.size() - 1).setPaddingHeight(-paddingNeeded);
+    } else {
+      setPaddingHeight(paddingNeeded);
+      for (PaddingManager other : others) {
+        other.setPaddingHeight(0);
+      }
+    }
+  }
+
+  /** This is unused now because threading info is ignored. */
+  int getReplyIndex(CommentBox box) {
+    return comments.indexOf(box) + 1;
+  }
+
+  int getCurrentCount() {
+    return comments.size();
+  }
+
+  void insert(CommentBox box, int index) {
+    comments.add(index, box);
+  }
+
+  void remove(CommentBox box) {
+    comments.remove(box);
+  }
+
+  static class LineWidgetElementPair {
+    private LineWidget widget;
+    private Element element;
+
+    LineWidgetElementPair(LineWidget w, Element e) {
+      widget = w;
+      element = e;
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
new file mode 100644
index 0000000..c78757b
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
@@ -0,0 +1,78 @@
+//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.client.diff;
+
+import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.HTMLPanel;
+
+import net.codemirror.lib.CodeMirror;
+
+/** An HtmlPanel for displaying a published comment */
+class PublishedBox extends CommentBox {
+  interface Binder extends UiBinder<HTMLPanel, PublishedBox> {}
+  private static UiBinder<HTMLPanel, CommentBox> uiBinder =
+      GWT.create(Binder.class);
+
+  private DraftBox replyBox;
+
+  PublishedBox(
+      SideBySide2 host,
+      CodeMirror cm,
+      PatchSet.Id id,
+      CommentInfo info,
+      CommentLinkProcessor linkProcessor) {
+    super(host, cm, uiBinder, id, info, linkProcessor, false);
+  }
+
+  void registerReplyBox(DraftBox box) {
+    replyBox = box;
+    box.registerReplyToBox(this);
+  }
+
+  void unregisterReplyBox() {
+    replyBox = null;
+  }
+
+  private void openReplyBox() {
+    replyBox.setOpen(true);
+    replyBox.setEdit(true);
+  }
+
+  @UiHandler("reply")
+  void onReply(ClickEvent e) {
+    if (replyBox == null) {
+      DraftBox box = getDiffView().addReply(getOriginal(), "", false);
+      registerReplyBox(box);
+    } else {
+      openReplyBox();
+    }
+  }
+
+  @UiHandler("replyDone")
+  void onReplyDone(ClickEvent e) {
+    if (replyBox == null) {
+      DraftBox box = getDiffView().addReply(getOriginal(), "Done", true);
+      registerReplyBox(box);
+    } else {
+      openReplyBox();
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
new file mode 100644
index 0000000..084aa16
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:d='urn:import:com.google.gerrit.client.diff'>
+  <ui:with field='res' type='com.google.gerrit.client.diff.CommentBoxResources' />
+  <g:HTMLPanel addStyleNames='{res.style.commentBox}'>
+    <d:CommentBoxHeader ui:field='header' />
+    <g:HTMLPanel addStyleNames='{res.style.contentPanel}'>
+      <g:HTML ui:field='contentPanelMessage' addStyleNames='{res.style.message}'></g:HTML>
+      <div class='{res.style.button}'>
+        <g:Button ui:field='reply'><ui:msg>Reply ...</ui:msg></g:Button>
+        <g:Button ui:field='replyDone'><ui:msg>Reply 'Done'</ui:msg></g:Button>
+      </div>
+    </g:HTMLPanel>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.java
new file mode 100644
index 0000000..750f466
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.java
@@ -0,0 +1,106 @@
+// 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.client.diff;
+
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ReviewInfo;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.patches.PatchUtil;
+import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.UIObject;
+
+class ReviewedPanel extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, ReviewedPanel> {}
+  private static UiBinder<HTMLPanel, ReviewedPanel> uiBinder =
+      GWT.create(Binder.class);
+
+  @UiField
+  Element fileName;
+
+  @UiField
+  CheckBox checkBox;
+
+  @UiField
+  Anchor nextLink;
+
+  private PatchSet.Id patchId;
+  private String fileId;
+  private ReviewedPanel other;
+
+  ReviewedPanel(PatchSet.Id id, String path, boolean bottom) {
+    initWidget(uiBinder.createAndBindUi(this));
+    patchId = id;
+    fileId = path;
+    if (bottom) {
+      UIObject.setVisible(fileName, false);
+    } else {
+      fileName.setInnerText(path);
+    }
+    nextLink.setHTML(PatchUtil.C.next() + Util.C.nextPatchLinkIcon());
+  }
+
+  static void link(ReviewedPanel top, ReviewedPanel bottom) {
+    top.other = bottom;
+    bottom.other = top;
+  }
+
+  void setReviewed(boolean reviewed) {
+    RestApi api = ChangeApi.revision(patchId)
+      .view("files")
+      .id(fileId)
+      .view("reviewed");
+    if (reviewed) {
+      api.put(CallbackGroup.<ReviewInfo>emptyCallback());
+    } else {
+      api.delete(CallbackGroup.<ReviewInfo>emptyCallback());
+    }
+    toggleReviewedBox(reviewed);
+  }
+
+  private void toggleReviewedBox(boolean reviewed) {
+    checkBox.setValue(reviewed);
+    CheckBox otherBox = other.checkBox;
+    if (otherBox.getValue() != reviewed) {
+      otherBox.setValue(reviewed);
+    }
+  }
+
+  boolean isReviewed() {
+    return checkBox.getValue();
+  }
+
+  @UiHandler("checkBox")
+  void onValueChange(ValueChangeEvent<Boolean> event) {
+    setReviewed(event.getValue());
+  }
+
+  // TODO: Implement this to go to the next file in the patchset.
+  void onNext(ClickEvent e) {
+    setReviewed(true);
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.ui.xml
new file mode 100644
index 0000000..552ef28
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ReviewedPanel.ui.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:d='urn:import:com.google.gerrit.client.diff'>
+  <ui:style>
+    .reviewedPanel, .table {
+      width: 100%;
+    }
+    .fileName {
+      font-size: larger;
+      font-weight: bold;
+    }
+    .reviewed {
+      width: 20%;
+      text-align: right;
+    }
+    .anchor {
+      color: inherit;
+      text-decoration: none;
+    }
+  </ui:style>
+  <g:HTMLPanel styleName='{style.reviewedPanel}'>
+    <table class='{style.table}'>
+    <tr>
+      <td ui:field='fileName' class='{style.fileName}' />
+      <td class='{style.reviewed}'>
+        <g:CheckBox ui:field='checkBox' />
+        <span><ui:msg>Reviewed &amp; </ui:msg></span>
+        <g:Anchor ui:field='nextLink' styleName='{style.anchor}' />
+      </td>
+    </tr>
+    </table>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
new file mode 100644
index 0000000..6a2a682
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
@@ -0,0 +1,870 @@
+// 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.client.diff;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.CommentApi;
+import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.diff.DiffInfo.Region;
+import com.google.gerrit.client.diff.DiffInfo.Span;
+import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
+import com.google.gerrit.client.diff.PaddingManager.LineWidgetElementPair;
+import com.google.gerrit.client.patches.PatchUtil;
+import com.google.gerrit.client.patches.SkippedLine;
+import com.google.gerrit.client.projects.ConfigInfoCache;
+import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.client.ui.Screen;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.globalkey.client.KeyCommand;
+import com.google.gwtexpui.globalkey.client.KeyCommandSet;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.LineClassWhere;
+import net.codemirror.lib.CodeMirror.LineHandle;
+import net.codemirror.lib.Configuration;
+import net.codemirror.lib.KeyMap;
+import net.codemirror.lib.LineCharacter;
+import net.codemirror.lib.LineWidget;
+import net.codemirror.lib.ModeInjector;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SideBySide2 extends Screen {
+  interface Binder extends UiBinder<HTMLPanel, SideBySide2> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+
+  private static final int HEADER_FOOTER = 60 + 15 * 2 + 16 + 26 * 2;
+  private static final JsArrayString EMPTY =
+      JavaScriptObject.createArray().cast();
+
+  @UiField(provided=true)
+  ReviewedPanel reviewedTop;
+
+  @UiField(provided=true)
+  ReviewedPanel reviewedBottom;
+
+  @UiField(provided=true)
+  DiffTable diffTable;
+
+  private final PatchSet.Id base;
+  private final PatchSet.Id revision;
+  private final String path;
+
+  private CodeMirror cmA;
+  private CodeMirror cmB;
+  private HandlerRegistration resizeHandler;
+  private JsArray<CommentInfo> published;
+  private JsArray<CommentInfo> drafts;
+  private List<CommentBox> initialBoxes;
+  private DiffInfo diff;
+  private LineMapper mapper;
+  private CommentLinkProcessor commentLinkProcessor;
+  private Map<String, PublishedBox> publishedMap;
+  private Map<LineHandle, CommentBox> lineActiveBoxMap;
+  private Map<LineHandle, PublishedBox> lineLastPublishedBoxMap;
+  private Map<LineHandle, PaddingManager> linePaddingManagerMap;
+  private List<SkippedLine> skips;
+  private int context;
+
+  private KeyCommandSet keysNavigation;
+  private KeyCommandSet keysAction;
+  private KeyCommandSet keysComment;
+  private KeyCommandSet keysOpenByEnter;
+  private List<HandlerRegistration> keyHandlers;
+
+  public SideBySide2(
+      PatchSet.Id base,
+      PatchSet.Id revision,
+      String path) {
+    this.base = base;
+    this.revision = revision;
+    this.path = path;
+    this.keyHandlers = new ArrayList<HandlerRegistration>(4);
+    // TODO: Re-implement necessary GlobalKey bindings.
+    addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
+    reviewedTop = new ReviewedPanel(revision, path, false);
+    reviewedBottom = new ReviewedPanel(revision, path, true);
+    ReviewedPanel.link(reviewedTop, reviewedBottom);
+    add(diffTable = new DiffTable());
+    add(uiBinder.createAndBindUi(this));
+  }
+
+  @Override
+  protected void onInitUI() {
+    super.onInitUI();
+    setHeaderVisible(false);
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+
+    CallbackGroup cmGroup = new CallbackGroup();
+    CodeMirror.initLibrary(cmGroup.add(CallbackGroup.<Void>emptyCallback()));
+    final CallbackGroup group = new CallbackGroup();
+    final AsyncCallback<Void> modeInjectorCb =
+        group.add(CallbackGroup.<Void>emptyCallback());
+
+    DiffApi.diff(revision, path)
+      .base(base)
+      .wholeFile()
+      .intraline()
+      .ignoreWhitespace(DiffApi.IgnoreWhitespace.NONE)
+      .get(cmGroup.addFinal(new GerritCallback<DiffInfo>() {
+        @Override
+        public void onSuccess(DiffInfo diffInfo) {
+          diff = diffInfo;
+          new ModeInjector()
+            .add(getContentType(diff.meta_a()))
+            .add(getContentType(diff.meta_b()))
+            .inject(modeInjectorCb);
+        }
+      }));
+    CommentApi.comments(revision,
+        group.add(new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+      @Override
+      public void onSuccess(NativeMap<JsArray<CommentInfo>> m) { published = m.get(path); }
+    }));
+    if (Gerrit.isSignedIn()) {
+      CommentApi.drafts(revision,
+          group.add(new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+        @Override
+        public void onSuccess(NativeMap<JsArray<CommentInfo>> m) { drafts = m.get(path); }
+      }));
+    } else {
+      drafts = JsArray.createArray().cast();
+    }
+    ConfigInfoCache.get(revision.getParentKey(), group.addFinal(
+        new ScreenLoadCallback<ConfigInfoCache.Entry>(SideBySide2.this) {
+          @Override
+          protected void preDisplay(ConfigInfoCache.Entry result) {
+            commentLinkProcessor = result.getCommentLinkProcessor();
+            setTheme(result.getTheme());
+
+            DiffInfo diffInfo = diff;
+            diff = null;
+            display(diffInfo);
+          }
+        }));
+  }
+
+  @Override
+  public void onShowView() {
+    super.onShowView();
+
+    if (cmA != null) {
+      cmA.refresh();
+    }
+    if (cmB != null) {
+      cmB.refresh();
+    }
+    Window.enableScrolling(false);
+    for (CommentBox box : initialBoxes) {
+      box.resizePaddingWidget();
+    }
+    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+      @Override
+      public void execute() {
+        if (cmA != null) {
+          cmA.setOption("viewportMargin", 10);
+        }
+        if (cmB != null) {
+          cmB.setOption("viewportMargin", 10);
+        }
+      }
+    });
+    (cmB != null ? cmB : cmA).focus();
+  }
+
+  @Override
+  protected void onUnload() {
+    super.onUnload();
+
+    removeKeyHandlerRegs();
+    if (resizeHandler != null) {
+      resizeHandler.removeHandler();
+      resizeHandler = null;
+    }
+    if (cmA != null) {
+      cmA.getWrapperElement().removeFromParent();
+      cmA = null;
+    }
+    if (cmB != null) {
+      cmB.getWrapperElement().removeFromParent();
+      cmB = null;
+    }
+    Window.enableScrolling(true);
+  }
+
+  private void removeKeyHandlerRegs() {
+    for (HandlerRegistration h : keyHandlers) {
+      h.removeHandler();
+    }
+    keyHandlers.clear();
+  }
+
+  private void registerCmEvents(CodeMirror cm) {
+    cm.on("cursorActivity", updateActiveLine(cm));
+    cm.on("scroll", doScroll(cm));
+    // TODO: Prevent right click from updating the cursor.
+    cm.addKeyMap(KeyMap.create().on("'j'", moveCursorDown(cm, 1)));
+    cm.addKeyMap(KeyMap.create().on("'k'", moveCursorDown(cm, -1)));
+    cm.addKeyMap(KeyMap.create().on("'u'", upToChange()));
+    cm.addKeyMap(KeyMap.create().on("'o'", toggleOpenBox(cm)));
+    cm.addKeyMap(KeyMap.create().on("Enter", toggleOpenBox(cm)));
+    CodeMirror.defineVimEx("up", "u", upToChange());
+    CodeMirror.defineVimEx("mark", "m", toggleReviewed());
+    if (Gerrit.isSignedIn()) {
+      cm.addKeyMap(KeyMap.create().on("'c'", insertNewDraft(cm)));
+    }
+    /**
+     * TODO: Work on a better way for customizing keybindings and remove
+     * temporary navigation hacks.
+     */
+    for (String s : new String[]{"C", "J", "K", "O", "R", "U", "Ctrl-C",
+        "Ctrl-F", "Enter"}) {
+      CodeMirror.disableUnwantedKey("vim", s);
+    }
+  }
+
+  @Override
+  public void registerKeys() {
+    super.registerKeys();
+
+    keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
+    keysNavigation.add(new NoOpKeyCommand(0, 'u', PatchUtil.C.upToChange()));
+    keysNavigation.add(new NoOpKeyCommand(0, 'j', PatchUtil.C.lineNext()));
+    keysNavigation.add(new NoOpKeyCommand(0, 'k', PatchUtil.C.linePrev()));
+
+    keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
+    keysAction.add(new NoOpKeyCommand(0, 'o', PatchUtil.C.expandComment()));
+    keysAction.add(new NoOpKeyCommand(0, 'm', PatchUtil.C.toggleReviewed()));
+
+    keysOpenByEnter = new KeyCommandSet(Gerrit.C.sectionNavigation());
+    keysOpenByEnter.add(new NoOpKeyCommand(0, KeyCodes.KEY_ENTER,
+        PatchUtil.C.expandComment()));
+
+    if (Gerrit.isSignedIn()) {
+      keysAction.add(new NoOpKeyCommand(0, 'c', PatchUtil.C.commentInsert()));
+      keysComment = new KeyCommandSet(PatchUtil.C.commentEditorSet());
+      keysComment.add(new NoOpKeyCommand(KeyCommand.M_CTRL, 's',
+          PatchUtil.C.commentSaveDraft()));
+      keysComment.add(new NoOpKeyCommand(0, KeyCodes.KEY_ESCAPE,
+          PatchUtil.C.commentCancelEdit()));
+    } else {
+      keysComment = null;
+    }
+    removeKeyHandlerRegs();
+    keyHandlers.add(GlobalKey.add(this, keysNavigation));
+    keyHandlers.add(GlobalKey.add(this, keysAction));
+    keyHandlers.add(GlobalKey.add(this, keysOpenByEnter));
+    if (keysComment != null) {
+      keyHandlers.add(GlobalKey.add(this, keysComment));
+    }
+  }
+
+  private void display(DiffInfo diffInfo) {
+    cmA = displaySide(diffInfo.meta_a(), diffInfo.text_a(), diffTable.cmA);
+    cmB = displaySide(diffInfo.meta_b(), diffInfo.text_b(), diffTable.cmB);
+    skips = new ArrayList<SkippedLine>();
+    render(diffInfo);
+    initialBoxes = new ArrayList<CommentBox>();
+    lineActiveBoxMap = new HashMap<LineHandle, CommentBox>();
+    lineLastPublishedBoxMap = new HashMap<LineHandle, PublishedBox>();
+    linePaddingManagerMap = new HashMap<LineHandle, PaddingManager>();
+    if (published != null) {
+      publishedMap = new HashMap<String, PublishedBox>(published.length());
+      renderPublished();
+    }
+    if (drafts != null) {
+      renderDrafts();
+    }
+    renderSkips();
+    published = null;
+    drafts = null;
+    skips = null;
+    registerCmEvents(cmA);
+    registerCmEvents(cmB);
+    // TODO: Probably need horizontal resize
+    resizeHandler = Window.addResizeHandler(new ResizeHandler() {
+      @Override
+      public void onResize(ResizeEvent event) {
+        if (cmA != null) {
+          cmA.setHeight(event.getHeight() - HEADER_FOOTER);
+          cmA.refresh();
+        }
+        if (cmB != null) {
+          cmB.setHeight(event.getHeight() - HEADER_FOOTER);
+          cmB.refresh();
+        }
+      }
+    });
+  }
+
+  private CodeMirror displaySide(DiffInfo.FileMeta meta, String contents,
+      Element ele) {
+    if (meta == null) {
+      contents = "";
+    }
+    Configuration cfg = Configuration.create()
+      .set("readOnly", true)
+      .set("lineNumbers", true)
+      .set("tabSize", 2)
+      .set("mode", getContentType(meta))
+      .set("lineWrapping", true)
+      .set("styleSelectedText", true)
+      .set("showTrailingSpace", true)
+      .set("keyMap", "vim")
+      .set("value", contents)
+      /**
+       * Without this, CM won't put line widgets too far down in the right spot,
+       * and padding widgets will be informed of wrong offset height. Reset to
+       * 10 (default) after initial rendering.
+       */
+      .setInfinity("viewportMargin");
+    final CodeMirror cm = CodeMirror.create(ele, cfg);
+    cm.setHeight(Window.getClientHeight() - HEADER_FOOTER);
+    return cm;
+  }
+
+  private void render(DiffInfo diff) {
+    AccountDiffPreference pref = Gerrit.getAccountDiffPreference();
+    context = pref != null
+        ? pref.getContext()
+        : AccountDiffPreference.DEFAULT_CONTEXT;
+    JsArray<Region> regions = diff.content();
+    String diffColor = diff.meta_a() == null || diff.meta_b() == null
+        ? DiffTable.style.intralineBg()
+        : DiffTable.style.diff();
+    mapper = new LineMapper();
+    for (int i = 0; i < regions.length(); i++) {
+      Region current = regions.get(i);
+      int origLineA = mapper.getLineA();
+      int origLineB = mapper.getLineB();
+      if (current.ab() != null) { // Common
+        int length = current.ab().length();
+        mapper.appendCommon(length);
+        if (i == 0 && length > context) {
+          skips.add(new SkippedLine(0, 0, length - context));
+        } else if (i == regions.length() - 1 && length > context) {
+          skips.add(new SkippedLine(origLineA + context, origLineB + context,
+              length - context));
+        } else if (length > 2 * context) {
+          skips.add(new SkippedLine(origLineA + context, origLineB + context,
+              length - 2 * context));
+        }
+      } else { // Insert, Delete or Edit
+        JsArrayString currentA = current.a() == null ? EMPTY : current.a();
+        JsArrayString currentB = current.b() == null ? EMPTY : current.b();
+        int aLength = currentA.length();
+        int bLength = currentB.length();
+        String color = currentA == EMPTY || currentB == EMPTY
+            ? diffColor
+            : DiffTable.style.intralineBg();
+        colorLines(cmA, color, origLineA, aLength);
+        colorLines(cmB, color, origLineB, bLength);
+        mapper.appendCommon(Math.min(aLength, bLength));
+        if (aLength < bLength) { // Edit with insertion
+          int insertCnt = bLength - aLength;
+          insertEmptyLines(cmA, mapper.getLineA(), insertCnt);
+          mapper.appendInsert(insertCnt);
+        } else if (aLength > bLength) { // Edit with deletion
+          int deleteCnt = aLength - bLength;
+          insertEmptyLines(cmB, mapper.getLineB(), deleteCnt);
+          mapper.appendDelete(deleteCnt);
+        }
+        markEdit(cmA, currentA, current.edit_a(), origLineA);
+        markEdit(cmB, currentB, current.edit_b(), origLineB);
+      }
+    }
+  }
+
+  private DraftBox addNewDraft(CodeMirror cm, int line) {
+    Side side = cm == cmA ? Side.PARENT : Side.REVISION;
+    CommentInfo info = CommentInfo.create(
+        path,
+        side,
+        line + 1,
+        null,
+        null);
+    return addDraftBox(info, false);
+  }
+
+  DraftBox addReply(CommentInfo replyTo, String initMessage, boolean doSave) {
+    Side side = replyTo.side();
+    int line = replyTo.line();
+    CommentInfo info = CommentInfo.create(
+        path,
+        side,
+        line,
+        replyTo.id(),
+        initMessage);
+    return addDraftBox(info, doSave);
+  }
+
+  private DraftBox addDraftBox(CommentInfo info, boolean doSave) {
+    CodeMirror cm = getCmFromSide(info.side());
+    DraftBox box = new DraftBox(this, cm, revision, info, commentLinkProcessor,
+        true, doSave);
+    addCommentBox(info, box);
+    if (!doSave) {
+      box.setEdit(true);
+    }
+    LineHandle handle = cm.getLineHandle(info.line() - 1);
+    lineActiveBoxMap.put(handle, box);
+    return box;
+  }
+
+  CommentBox addCommentBox(CommentInfo info, CommentBox box) {
+    diffTable.add(box);
+    Side mySide = info.side();
+    CodeMirror cm = mySide == Side.PARENT ? cmA : cmB;
+    CodeMirror other = otherCm(cm);
+    int line = info.line() - 1; // CommentInfo is 1-based, but CM is 0-based
+    LineHandle handle = cm.getLineHandle(line);
+    PaddingManager manager;
+    if (linePaddingManagerMap.containsKey(handle)) {
+      manager = linePaddingManagerMap.get(handle);
+    } else {
+      // Estimated height at 28px, fixed by deferring after display
+      manager = new PaddingManager(
+          addPaddingWidget(cm, DiffTable.style.padding(), line, 28, Unit.PX, 0));
+      linePaddingManagerMap.put(handle, manager);
+    }
+    int lineToPad = mapper.lineOnOther(mySide, line).getLine();
+    LineHandle otherHandle = other.getLineHandle(lineToPad);
+    if (linePaddingManagerMap.containsKey(otherHandle)) {
+      PaddingManager.link(manager, linePaddingManagerMap.get(otherHandle));
+    } else {
+      PaddingManager otherManager = new PaddingManager(
+          addPaddingWidget(other, DiffTable.style.padding(), lineToPad, 28, Unit.PX, 0));
+      linePaddingManagerMap.put(otherHandle, otherManager);
+      PaddingManager.link(manager, otherManager);
+    }
+    int index = manager.getCurrentCount();
+    manager.insert(box, index);
+    Configuration config = Configuration.create()
+      .set("coverGutter", true)
+      .set("insertAt", index);
+    LineWidget boxWidget = cm.addLineWidget(line, box.getElement(), config);
+    box.setPaddingManager(manager);
+    box.setSelfWidget(boxWidget);
+    return box;
+  }
+
+  void removeDraft(Side side, int line) {
+    LineHandle handle = getCmFromSide(side).getLineHandle(line);
+    lineActiveBoxMap.remove(handle);
+    if (lineLastPublishedBoxMap.containsKey(handle)) {
+      lineActiveBoxMap.put(handle, lineLastPublishedBoxMap.get(handle));
+    }
+  }
+
+  private List<CommentInfo> sortComment(JsArray<CommentInfo> unsorted) {
+    List<CommentInfo> sorted = new ArrayList<CommentInfo>();
+    for (int i = 0; i < unsorted.length(); i++) {
+      sorted.add(unsorted.get(i));
+    }
+    Collections.sort(sorted, new Comparator<CommentInfo>() {
+      @Override
+      public int compare(CommentInfo o1, CommentInfo o2) {
+        return o1.updated().compareTo(o2.updated());
+      }
+    });
+    return sorted;
+  }
+
+  private void renderPublished() {
+    List<CommentInfo> sorted = sortComment(published);
+    for (CommentInfo info : sorted) {
+      CodeMirror cm = getCmFromSide(info.side());
+      PublishedBox box =
+          new PublishedBox(this, cm, revision, info, commentLinkProcessor);
+      box.setOpen(false);
+      initialBoxes.add(box);
+      publishedMap.put(info.id(), box);
+      int line = info.line() - 1;
+      LineHandle handle = cm.getLineHandle(line);
+      lineLastPublishedBoxMap.put(handle, box);
+      lineActiveBoxMap.put(handle, box);
+      addCommentBox(info, box);
+    }
+  }
+
+  private void renderDrafts() {
+    List<CommentInfo> sorted = sortComment(drafts);
+    for (CommentInfo info : sorted) {
+      DraftBox box =
+          new DraftBox(this, getCmFromSide(info.side()), revision, info,
+              commentLinkProcessor, false, false);
+      box.setOpen(false);
+      box.setEdit(false);
+      initialBoxes.add(box);
+      if (published != null) {
+        PublishedBox replyToBox = publishedMap.get(info.in_reply_to());
+        if (replyToBox != null) {
+          replyToBox.registerReplyBox(box);
+        }
+      }
+      lineActiveBoxMap.put(
+          getCmFromSide(info.side()).getLineHandle(info.line() - 1), box);
+      addCommentBox(info, box);
+    }
+  }
+
+  private void renderSkips() {
+    if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
+      skips.clear();
+      return;
+    }
+
+    for (CommentBox box : initialBoxes) {
+      List<SkippedLine> temp = new ArrayList<SkippedLine>();
+      for (SkippedLine skip : skips) {
+        CommentInfo info = box.getOriginal();
+        int startLine = info.side() == Side.PARENT
+            ? skip.getStartA()
+            : skip.getStartB();
+        int boxLine = info.line();
+        int deltaBefore = boxLine - startLine;
+        int deltaAfter = startLine + skip.getSize() - boxLine;
+        if (deltaBefore < -context || deltaAfter < -context) {
+          checkAndAddSkip(temp, skip);
+        } else if (deltaBefore > context && deltaAfter > context) {
+          SkippedLine before = new SkippedLine(
+              skip.getStartA(), skip.getStartB(),
+              skip.getSize() - deltaAfter - context);
+          skip.incrementStart(deltaBefore + context);
+          checkAndAddSkip(temp, before);
+          checkAndAddSkip(temp, skip);
+        } else if (deltaAfter > context) {
+          skip.incrementStart(deltaBefore + context);
+          checkAndAddSkip(temp, skip);
+        } else if (deltaBefore > context) {
+          skip.reduceSize(deltaAfter + context);
+          checkAndAddSkip(temp, skip);
+        }
+      }
+      skips = temp;
+    }
+    for (SkippedLine skip : skips) {
+      SkipBar barA = renderSkipHelper(cmA, skip);
+      SkipBar barB = renderSkipHelper(cmB, skip);
+      SkipBar.link(barA, barB);
+    }
+  }
+
+  private void checkAndAddSkip(List<SkippedLine> list,
+      SkippedLine toAdd) {
+    if (toAdd.getSize() > 1) {
+      list.add(toAdd);
+    }
+  }
+
+  private SkipBar renderSkipHelper(CodeMirror cm, SkippedLine skip) {
+    int size = skip.getSize();
+    int markStart = cm == cmA ? skip.getStartA() - 1 : skip.getStartB() - 1;
+    int markEnd = markStart + size;
+    SkipBar bar = new SkipBar(cm);
+    diffTable.add(bar);
+    /**
+     * Due to CodeMirror limitation, there's no way to make the first
+     * line disappear completely, and CodeMirror doesn't like manually
+     * setting the display of a line to "none". The workaround here uses
+     * inline widget for the first line and regular line widgets for others.
+     */
+    Configuration markerConfig;
+    if (markStart == -1) {
+      markerConfig = Configuration.create()
+        .set("inclusiveLeft", true)
+        .set("inclusiveRight", true)
+        .set("replacedWith", bar.getElement());
+      cm.addLineClass(0, LineClassWhere.WRAP, DiffTable.style.hideNumber());
+    } else {
+      markerConfig = Configuration.create().set("collapsed", true);
+      Configuration config = Configuration.create().set("coverGutter", true);
+      bar.setWidget(cm.addLineWidget(markStart, bar.getElement(), config));
+    }
+    bar.setMarker(cm.markText(CodeMirror.pos(markStart),
+        CodeMirror.pos(markEnd), markerConfig), size);
+    return bar;
+  }
+
+  private CodeMirror otherCm(CodeMirror me) {
+    return me == cmA ? cmB : cmA;
+  }
+
+  private CodeMirror getCmFromSide(Side side) {
+    return side == Side.PARENT ? cmA : cmB;
+  }
+
+  private void markEdit(CodeMirror cm, JsArrayString lines,
+      JsArray<Span> edits, int startLine) {
+    if (edits == null) {
+      return;
+    }
+    EditIterator iter = new EditIterator(lines, startLine);
+    Configuration intralineBgOpt = Configuration.create()
+        .set("className", DiffTable.style.intralineBg())
+        .set("readOnly", true);
+    Configuration diffOpt = Configuration.create()
+        .set("className", DiffTable.style.diff())
+        .set("readOnly", true);
+    LineCharacter last = CodeMirror.pos(0, 0);
+    for (int i = 0; i < edits.length(); i++) {
+      Span span = edits.get(i);
+      LineCharacter from = iter.advance(span.skip());
+      LineCharacter to = iter.advance(span.mark());
+      int fromLine = from.getLine();
+      if (last.getLine() == fromLine) {
+        cm.markText(last, from, intralineBgOpt);
+      } else {
+        cm.markText(CodeMirror.pos(fromLine, 0), from, intralineBgOpt);
+      }
+      cm.markText(from, to, diffOpt);
+      last = to;
+      for (int line = fromLine; line < to.getLine(); line++) {
+        cm.addLineClass(line, LineClassWhere.BACKGROUND,
+            DiffTable.style.diff());
+      }
+    }
+  }
+
+  private void colorLines(CodeMirror cm, String color, int line, int cnt) {
+    for (int i = 0; i < cnt; i++) {
+      cm.addLineClass(line + i, LineClassWhere.WRAP, color);
+    }
+  }
+
+  private void insertEmptyLines(CodeMirror cm, int nextLine, int cnt) {
+    // -1 to compensate for the line we went past when this method is called.
+    addPaddingWidget(cm, DiffTable.style.padding(), nextLine - 1,
+        1.1 * cnt, Unit.EM, null);
+  }
+
+  private LineWidgetElementPair addPaddingWidget(CodeMirror cm, String style,
+      int line, double height, Unit unit, Integer index) {
+    Element div = DOM.createDiv();
+    div.setClassName(style);
+    div.getStyle().setHeight(height, unit);
+    Configuration config = Configuration.create()
+        .set("coverGutter", true)
+        .set("above", line == -1);
+    if (index != null) {
+      config = config.set("insertAt", index);
+    }
+    LineWidget widget = cm.addLineWidget(line == -1 ? 0 : line, div, config);
+    return new LineWidgetElementPair(widget, div);
+  }
+
+  private Runnable doScroll(final CodeMirror cm) {
+    final CodeMirror other = otherCm(cm);
+    return new Runnable() {
+      public void run() {
+        // Hack to prevent feedback loop, Chrome seems fine but Firefox chokes.
+        if (cm.isScrollSetByOther()) {
+          return;
+        }
+        other.scrollToY(cm.getScrollInfo().getTop());
+        other.setScrollSetByOther(true);
+        Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
+          @Override
+          public boolean execute() {
+            other.setScrollSetByOther(false);
+            return false;
+          }
+        }, 0);
+      }
+    };
+  }
+
+  private Runnable updateActiveLine(final CodeMirror cm) {
+    final CodeMirror other = otherCm(cm);
+    return new Runnable() {
+      public void run() {
+        if (cm.hasActiveLine()) {
+          cm.removeLineClass(cm.getActiveLine(),
+              LineClassWhere.WRAP, DiffTable.style.activeLine());
+          cm.removeLineClass(cm.getActiveLine(),
+              LineClassWhere.BACKGROUND, DiffTable.style.activeLineBg());
+        }
+        if (other.hasActiveLine()) {
+          other.removeLineClass(other.getActiveLine(),
+              LineClassWhere.WRAP, DiffTable.style.activeLine());
+          other.removeLineClass(other.getActiveLine(),
+              LineClassWhere.BACKGROUND, DiffTable.style.activeLineBg());
+        }
+        LineHandle handle = cm.getLineHandleVisualStart(cm.getCursor().getLine());
+        int line = cm.getLineNumber(handle);
+        cm.setActiveLine(handle);
+        if (cm.somethingSelected()) {
+          return;
+        }
+        cm.addLineClass(line, LineClassWhere.WRAP, DiffTable.style.activeLine());
+        cm.addLineClass(line, LineClassWhere.BACKGROUND, DiffTable.style.activeLineBg());
+        LineOnOtherInfo info =
+            mapper.lineOnOther(cm == cmA ? Side.PARENT : Side.REVISION, line);
+        int oLine = info.getLine();
+        if (info.isAligned()) {
+          other.setActiveLine(other.getLineHandle(oLine));
+          other.addLineClass(oLine, LineClassWhere.WRAP,
+              DiffTable.style.activeLine());
+          other.addLineClass(oLine, LineClassWhere.BACKGROUND,
+              DiffTable.style.activeLineBg());
+        }
+      }
+    };
+  }
+
+  private Runnable insertNewDraft(final CodeMirror cm) {
+    return new Runnable() {
+      public void run() {
+        LineHandle handle = cm.getActiveLine();
+        int line = cm.getLineNumber(handle);
+        CommentBox box = lineActiveBoxMap.get(handle);
+        if (box == null) {
+          lineActiveBoxMap.put(handle, addNewDraft(cm, line));
+        } else if (box.isDraft()) {
+          ((DraftBox) lineActiveBoxMap.get(handle)).setEdit(true);
+        } else {
+          ((PublishedBox) box).onReply(null);
+        }
+      }
+    };
+  }
+
+  private Runnable toggleOpenBox(final CodeMirror cm) {
+    return new Runnable() {
+      public void run() {
+        CommentBox box = lineActiveBoxMap.get(cm.getActiveLine());
+        if (box != null) {
+          box.setOpen(!box.isOpen());
+        }
+      }
+    };
+  }
+
+  private Runnable moveCursorDown(final CodeMirror cm, final int numLines) {
+    return new Runnable() {
+      public void run() {
+        cm.moveCursorDown(numLines);
+      }
+    };
+  }
+
+  private Runnable upToChange() {
+    return new Runnable() {
+      public void run() {
+        Gerrit.display(PageLinks.toChange2(
+          revision.getParentKey(),
+          String.valueOf(revision.get())));
+      }
+    };
+  }
+
+  private Runnable toggleReviewed() {
+    return new Runnable() {
+     public void run() {
+       reviewedTop.setReviewed(!reviewedTop.isReviewed());
+     }
+    };
+  }
+
+  private static String getContentType(DiffInfo.FileMeta meta) {
+    return meta != null && meta.content_type() != null
+        ? ModeInjector.getContentType(meta.content_type())
+        : null;
+  }
+
+  static class EditIterator {
+    private final JsArrayString lines;
+    private final int startLine;
+    private int currLineIndex;
+    private int currLineOffset;
+
+    EditIterator(JsArrayString lineArray, int start) {
+      lines = lineArray;
+      startLine = start;
+    }
+
+    LineCharacter advance(int numOfChar) {
+      while (currLineIndex < lines.length()) {
+        int lengthWithNewline =
+            lines.get(currLineIndex).length() - currLineOffset + 1;
+        if (numOfChar < lengthWithNewline) {
+          LineCharacter at = LineCharacter.create(
+              startLine + currLineIndex,
+              numOfChar + currLineOffset);
+          currLineOffset += numOfChar;
+          return at;
+        }
+        numOfChar -= lengthWithNewline;
+        advanceLine();
+        if (numOfChar == 0) {
+          return LineCharacter.create(startLine + currLineIndex, 0);
+        }
+      }
+      throw new IllegalStateException("EditIterator index out of bound");
+    }
+
+    private void advanceLine() {
+      currLineIndex++;
+      currLineOffset = 0;
+    }
+  }
+
+  private static class NoOpKeyCommand extends KeyCommand {
+    private NoOpKeyCommand(int mask, int key, String help) {
+      super(mask, key, help);
+    }
+
+    @Override
+    public void onKeyPress(KeyPressEvent event) {
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
new file mode 100644
index 0000000..b1d386e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:d='urn:import:com.google.gerrit.client.diff'>
+  <g:HTMLPanel>
+    <d:ReviewedPanel ui:field='reviewedTop'/>
+    <d:DiffTable ui:field='diffTable' />
+    <d:ReviewedPanel ui:field='reviewedBottom' />
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
new file mode 100644
index 0000000..9274402
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
@@ -0,0 +1,183 @@
+//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.client.diff;
+
+import com.google.gerrit.client.patches.PatchUtil;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.LineClassWhere;
+import net.codemirror.lib.Configuration;
+import net.codemirror.lib.LineWidget;
+import net.codemirror.lib.TextMarker;
+import net.codemirror.lib.TextMarker.FromTo;
+
+/** The Widget that handles expanding of skipped lines */
+class SkipBar extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, SkipBar> {}
+  private static Binder uiBinder = GWT.create(Binder.class);
+  private static final int NUM_ROWS_TO_EXPAND = 10;
+  private static final int UP_DOWN_THRESHOLD = 30;
+  private static final Configuration COLLAPSED =
+      Configuration.create().set("collapsed", true);
+
+  private LineWidget widget;
+
+  interface SkipBarStyle extends CssResource {
+    String noExpand();
+  }
+
+  @UiField(provided=true)
+  Anchor skipNum;
+
+  @UiField(provided=true)
+  Anchor upArrow;
+
+  @UiField(provided=true)
+  Anchor downArrow;
+
+  @UiField
+  SkipBarStyle style;
+
+  private TextMarker marker;
+  private SkipBar otherBar;
+  private CodeMirror cm;
+  private int numSkipLines;
+
+  SkipBar(CodeMirror cmInstance) {
+    cm = cmInstance;
+    skipNum = new Anchor(true);
+    upArrow = new Anchor(true);
+    downArrow = new Anchor(true);
+    initWidget(uiBinder.createAndBindUi(this));
+    addDomHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        cm.focus();
+      }
+    }, ClickEvent.getType());
+  }
+
+  void setWidget(LineWidget lineWidget) {
+    widget = lineWidget;
+    Scheduler.get().scheduleDeferred(new ScheduledCommand(){
+      @Override
+      public void execute() {
+        getElement().getStyle().setPaddingLeft(
+            cm.getGutterElement().getOffsetWidth(), Unit.PX);
+      }
+    });
+  }
+
+  void setMarker(TextMarker marker, int length) {
+    this.marker = marker;
+    numSkipLines = length;
+    skipNum.setText(Integer.toString(length));
+    if (checkAndUpdateArrows()) {
+      upArrow.setHTML(PatchUtil.M.expandBefore(NUM_ROWS_TO_EXPAND));
+      downArrow.setHTML(PatchUtil.M.expandAfter(NUM_ROWS_TO_EXPAND));
+    }
+  }
+
+  static void link(SkipBar barA, SkipBar barB) {
+    barA.otherBar = barB;
+    barB.otherBar = barA;
+  }
+
+  private void updateSkipNum() {
+    numSkipLines -= NUM_ROWS_TO_EXPAND;
+    skipNum.setText(String.valueOf(numSkipLines));
+    checkAndUpdateArrows();
+  }
+
+  private boolean checkAndUpdateArrows() {
+    if (numSkipLines <= UP_DOWN_THRESHOLD) {
+      addStyleName(style.noExpand());
+      return false;
+    }
+    return true;
+  }
+
+  private void clearMarkerAndWidget() {
+    marker.clear();
+    if (widget != null) {
+      widget.clear();
+    } else {
+      cm.removeLineClass(0, LineClassWhere.WRAP, DiffTable.style.hideNumber());
+    }
+  }
+
+  private void expandAll() {
+    clearMarkerAndWidget();
+    removeFromParent();
+    cm.focus();
+  }
+
+  private void expandBefore() {
+    FromTo fromTo = marker.find();
+    int oldStart = fromTo.getFrom().getLine();
+    int newStart = oldStart + NUM_ROWS_TO_EXPAND;
+    int end = fromTo.getTo().getLine();
+    clearMarkerAndWidget();
+    marker = cm.markText(CodeMirror.pos(newStart), CodeMirror.pos(end), COLLAPSED);
+    Configuration config = Configuration.create().set("coverGutter", true);
+    LineWidget newWidget = cm.addLineWidget(newStart, getElement(), config);
+    setWidget(newWidget);
+    updateSkipNum();
+    cm.focus();
+  }
+
+  private void expandAfter() {
+    FromTo fromTo = marker.find();
+    int oldEnd = fromTo.getTo().getLine();
+    int newEnd = oldEnd - NUM_ROWS_TO_EXPAND;
+    marker.clear();
+    marker = cm.markText(CodeMirror.pos(fromTo.getFrom().getLine()),
+        CodeMirror.pos(newEnd),
+        COLLAPSED);
+    updateSkipNum();
+    cm.focus();
+  }
+
+  @UiHandler("skipNum")
+  void onExpandAll(ClickEvent e) {
+    otherBar.expandAll();
+    expandAll();
+  }
+
+  @UiHandler("upArrow")
+  void onExpandBefore(ClickEvent e) {
+    otherBar.expandBefore();
+    expandBefore();
+  }
+
+  @UiHandler("downArrow")
+  void onExpandAfter(ClickEvent e) {
+    otherBar.expandAfter();
+    expandAfter();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.ui.xml
new file mode 100644
index 0000000..9a0b6a6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.ui.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:style type='com.google.gerrit.client.diff.SkipBar.SkipBarStyle'>
+    .skipBar {
+      background-color: #def;
+      height: 1.3em;
+      overflow: hidden;
+    }
+    .text {
+      display: table;
+      margin: 0 auto;
+      color: #777;
+      font-style: italic;
+      overflow: hidden;
+    }
+    .anchor {
+      color: inherit;
+      text-decoration: none;
+    }
+    .noExpand .arrow {
+      display: none;
+    }
+  </ui:style>
+  <g:HTMLPanel addStyleNames='{style.skipBar}'>
+  <div class='{style.text}'>
+    <ui:msg>
+      <g:Anchor ui:field='upArrow' addStyleNames='{style.arrow} {style.anchor}' />
+      <span><ui:msg>... skipped </ui:msg></span>
+      <g:Anchor ui:field='skipNum' addStyleNames='{style.anchor}' />
+      <span><ui:msg> common lines ...</ui:msg></span>
+      <g:Anchor ui:field='downArrow' addStyleNames=' {style.arrow} {style.anchor}' />
+    </ui:msg>
+  </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index 5f16af6..2e471c4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -39,7 +39,6 @@
 /** Override various GWT defaults */
 .gerritTopMenu {
   font-size: 9pt;
-  padding-top: 5px;
   padding-left: 5px;
   padding-right: 5px;
   background: transparent;
@@ -62,12 +61,11 @@
   text-decoration: underline;
 }
 
-.version,
-.keyhelp {
+#gerrit_btmmenu {
+  clear: both;
   color: #a0adcc;
-  right: 0;
-  padding-right: 10px;
   text-align: right;
+  padding-right: 10px;
 }
 
 .version a,
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
index 200562a..4817ffd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
@@ -386,7 +386,7 @@
           }
         }));
     PatchUtil.DETAIL_SVC.patchScript(patchKey, idSideA, idSideB,
-        settingsPanel.getValue(), cb.addGwtjsonrpc(
+        settingsPanel.getValue(), cb.addFinal(
             new ScreenLoadCallback<PatchScript>(this) {
               @Override
               protected void preDisplay(final PatchScript result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
index 8e76e47..9795a64 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
@@ -314,7 +314,7 @@
     if (0 <= sel) {
       return Short.parseShort(context.getValue(sel));
     }
-    return (short) getValue().getContext();
+    return getValue().getContext();
   }
 
   private void setContext(int ctx) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
new file mode 100644
index 0000000..3f79afe
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/BranchInfo.java
@@ -0,0 +1,33 @@
+// 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.client.projects;
+
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class BranchInfo extends JavaScriptObject {
+  public final String getShortName() {
+    return ref().startsWith(Branch.R_HEADS)
+        ? ref().substring(Branch.R_HEADS.length())
+        : ref();
+  }
+
+  public final native String ref() /*-{ return this.ref; }-*/;
+  public final native String revision() /*-{ return this.revision; }-*/;
+  public final native boolean canDelete() /*-{ return this['can_delete'] ? true : false; }-*/;
+
+  protected BranchInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfoCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfoCache.java
index 16406f4..4645768 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfoCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfoCache.java
@@ -14,7 +14,10 @@
 
 package com.google.gerrit.client.projects;
 
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -24,7 +27,8 @@
 
 /** Cache of {@link ConfigInfo} objects by project name. */
 public class ConfigInfoCache {
-  private static final int LIMIT = 25;
+  private static final int PROJECT_LIMIT = 25;
+  private static final int CHANGE_LIMIT = 100;
   private static final ConfigInfoCache instance =
       GWT.create(ConfigInfoCache.class);
 
@@ -49,36 +53,74 @@
   }
 
   public static void get(Project.NameKey name, AsyncCallback<Entry> cb) {
-    instance.getImpl(name, cb);
+    instance.getImpl(name.get(), cb);
+  }
+
+  public static void get(Change.Id changeId, AsyncCallback<Entry> cb) {
+    instance.getImpl(changeId.get(), cb);
+  }
+
+  public static void add(ChangeInfo info) {
+    instance.changeToProject.put(info.legacy_id().get(), info.project());
   }
 
   private final LinkedHashMap<String, Entry> cache;
+  private final LinkedHashMap<Integer, String> changeToProject;
 
   protected ConfigInfoCache() {
-    cache = new LinkedHashMap<String, Entry>(LIMIT) {
+    cache = new LinkedHashMap<String, Entry>(PROJECT_LIMIT) {
       private static final long serialVersionUID = 1L;
 
       @Override
       protected boolean removeEldestEntry(
           Map.Entry<String, ConfigInfoCache.Entry> e) {
-        return size() > LIMIT;
+        return size() > PROJECT_LIMIT;
+      }
+    };
+
+    changeToProject = new LinkedHashMap<Integer, String>(CHANGE_LIMIT) {
+      private static final long serialVersionUID = 1L;
+
+      @Override
+      protected boolean removeEldestEntry(Map.Entry<Integer, String> e) {
+        return size() > CHANGE_LIMIT;
       }
     };
   }
 
-  private void getImpl(final Project.NameKey name,
-      final AsyncCallback<Entry> cb) {
-    Entry e = cache.get(name.get());
+  private void getImpl(final String name, final AsyncCallback<Entry> cb) {
+    Entry e = cache.get(name);
     if (e != null) {
       cb.onSuccess(e);
       return;
     }
-    ProjectApi.config(name).get(new AsyncCallback<ConfigInfo>() {
+    ProjectApi.config(new Project.NameKey(name)).get(
+        new AsyncCallback<ConfigInfo>() {
+          @Override
+          public void onSuccess(ConfigInfo result) {
+            Entry e = new Entry(result);
+            cache.put(name, e);
+            cb.onSuccess(e);
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            cb.onFailure(caught);
+          }
+        });
+  }
+
+  private void getImpl(final Integer id, final AsyncCallback<Entry> cb) {
+    String name = changeToProject.get(id);
+    if (name != null) {
+      getImpl(name, cb);
+      return;
+    }
+    ChangeApi.change(id).get(new AsyncCallback<ChangeInfo>() {
       @Override
-      public void onSuccess(ConfigInfo result) {
-        Entry e = new Entry(result);
-        cache.put(name.get(), e);
-        cb.onSuccess(e);
+      public void onSuccess(ChangeInfo result) {
+        changeToProject.put(id, result.project());
+        getImpl(result.project(), cb);
       }
 
       @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index be133c5..628740c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -14,23 +14,60 @@
 package com.google.gerrit.client.projects;
 
 import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
+import java.util.Set;
+
 public class ProjectApi {
   /** Create a new project */
   public static void createProject(String projectName, String parent,
       Boolean createEmptyCcommit, Boolean permissionsOnly,
-      AsyncCallback<VoidResult> asyncCallback) {
+      AsyncCallback<VoidResult> cb) {
     ProjectInput input = ProjectInput.create();
     input.setName(projectName);
     input.setParent(parent);
     input.setPermissionsOnly(permissionsOnly);
     input.setCreateEmptyCommit(createEmptyCcommit);
     new RestApi("/projects/").id(projectName).ifNoneMatch()
-        .put(input, asyncCallback);
+        .put(input, cb);
+  }
+
+  /** Create a new branch */
+  public static void createBranch(Project.NameKey projectName, String ref,
+      String revision, AsyncCallback<BranchInfo> cb) {
+    BranchInput input = BranchInput.create();
+    input.setRevision(revision);
+    new RestApi("/projects/").id(projectName.get()).view("branches").id(ref)
+        .ifNoneMatch().put(input, cb);
+  }
+
+  /** Retrieve all visible branches of the project */
+  public static void getBranches(Project.NameKey projectName,
+      AsyncCallback<JsArray<BranchInfo>> cb) {
+    new RestApi("/projects/").id(projectName.get()).view("branches").get(cb);
+  }
+
+  /**
+   * Delete branches. For each branch to be deleted a separate DELETE request is
+   * fired to the server. The {@code onSuccess} method of the provided callback
+   * is invoked once after all requests succeeded. If any request fails the
+   * callbacks' {@code onFailure} method is invoked. In a failure case it can be
+   * that still some of the branches were successfully deleted.
+   */
+  public static void deleteBranches(Project.NameKey projectName,
+      Set<String> refs, AsyncCallback<VoidResult> cb) {
+    CallbackGroup group = new CallbackGroup();
+    for (String ref : refs) {
+      new RestApi("/projects/").id(projectName.get()).view("branches").id(ref)
+          .delete(group.add(cb));
+      cb = CallbackGroup.emptyCallback();
+    }
+    group.done();
   }
 
   static RestApi config(Project.NameKey name) {
@@ -53,4 +90,15 @@
 
     final native void setCreateEmptyCommit(boolean cc) /*-{ if(cc)this.create_empty_commit=cc; }-*/;
   }
+
+  private static class BranchInput extends JavaScriptObject {
+    static BranchInput create() {
+      return (BranchInput) createObject();
+    }
+
+    protected BranchInput() {
+    }
+
+    final native void setRevision(String r) /*-{ if(r)this.revision=r; }-*/;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ThemeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ThemeInfo.java
index 67b6077..9a852a2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ThemeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ThemeInfo.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.client.projects;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
index 0b1f905..073c949 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
@@ -17,15 +17,18 @@
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
+import java.util.Set;
 
 /**
  * Class for grouping together callbacks and calling them in order.
  * <p>
  * Callbacks are added to the group with {@link #add(AsyncCallback)}, which
  * returns a wrapped callback suitable for passing to an asynchronous RPC call.
+ * The last callback must be added using {@link #addFinal(AsyncCallback)} or
+ * {@link #done()} must be invoked.
+ *
  * The enclosing group buffers returned results and ensures that
  * {@code onSuccess} is called exactly once for each callback in the group, in
  * the same order that callbacks were added. This allows callers to, for
@@ -39,82 +42,119 @@
  * processing it.
  */
 public class CallbackGroup {
-  private final List<Object> callbacks;
-  private final Map<Object, Object> results;
+  private final List<CallbackImpl<?>> callbacks;
+  private final Set<CallbackImpl<?>> remaining;
+  private boolean finalAdded;
+
   private boolean failed;
+  private Throwable failedThrowable;
+
+  public static <T> Callback<T> emptyCallback() {
+    return new Callback<T>() {
+      @Override
+      public void onSuccess(T result) {
+      }
+
+      @Override
+      public void onFailure(Throwable err) {
+      }
+    };
+  }
 
   public CallbackGroup() {
-    callbacks = new ArrayList<Object>();
-    results = new HashMap<Object, Object>();
+    callbacks = new ArrayList<CallbackImpl<?>>();
+    remaining = new HashSet<CallbackImpl<?>>();
   }
 
-  public <T> AsyncCallback<T> add(final AsyncCallback<T> cb) {
-    callbacks.add(cb);
-    return new AsyncCallback<T>() {
-      @Override
-      public void onSuccess(T result) {
-        results.put(cb, result);
-        CallbackGroup.this.onSuccess();
-      }
-
-      @Override
-      public void onFailure(Throwable caught) {
-        CallbackGroup.this.onFailure(caught);
-      }
-    };
+  public <T> Callback<T> add(final AsyncCallback<T> cb) {
+    checkFinalAdded();
+    return handleAdd(cb);
   }
 
-  public <T> com.google.gwtjsonrpc.common.AsyncCallback<T> addGwtjsonrpc(
-      final com.google.gwtjsonrpc.common.AsyncCallback<T> cb) {
-    callbacks.add(cb);
-    return new com.google.gwtjsonrpc.common.AsyncCallback<T>() {
-      @Override
-      public void onSuccess(T result) {
-        results.put(cb, result);
-        CallbackGroup.this.onSuccess();
-      }
-
-      @Override
-      public void onFailure(Throwable caught) {
-        CallbackGroup.this.onFailure(caught);
-      }
-    };
+  public <T> Callback<T> addFinal(final AsyncCallback<T> cb) {
+    checkFinalAdded();
+    finalAdded = true;
+    return handleAdd(cb);
   }
 
-  private void onSuccess() {
-    if (results.size() < callbacks.size()) {
-      return;
-    }
-    for (Object o : callbacks) {
-      Object result = results.get(o);
-      if (o instanceof AsyncCallback) {
-        @SuppressWarnings("unchecked")
-        AsyncCallback<Object> cb = (AsyncCallback<Object>) o;
-        cb.onSuccess(result);
-      } else {
-        @SuppressWarnings("unchecked")
-        com.google.gwtjsonrpc.common.AsyncCallback<Object> cb =
-            (com.google.gwtjsonrpc.common.AsyncCallback<Object>) o;
-        cb.onSuccess(result);
+  public void done() {
+    finalAdded = true;
+    applyAllSuccess();
+  }
+
+  private void applyAllSuccess() {
+    if (!failed && finalAdded && remaining.isEmpty()) {
+      for (CallbackImpl<?> cb : callbacks) {
+        cb.applySuccess();
       }
+      callbacks.clear();
     }
   }
 
-  private void onFailure(Throwable caught) {
+  private <T> Callback<T> handleAdd(AsyncCallback<T> cb) {
     if (failed) {
-      return;
+      cb.onFailure(failedThrowable);
+      return emptyCallback();
     }
-    failed = true;
-    for (Object o : callbacks) {
-      if (o instanceof AsyncCallback) {
-        @SuppressWarnings("unchecked")
-        AsyncCallback<Object> cb = (AsyncCallback<Object>) o;
-        cb.onFailure(caught);
-      } else {
-        @SuppressWarnings("unchecked")
-        com.google.gwtjsonrpc.common.AsyncCallback<Object> cb =
-            (com.google.gwtjsonrpc.common.AsyncCallback<Object>) o;
-        cb.onFailure(caught);
+
+    CallbackImpl<T> wrapper = new CallbackImpl<T>(cb);
+    callbacks.add(wrapper);
+    remaining.add(wrapper);
+    return wrapper;
+  }
+
+  private void checkFinalAdded() {
+    if (finalAdded) {
+      throw new IllegalStateException("final callback already added");
+    }
+  }
+
+  public interface Callback<T>
+      extends AsyncCallback<T>, com.google.gwtjsonrpc.common.AsyncCallback<T> {
+  }
+
+  private class CallbackImpl<T> implements Callback<T> {
+    AsyncCallback<T> delegate;
+    T result;
+
+    CallbackImpl(AsyncCallback<T> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void onSuccess(T value) {
+      if (failed) {
+        return;
+      }
+
+      this.result = value;
+      remaining.remove(this);
+      CallbackGroup.this.applyAllSuccess();
+    }
+
+    @Override
+    public void onFailure(Throwable caught) {
+      if (failed) {
+        return;
+      }
+
+      failed = true;
+      failedThrowable = caught;
+      for (CallbackImpl<?> cb : callbacks) {
+        cb.delegate.onFailure(failedThrowable);
+        cb.delegate = null;
+        cb.result = null;
+      }
+      callbacks.clear();
+      remaining.clear();
+    }
+
+    void applySuccess() {
+      AsyncCallback<T> cb = delegate;
+      if (cb != null) {
+        delegate = null;
+        cb.onSuccess(result);
+        result = null;
       }
     }
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
index d56eeaf..2e407d4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
@@ -22,6 +22,10 @@
 
 /** A map of native JSON objects, keyed by a string. */
 public class NativeMap<T extends JavaScriptObject> extends JavaScriptObject {
+  public static <T extends JavaScriptObject> NativeMap<T> create() {
+    return createObject().cast();
+  }
+
   /**
    * Loop through the result map's entries and copy the key strings into the
    * "name" property of the corresponding child object. This only runs on the
@@ -80,6 +84,7 @@
   }
 
   public final native T get(String n) /*-{ return this[n]; }-*/;
+  public final native void put(String n, T v) /*-{ this[n] = v; }-*/;
 
   public final native void copyKeysIntoChildren(String p)
   /*-{
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
index 4ee63c6..60cbe1f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
@@ -304,6 +304,11 @@
     sendJSON(POST, content, cb);
   }
 
+  public <T extends JavaScriptObject> void post(String content,
+      AsyncCallback<T> cb) {
+    sendRaw(POST, content, cb);
+  }
+
   public <T extends JavaScriptObject> void put(AsyncCallback<T> cb) {
     send(PUT, cb);
   }
@@ -329,6 +334,19 @@
     }
   }
 
+  private <T extends JavaScriptObject> void sendRaw(Method method, String body,
+      AsyncCallback<T> cb) {
+    HttpCallback<T> httpCallback = new HttpCallback<T>(cb);
+    try {
+      RpcStatus.INSTANCE.onRpcStart();
+      RequestBuilder req = request(method);
+      req.setHeader("Content-Type", TEXT_TYPE);
+      req.sendRequest(body, httpCallback);
+    } catch (RequestException e) {
+      httpCallback.onError(null, e);
+    }
+  }
+
   private RequestBuilder request(Method method) {
     RequestBuilder req = new RequestBuilder(method, url());
     if (ifNoneMatch != null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ActionDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ActionDialog.java
new file mode 100644
index 0000000..295842c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ActionDialog.java
@@ -0,0 +1,42 @@
+// 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.client.ui;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeDetailCache;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gwt.user.client.ui.FocusWidget;
+
+public abstract class ActionDialog extends CommentedActionDialog<ChangeDetail> {
+  public ActionDialog(final FocusWidget enableOnFailure, final boolean redirect,
+      String dialogTitle, String dialogHeading) {
+    super(dialogTitle, dialogHeading, new ChangeDetailCache.IgnoreErrorCallback() {
+        @Override
+        public void onSuccess(ChangeDetail result) {
+          if (redirect) {
+            Gerrit.display(PageLinks.toChange(result.getChange().getId()));
+          } else {
+            super.onSuccess(result);
+          }
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+          enableOnFailure.setEnabled(true);
+        }
+      });
+  }
+}
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ChangeLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ChangeLink.java
index d708286..95509d8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ChangeLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ChangeLink.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.client.ui;
 
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeScreen;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.ChangeInfo;
 import com.google.gerrit.reviewdb.client.Change;
@@ -57,14 +56,6 @@
 
   @Override
   public void go() {
-    Gerrit.display(getTargetHistoryToken(), createScreen());
-  }
-
-  private Screen createScreen() {
-    if (psid != null) {
-      return new ChangeScreen(psid);
-    } else {
-      return new ChangeScreen(cid);
-    }
+    Gerrit.display(getTargetHistoryToken());
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
new file mode 100644
index 0000000..9a10c23
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
@@ -0,0 +1,96 @@
+// 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.client.ui;
+
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.projects.BranchInfo;
+import com.google.gerrit.client.projects.ProjectApi;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public abstract class CherryPickDialog extends ActionDialog {
+  private SuggestBox newBranch;
+  private List<BranchInfo> branches;
+
+  public CherryPickDialog(final FocusWidget enableOnFailure, Project.NameKey project) {
+    super(enableOnFailure, true, Util.C.cherryPickTitle(), Util.C
+        .cherryPickCommitMessage());
+    ProjectApi.getBranches(project,
+        new GerritCallback<JsArray<BranchInfo>>() {
+          @Override
+          public void onSuccess(JsArray<BranchInfo> result) {
+            branches = Natives.asList(result);
+          }
+        });
+
+    newBranch = new SuggestBox(new HighlightSuggestOracle() {
+      @Override
+      protected void onRequestSuggestions(Request request, Callback done) {
+        LinkedList<BranchSuggestion> suggestions =
+            new LinkedList<BranchSuggestion>();
+        for (final BranchInfo b : branches) {
+          if (b.ref().indexOf(request.getQuery()) >= 0) {
+            suggestions.add(new BranchSuggestion(b));
+          }
+        }
+        done.onSuggestionsReady(request, new Response(suggestions));
+      }
+    });
+
+    newBranch.setWidth("70ex");
+    message.setCharacterWidth(70);
+    panel.insert(newBranch, 0);
+    panel.insert(new SmallHeading(Util.C.headingCherryPickBranch()), 0);
+  }
+
+  @Override
+  public void center() {
+    super.center();
+    GlobalKey.dialog(this);
+    newBranch.setFocus(true);
+  }
+
+  public String getDestinationBranch() {
+    return newBranch.getText();
+  }
+
+  class BranchSuggestion implements Suggestion {
+    private BranchInfo branch;
+
+    public BranchSuggestion(BranchInfo branch) {
+      this.branch = branch;
+    }
+
+    @Override
+    public String getDisplayString() {
+      return branch.ref();
+    }
+
+    @Override
+    public String getReplacementString() {
+      return branch.getShortName();
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
index 05c6b5a..01e71fe 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentPanel.java
@@ -50,6 +50,7 @@
     HasFocusHandlers, FocusHandler, HasBlurHandlers, BlurHandler {
   private static final int SUMMARY_LENGTH = 75;
   private final HandlerManager handlerManager = new HandlerManager(this);
+  private final FlowPanel body;
   private final FlexTable header;
   private final InlineLabel messageSummary;
   private final FlowPanel content;
@@ -73,7 +74,7 @@
 
   protected CommentPanel(CommentLinkProcessor commentLinkProcessor) {
     this.commentLinkProcessor = commentLinkProcessor;
-    final FlowPanel body = new FlowPanel();
+    body = new FlowPanel();
     initWidget(body);
     setStyleName(Gerrit.RESOURCES.css().commentPanel());
 
@@ -129,6 +130,8 @@
   public void setAuthorNameText(final AccountInfo author, final String nameText) {
     header.setWidget(0, 0, new AvatarImage(author, 26));
     header.setText(0, 1, nameText);
+    body.getElement().setAttribute("email", author.email());
+    body.getElement().setAttribute("name", author.name());
   }
 
   protected void setDateText(final String dateText) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
index 1edb8fd..0793527 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
@@ -25,6 +25,7 @@
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 
+import java.util.Comparator;
 import java.util.Iterator;
 
 public abstract class FancyFlexTable<RowItem> extends Composite {
@@ -58,6 +59,71 @@
     setRowItem(table.getCellFormatter().getElement(row, 0), item);
   }
 
+  /**
+   * Finds an item in the table.
+   *
+   * @param comparator comparator by which the items in the table are sorted
+   * @param item the item that should be found
+   * @return if the item is found the number of the row that contains the item;
+   *         if the item is not found <code>-1</code>
+   */
+  protected int findRowItem(Comparator<RowItem> comparator, RowItem item) {
+    int row = lookupRowItem(comparator, item);
+    if (row < table.getRowCount()
+        && comparator.compare(item, getRowItem(row)) == 0) {
+      return row;
+    }
+    return -1;
+  }
+
+  /**
+   * Finds the number of the row where a new item should be inserted into the
+   * table.
+   *
+   * @param comparator comparator by which the items in the table are sorted
+   * @param item the new item that should be inserted
+   * @return if the item is not yet contained in the table, the number of the
+   *         row where the new item should be inserted; if the item is already
+   *         contained in the table <code>-1</code>
+   */
+  protected int getInsertRow(Comparator<RowItem> comparator, RowItem item) {
+    int row = lookupRowItem(comparator, item);
+    if (row >= table.getRowCount()
+        || comparator.compare(item, getRowItem(row)) != 0) {
+      return row;
+    }
+    return -1;
+  }
+
+  /**
+   * Makes a binary search for the given row item over the table.
+   *
+   * @param comparator comparator by which the items in the table are sorted
+   * @param item the item that should be looked up
+   * @return if the item is found the number of the row that contains the item;
+   *         if the item is not found the number of the row where the item
+   *         should be inserted according to the given comparator.
+   */
+  private int lookupRowItem(Comparator<RowItem> comparator, RowItem item) {
+    int left = 1;
+    int right = table.getRowCount() - 1;
+    while (left <= right) {
+      int middle = (left + right) >>> 1; // (left+right)/2
+      RowItem i = getRowItem(middle);
+      int cmp = comparator.compare(i, item);
+
+      if (cmp < 0) {
+        left = middle + 1;
+      } else if (cmp > 0) {
+        right = middle - 1;
+      } else {
+        // item is already contained in the table
+        return middle;
+      }
+    }
+    return left;
+  }
+
   protected void resetHtml(final SafeHtml body) {
     for (final Iterator<Widget> i = table.iterator(); i.hasNext();) {
       i.next();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
index 63c2636..ce417bb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
@@ -195,7 +195,8 @@
       final Element tr = DOM.getParent(fmt.getElement(currentRow, C_ARROW));
       UIObject.setStyleName(tr, Gerrit.RESOURCES.css().activeRow(), false);
     }
-    if (newRow >= 0) {
+    if (0 <= newRow && newRow < table.getRowCount()
+        && getRowItem(newRow) != null) {
       table.setWidget(newRow, C_ARROW, pointer);
       final Element tr = DOM.getParent(fmt.getElement(newRow, C_ARROW));
       UIObject.setStyleName(tr, Gerrit.RESOURCES.css().activeRow(), true);
@@ -255,12 +256,15 @@
   }
 
   @Override
-  protected void resetHtml(SafeHtml body) {
+  public void resetHtml(SafeHtml body) {
     currentRow = -1;
     super.resetHtml(body);
   }
 
   public void finishDisplay() {
+    if (currentRow >= table.getRowCount()) {
+      currentRow = -1;
+    }
     if (saveId != null) {
       movePointerTo(savedPositions.get(saveId));
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
index a7d49a4..8082527 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
@@ -47,6 +47,7 @@
 
   private final FocusWidget widget;
   private Map<TextBoxBase, String> strings = new HashMap<TextBoxBase, String>();
+  private String originalValue;
 
 
   // The first parameter to the contructors must be the FocusWidget to enable,
@@ -54,6 +55,7 @@
 
   public OnEditEnabler(final FocusWidget w, final TextBoxBase tb) {
     this(w);
+    originalValue = tb.getValue().trim();
     listenTo(tb);
   }
 
@@ -75,7 +77,7 @@
   // Register input widgets to be listened to
 
   public void listenTo(final TextBoxBase tb) {
-    strings.put(tb, tb.getText());
+    strings.put(tb, tb.getText().trim());
     tb.addKeyPressHandler(this);
 
     // Is there another way to capture middle button X11 pastes in browsers
@@ -89,7 +91,7 @@
     tb.addFocusHandler(new FocusHandler() {
         @Override
         public void onFocus(FocusEvent event) {
-          strings.put(tb, tb.getText());
+          strings.put(tb, tb.getText().trim());
         }
       });
 
@@ -145,7 +147,7 @@
         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
           @Override
           public void execute() {
-            if (box.getValue().trim().length() == 0) {
+            if (box.getValue().trim().equals(originalValue)) {
               widget.setEnabled(false);
             }
           }
@@ -173,7 +175,7 @@
         if (orig == null) {
           orig = "";
         }
-        if (! orig.equals(tb.getText())) {
+        if (! orig.equals(tb.getText().trim())) {
           widget.setEnabled(true);
         }
       }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
index a26db05..3f79a61 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
@@ -48,6 +48,7 @@
   protected Screen() {
     initWidget(new FlowPanel());
     setStyleName(Gerrit.RESOURCES.css().screen());
+    body = new FlowPanel();
   }
 
   @Override
@@ -76,7 +77,7 @@
   protected void onInitUI() {
     final FlowPanel me = (FlowPanel) getWidget();
     me.add(header = new Grid(1, Cols.values().length));
-    me.add(body = new FlowPanel());
+    me.add(body);
 
     headerText = new InlineLabel();
     if (titleWidget == null) {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml b/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml
new file mode 100644
index 0000000..24a0f57
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml
@@ -0,0 +1,23 @@
+<!--
+ 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.
+-->
+<module>
+  <inherits name='com.google.gwt.logging.Logging'/>
+  <inherits name='com.google.gwt.resources.Resources'/>
+  <source path='addon'/>
+  <source path='lib'/>
+  <source path='keymap'/>
+  <source path='mode'/>
+</module>
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java b/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
new file mode 100644
index 0000000..8f68a34
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
@@ -0,0 +1,48 @@
+// 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 net.codemirror.addon;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.DataResource.DoNotEmbed;
+import com.google.gwt.resources.client.ExternalTextResource;
+
+public interface Addons extends ClientBundle {
+  public static final Addons I = GWT.create(Addons.class);
+
+  @Source("selection/mark-selection.js")
+  @DoNotEmbed
+  DataResource mark_selection();
+
+  @Source("edit/trailingspace.js")
+  @DoNotEmbed
+  DataResource trailingspace();
+
+  @Source("dialog/dialog.css")
+  ExternalTextResource dialogCss();
+
+  @Source("dialog/dialog.js")
+  @DoNotEmbed
+  DataResource dialog();
+
+  @Source("search/searchcursor.js")
+  @DoNotEmbed
+  DataResource searchcursor();
+
+  @Source("search/search.js")
+  @DoNotEmbed
+  DataResource search();
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/ClientVersion.java b/gerrit-gwtui/src/main/java/net/codemirror/keymap/Keymap.java
similarity index 60%
rename from gerrit-common/src/main/java/com/google/gerrit/common/ClientVersion.java
rename to gerrit-gwtui/src/main/java/net/codemirror/keymap/Keymap.java
index 42ee5dd..3d03608 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/ClientVersion.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/keymap/Keymap.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// 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.
@@ -12,13 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.common;
+package net.codemirror.keymap;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.resources.client.TextResource;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.DataResource.DoNotEmbed;
 
-public interface ClientVersion extends ClientBundle {
-  /** Version number of this client software build. */
-  @Source("Version")
-  TextResource version();
+public interface Keymap extends ClientBundle {
+  static final Keymap I = GWT.create(Keymap.class);
+
+  @Source("vim.js")
+  @DoNotEmbed
+  DataResource vim();
 }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
new file mode 100644
index 0000000..54df87d4
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -0,0 +1,254 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * Glue to connect CodeMirror to be callable from GWT.
+ *
+ * @link http://codemirror.net/doc/manual.html#api
+ */
+public class CodeMirror extends JavaScriptObject {
+  public static void initLibrary(AsyncCallback<Void> cb) {
+    Loader.initLibrary(cb);
+  }
+
+  public static native CodeMirror create(Element parent,
+      Configuration cfg) /*-{
+    return $wnd.CodeMirror(parent, cfg);
+  }-*/;
+
+  public final native void setOption(String option, boolean value) /*-{
+    this.setOption(option, value);
+  }-*/;
+
+  public final native void setOption(String option, double value) /*-{
+    this.setOption(option, value);
+  }-*/;
+
+  public final native void setOption(String option, JavaScriptObject val) /*-{
+    this.setOption(option, val);
+  }-*/;
+
+  public final native void setOptionToInfinity(String option) /*-{
+    this.setOption(option, Infinity);
+  }-*/;
+
+  public final native void setValue(String v) /*-{ this.setValue(v); }-*/;
+
+  public final native void setWidth(int w) /*-{ this.setSize(w, null); }-*/;
+  public final native void setWidth(String w) /*-{ this.setSize(w, null); }-*/;
+  public final native void setHeight(int h) /*-{ this.setSize(null, h); }-*/;
+  public final native void setHeight(String h) /*-{ this.setSize(null, h); }-*/;
+
+  public final native void refresh() /*-{ this.refresh(); }-*/;
+  public final native Element getWrapperElement() /*-{ return this.getWrapperElement(); }-*/;
+
+  public final native TextMarker markText(LineCharacter from, LineCharacter to,
+      Configuration options) /*-{
+    return this.markText(from, to, options);
+  }-*/;
+
+  public enum LineClassWhere {
+    TEXT, BACKGROUND, WRAP;
+  }
+
+  public final void addLineClass(int line, LineClassWhere where,
+      String className) {
+    addLineClassNative(line, where.name().toLowerCase(), className);
+  }
+
+  private final native void addLineClassNative(int line, String where,
+      String lineClass) /*-{
+    this.addLineClass(line, where, lineClass);
+  }-*/;
+
+  public final void addLineClass(LineHandle line, LineClassWhere where,
+      String className) {
+    addLineClassNative(line, where.name().toLowerCase(), className);
+  }
+
+  private final native void addLineClassNative(LineHandle line, String where,
+      String lineClass) /*-{
+    this.addLineClass(line, where, lineClass);
+  }-*/;
+
+  public final void removeLineClass(int line, LineClassWhere where,
+      String className) {
+    removeLineClassNative(line, where.name().toLowerCase(), className);
+  }
+
+  private final native void removeLineClassNative(int line, String where,
+      String lineClass) /*-{
+    this.removeLineClass(line, where, lineClass);
+  }-*/;
+
+  public final void removeLineClass(LineHandle line, LineClassWhere where,
+      String className) {
+    removeLineClassNative(line, where.name().toLowerCase(), className);
+  }
+
+  private final native void removeLineClassNative(LineHandle line, String where,
+      String lineClass) /*-{
+    this.removeLineClass(line, where, lineClass);
+  }-*/;
+
+
+  public final native void addWidget(LineCharacter pos, Element node,
+      boolean scrollIntoView) /*-{
+    this.addWidget(pos, node, scrollIntoView);
+  }-*/;
+
+  public final native LineWidget addLineWidget(int line, Element node,
+      Configuration options) /*-{
+    return this.addLineWidget(line, node, options);
+  }-*/;
+
+  public final native int lineAtHeight(int height) /*-{
+    return this.lineAtHeight(height);
+  }-*/;
+
+  public final native CodeMirrorDoc getDoc() /*-{
+    return this.getDoc();
+  }-*/;
+
+  public final native void scrollTo(int x, int y) /*-{
+    this.scrollTo(x, y);
+  }-*/;
+
+  public final native void scrollToY(int y) /*-{
+    this.scrollTo(null, y);
+  }-*/;
+
+  public final native ScrollInfo getScrollInfo() /*-{
+    return this.getScrollInfo();
+  }-*/;
+
+  public final native boolean isScrollSetByOther() /*-{
+    return this.state.scrollSetByOther == true;
+  }-*/;
+
+  public final native void setScrollSetByOther(boolean setByOther) /*-{
+    this.state.scrollSetByOther = setByOther;
+  }-*/;
+
+  public final native void on(String event, Runnable thunk) /*-{
+    this.on(event, $entry(function() {
+      thunk.@java.lang.Runnable::run()();
+    }));
+  }-*/;
+
+  /** TODO: Break this line after updating GWT */
+  public final native void on(String event, EventHandler handler) /*-{
+    this.on(event, $entry(function(cm, e) {
+      handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);
+    }));
+  }-*/;
+
+  public final native LineCharacter getCursor() /*-{
+    return this.getCursor();
+  }-*/;
+
+  public final native LineCharacter getCursor(String start) /*-{
+    return this.getCursor(start);
+  }-*/;
+
+  public final native void setCursor(LineCharacter lineCh) /*-{
+    this.setCursor(lineCh);
+  }-*/;
+
+  public final native boolean somethingSelected() /*-{
+    return this.somethingSelected();
+  }-*/;
+
+  public final native boolean hasActiveLine() /*-{
+    return this.state.hasOwnProperty('activeLine');
+  }-*/;
+
+  public final native LineHandle getActiveLine() /*-{
+    return this.state.activeLine;
+  }-*/;
+
+  public final native void setActiveLine(LineHandle line) /*-{
+    this.state.activeLine = line;
+  }-*/;
+
+  public final native void addKeyMap(KeyMap map) /*-{ this.addKeyMap(map); }-*/;
+
+  public final native void removeKeyMap(KeyMap map) /*-{ this.removeKeyMap(map); }-*/;
+
+  public final native void removeKeyMap(String name) /*-{ this.removeKeyMap(name); }-*/;
+
+  public static final native LineCharacter pos(int line, int ch) /*-{
+    return $wnd.CodeMirror.Pos(line, ch);
+  }-*/;
+
+  public static final native LineCharacter pos(int line) /*-{
+    return $wnd.CodeMirror.Pos(line);
+  }-*/;
+
+  public final native LineHandle getLineHandle(int line) /*-{
+    return this.getLineHandle(line);
+  }-*/;
+
+  public final native LineHandle getLineHandleVisualStart(int line) /*-{
+    return this.getLineHandleVisualStart(line);
+  }-*/;
+
+  public final native int getLineNumber(LineHandle handle) /*-{
+    return this.getLineNumber(handle);
+  }-*/;
+
+  public final native void focus() /*-{
+    this.focus();
+  }-*/;
+
+  /** Hack into CodeMirror to disable unwanted keys */
+  public static final native void disableUnwantedKey(String category,
+      String name) /*-{
+    $wnd.CodeMirror.keyMap[category][name] = undefined;
+  }-*/;
+
+  public static final native void defineVimEx(String name, String prefix,
+      Runnable thunk) /*-{
+    $wnd.CodeMirror.Vim.defineEx(name, prefix, $entry(function() {
+      thunk.@java.lang.Runnable::run()();
+    }));
+  }-*/;
+
+  public final native void moveCursorDown(int numLines) /*-{
+    this.moveV(numLines, "line");
+  }-*/;
+
+  public final native Element getGutterElement() /*-{
+    return this.getGutterElement();
+  }-*/;
+
+  protected CodeMirror() {
+  }
+
+  public static class LineHandle extends JavaScriptObject {
+    protected LineHandle(){
+    }
+  }
+
+  public interface EventHandler {
+    public void handle(CodeMirror instance, NativeEvent event);
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirrorDoc.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirrorDoc.java
new file mode 100644
index 0000000..ff5d230
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirrorDoc.java
@@ -0,0 +1,33 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** The Doc object representing the content in a CodeMirror */
+public class CodeMirrorDoc extends JavaScriptObject {
+
+  public final native void replaceRange(String replacement,
+      LineCharacter from, LineCharacter to) /*-{
+    this.replaceRange(replacement, from, to);
+  }-*/;
+
+  public final native void insertText(String insertion, LineCharacter at) /*-{
+    this.replaceRange(insertion, at);
+  }-*/;
+
+  protected CodeMirrorDoc() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
new file mode 100644
index 0000000..fde7970
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
@@ -0,0 +1,47 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * Simple map-like structure to pass configuration to CodeMirror.
+ *
+ * @link http://codemirror.net/doc/manual.html#config
+ * @see CodeMirror#create(com.google.gwt.dom.client.Element, Configuration)
+ */
+public class Configuration extends JavaScriptObject {
+  public static Configuration create() {
+    return createObject().cast();
+  }
+
+  public final native Configuration set(String name, String val)
+  /*-{ this[name] = val; return this; }-*/;
+
+  public final native Configuration set(String name, int val)
+  /*-{ this[name] = val; return this; }-*/;
+
+  public final native Configuration set(String name, boolean val)
+  /*-{ this[name] = val; return this; }-*/;
+
+  public final native Configuration set(String name, JavaScriptObject val)
+  /*-{ this[name] = val; return this; }-*/;
+
+  public final native Configuration setInfinity(String name)
+  /*-{ this[name] = Infinity; return this; }-*/;
+
+  protected Configuration() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java
new file mode 100644
index 0000000..ac2ca5c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java
@@ -0,0 +1,32 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** Object that associates a key or key combination with a handler. */
+public class KeyMap extends JavaScriptObject {
+  public static KeyMap create() {
+    return createObject().cast();
+  }
+
+  public final native KeyMap on(String key, Runnable thunk) /*-{
+    this[key] = function() { $entry(thunk.@java.lang.Runnable::run()()); };
+    return this;
+  }-*/;
+
+  protected KeyMap() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Lib.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Lib.java
new file mode 100644
index 0000000..242fd54
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Lib.java
@@ -0,0 +1,32 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.DataResource.DoNotEmbed;
+import com.google.gwt.resources.client.ExternalTextResource;
+
+interface Lib extends ClientBundle {
+  static final Lib I = GWT.create(Lib.class);
+
+  @Source("codemirror.css")
+  ExternalTextResource css();
+
+  @Source("codemirror.js")
+  @DoNotEmbed
+  DataResource js();
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
new file mode 100644
index 0000000..5076aff
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
@@ -0,0 +1,44 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** {line, ch} objects used within CodeMirror. */
+public class LineCharacter extends JavaScriptObject {
+  public static LineCharacter create(int line, int ch) {
+    return createImpl(line, ch);
+  }
+
+  public static LineCharacter create(int line) {
+    return createImpl(line, 0);
+  }
+
+  private static LineCharacter createImpl(int line, int ch) {
+    LineCharacter lineCh = createObject().cast();
+    lineCh.setLine(line);
+    lineCh.setCh(ch);
+    return lineCh;
+  }
+
+  public final native void setLine(int line) /*-{ this.line = line; }-*/;
+  public final native void setCh(int ch) /*-{ this.ch = ch; }-*/;
+
+  public final native int getLine() /*-{ return this.line; }-*/;
+  public final native int getCh() /*-{ return this.ch; }-*/;
+
+  protected LineCharacter() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
new file mode 100644
index 0000000..c7b0300
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
@@ -0,0 +1,31 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** LineWidget objects used within CodeMirror. */
+public class LineWidget extends JavaScriptObject {
+  public static LineWidget create() {
+    return createObject().cast();
+  }
+
+  public final native void clear() /*-{ this.clear(); }-*/;
+  public final native void changed() /*-{ this.changed(); }-*/;
+  public final native JavaScriptObject getLine() /*-{ return this.line; }-*/;
+
+  protected LineWidget() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
new file mode 100644
index 0000000..37f6dde
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
@@ -0,0 +1,113 @@
+// 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 net.codemirror.lib;
+
+import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gwt.core.client.Callback;
+import com.google.gwt.core.client.ScriptInjector;
+import com.google.gwt.dom.client.ScriptElement;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.resources.client.ExternalTextResource;
+import com.google.gwt.resources.client.ResourceCallback;
+import com.google.gwt.resources.client.ResourceException;
+import com.google.gwt.resources.client.TextResource;
+import com.google.gwt.safehtml.shared.SafeUri;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import net.codemirror.addon.Addons;
+import net.codemirror.keymap.Keymap;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+class Loader {
+  private static native boolean isLibLoaded()
+  /*-{ return $wnd.hasOwnProperty('CodeMirror'); }-*/;
+
+  static void initLibrary(final AsyncCallback<Void> cb) {
+    if (isLibLoaded()) {
+      cb.onSuccess(null);
+    } else {
+      injectCss(Lib.I.css());
+      injectCss(Addons.I.dialogCss());
+      injectScript(Lib.I.js().getSafeUri(), new GerritCallback<Void>(){
+        @Override
+        public void onSuccess(Void result) {
+          CallbackGroup group = new CallbackGroup();
+          injectScript(Keymap.I.vim().getSafeUri(),
+              group.add(CallbackGroup.<Void>emptyCallback()));
+          injectScript(Addons.I.dialog().getSafeUri(),
+              group.add(CallbackGroup.<Void>emptyCallback()));
+          injectScript(Addons.I.searchcursor().getSafeUri(),
+              group.add(CallbackGroup.<Void>emptyCallback()));
+          injectScript(Addons.I.search().getSafeUri(),
+              group.add(CallbackGroup.<Void>emptyCallback()));
+          injectScript(Addons.I.mark_selection().getSafeUri(),
+              group.add(CallbackGroup.<Void>emptyCallback()));
+          injectScript(Addons.I.trailingspace().getSafeUri(),
+              group.addFinal(cb));
+        }
+      });
+    }
+  }
+
+  private static void injectCss(ExternalTextResource css) {
+    try {
+      css.getText(new ResourceCallback<TextResource>() {
+        @Override
+        public void onSuccess(TextResource resource) {
+          StyleInjector.inject(resource.getText());
+        }
+
+        @Override
+        public void onError(ResourceException e) {
+          error(e);
+        }
+      });
+    } catch (ResourceException e) {
+      error(e);
+    }
+  }
+
+  static void injectScript(SafeUri js, final AsyncCallback<Void> callback) {
+    final ScriptElement[] script = new ScriptElement[1];
+    script[0] = ScriptInjector.fromUrl(js.asString())
+      .setWindow(ScriptInjector.TOP_WINDOW)
+      .setCallback(new Callback<Void, Exception>() {
+        @Override
+        public void onSuccess(Void result) {
+          script[0].removeFromParent();
+          callback.onSuccess(result);
+        }
+
+        @Override
+        public void onFailure(Exception reason) {
+          error(reason);
+          callback.onFailure(reason);
+        }
+       })
+      .inject()
+      .cast();
+  }
+
+  private static void error(Exception e) {
+    Logger log = Logger.getLogger("net.codemirror");
+    log.log(Level.SEVERE, "Cannot load portions of CodeMirror", e);
+  }
+
+  private Loader() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
new file mode 100644
index 0000000..4c32f38
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
@@ -0,0 +1,180 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.safehtml.shared.SafeUri;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import net.codemirror.mode.Modes;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ModeInjector {
+  /** Map of server content type to CodeMiror mode or content type. */
+  private static final Map<String, String> mimeAlias;
+
+  /** Map of content type "text/x-java" to mode name "clike". */
+  private static final Map<String, String> mimeModes;
+
+  /** Map of names such as "clike" to URI for code download. */
+  private static final Map<String, SafeUri> modeUris;
+
+  static {
+    DataResource[] all = {
+      Modes.I.clike(),
+      Modes.I.css(),
+      Modes.I.go(),
+      Modes.I.htmlmixed(),
+      Modes.I.javascript(),
+      Modes.I.properties(),
+      Modes.I.python(),
+      Modes.I.shell(),
+      Modes.I.sql(),
+      Modes.I.velocity(),
+      Modes.I.xml(),
+    };
+
+    mimeAlias = new HashMap<String, String>();
+    mimeModes = new HashMap<String, String>();
+    modeUris = new HashMap<String, SafeUri>();
+
+    for (DataResource m : all) {
+      modeUris.put(m.getName(), m.getSafeUri());
+    }
+    parseModeMap();
+  }
+
+  private static void parseModeMap() {
+    String mode = null;
+    for (String line : Modes.I.mode_map().getText().split("\n")) {
+      int eq = line.indexOf('=');
+      if (0 < eq) {
+        mimeAlias.put(
+          line.substring(0, eq).trim(),
+          line.substring(eq + 1).trim());
+      } else if (line.endsWith(":")) {
+        String n = line.substring(0, line.length() - 1);
+        if (modeUris.containsKey(n)) {
+          mode = n;
+        }
+      } else if (mode != null && line.contains("/")) {
+        mimeModes.put(line, mode);
+      } else {
+        mode = null;
+      }
+    }
+  }
+
+  public static String getContentType(String mode) {
+    String real = mode != null ? mimeAlias.get(mode) : null;
+    return real != null ? real : mode;
+  }
+
+  private static native boolean isModeLoaded(String n)
+  /*-{ return $wnd.CodeMirror.modes.hasOwnProperty(n); }-*/;
+
+  private static native boolean isMimeLoaded(String n)
+  /*-{ return $wnd.CodeMirror.mimeModes.hasOwnProperty(n); }-*/;
+
+  private static native JsArrayString getDependencies(String n)
+  /*-{ return $wnd.CodeMirror.modes[n].dependencies || []; }-*/;
+
+  private final Set<String> loading = new HashSet<String>(4);
+  private int pending;
+  private AsyncCallback<Void> appCallback;
+
+  public ModeInjector add(String name) {
+    if (name == null || isModeLoaded(name) || isMimeLoaded(name)) {
+      return this;
+    }
+
+    String mode = mimeModes.get(name);
+    if (mode == null) {
+      mode = name;
+    }
+
+    SafeUri uri = modeUris.get(mode);
+    if (uri == null) {
+      Logger.getLogger("net.codemirror").log(
+        Level.WARNING,
+        "CodeMirror mode " + mode + " not configured.");
+      return this;
+    }
+
+    loading.add(mode);
+    return this;
+  }
+
+  public void inject(AsyncCallback<Void> appCallback) {
+    this.appCallback = appCallback;
+    for (String mode : loading) {
+      beginLoading(mode);
+    }
+    if (pending == 0) {
+      appCallback.onSuccess(null);
+    }
+  }
+
+  private void beginLoading(final String mode) {
+    pending++;
+    Loader.injectScript(
+      modeUris.get(mode),
+      new AsyncCallback<Void>() {
+        @Override
+        public void onSuccess(Void result) {
+          pending--;
+          ensureDependenciesAreLoaded(mode);
+          if (pending == 0) {
+            appCallback.onSuccess(null);
+          }
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+          if (--pending == 0) {
+            appCallback.onFailure(caught);
+          }
+        }
+      });
+  }
+
+  private void ensureDependenciesAreLoaded(String mode) {
+    JsArrayString deps = getDependencies(mode);
+    for (int i = 0; i < deps.length(); i++) {
+      String d = deps.get(i);
+      if (loading.contains(d) || isModeLoaded(d)) {
+        continue;
+      }
+
+      SafeUri uri = modeUris.get(d);
+      if (uri == null) {
+        Logger.getLogger("net.codemirror").log(
+          Level.SEVERE,
+          "CodeMirror mode " + mode + " needs " + d);
+        continue;
+      }
+
+      loading.add(d);
+      beginLoading(d);
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java
new file mode 100644
index 0000000..e1890b1
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java
@@ -0,0 +1,36 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** {left, top, width, height, clientWidth, clientHeight} objects returned by
+ * getScrollInfo(). */
+public class ScrollInfo extends JavaScriptObject {
+  public static ScrollInfo create() {
+    return createObject().cast();
+  }
+
+  public final native int getLeft() /*-{ return this.left; }-*/;
+  public final native int getTop() /*-{ return this.top; }-*/;
+  public final native int getWidth() /*-{ return this.width; }-*/;
+  public final native int getHeight() /*-{ return this.height; }-*/;
+  public final native int getClientWidth() /*-{ return this.clientWidth; }-*/;
+  public final native int getClientHeight() /*-{ return this.clientHeight; }-*/;
+
+
+  protected ScrollInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java
new file mode 100644
index 0000000..b2d2302
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java
@@ -0,0 +1,39 @@
+// 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** Object that represents a text marker within CodeMirror */
+public class TextMarker extends JavaScriptObject {
+  public static TextMarker create() {
+    return createObject().cast();
+  }
+
+  public final native void clear() /*-{ this.clear(); }-*/;
+  public final native void changed() /*-{ this.changed(); }-*/;
+  public final native FromTo find() /*-{ return this.find(); }-*/;
+
+  protected TextMarker() {
+  }
+
+  public static class FromTo extends JavaScriptObject {
+    public final native LineCharacter getFrom() /*-{ return this.from; }-*/;
+    public final native LineCharacter getTo() /*-{ return this.to; }-*/;
+
+    protected FromTo() {
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
new file mode 100644
index 0000000..e2d3e3c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
@@ -0,0 +1,40 @@
+// 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 net.codemirror.mode;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.DataResource.DoNotEmbed;
+import com.google.gwt.resources.client.TextResource;
+
+public interface Modes extends ClientBundle {
+  public static final Modes I = GWT.create(Modes.class);
+
+  @Source("mode_map") TextResource mode_map();
+  @Source("clike/clike.js") @DoNotEmbed DataResource clike();
+  @Source("css/css.js") @DoNotEmbed DataResource css();
+  @Source("go/go.js") @DoNotEmbed DataResource go();
+  @Source("htmlmixed/htmlmixed.js") @DoNotEmbed DataResource htmlmixed();
+  @Source("javascript/javascript.js") @DoNotEmbed DataResource javascript();
+  @Source("properties/properties.js") @DoNotEmbed DataResource properties();
+  @Source("python/python.js") @DoNotEmbed DataResource python();
+  @Source("shell/shell.js") @DoNotEmbed DataResource shell();
+  @Source("sql/sql.js") @DoNotEmbed DataResource sql();
+  @Source("velocity/velocity.js") @DoNotEmbed DataResource velocity();
+  @Source("xml/xml.js") @DoNotEmbed DataResource xml();
+
+  // When adding a resource, update static initializer in ModeInjector.
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
new file mode 100644
index 0000000..bcb615a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
@@ -0,0 +1,54 @@
+clike:
+text/x-csrc
+text/x-c
+text/x-chdr
+text/x-c++src
+text/x-c++hdr
+text/x-java
+text/x-csharp
+text/x-scala
+
+css:
+text/css
+text/x-scss
+
+go:
+text/x-go
+
+htmlmixed:
+text/html
+
+javascript:
+text/javascript
+text/ecmascript
+application/javascript
+application/ecmascript
+application/json
+application/x-json
+text/typescript
+application/typescript
+
+properties:
+text/x-ini
+text/x-properties
+
+python:
+text/x-python
+
+shell:
+text/x-sh
+
+sql:
+text/x-sql
+text/x-mariadb
+text/x-mysql
+text/x-plsql
+
+velocity:
+text/velocity
+
+xml:
+text/xml
+application/xml
+
+text/x-java-source = text/x-java
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
new file mode 100644
index 0000000..e35ea7a
--- /dev/null
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
@@ -0,0 +1,97 @@
+// 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.client.diff;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.client.diff.SideBySide2.EditIterator;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+import com.googlecode.gwt.test.GwtModule;
+import com.googlecode.gwt.test.GwtTest;
+
+import net.codemirror.lib.LineCharacter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/** Unit tests for EditIterator */
+@GwtModule("com.google.gerrit.GerritGwtUI")
+public class EditIteratorTest extends GwtTest {
+  private JsArrayString lines;
+
+  private void assertLineChsEqual(LineCharacter a, LineCharacter b) {
+    assertEquals(a.getLine() + "," + a.getCh(), b.getLine() + "," + b.getCh());
+  }
+
+  @Before
+  public void initialize() {
+    lines = (JsArrayString) JavaScriptObject.createArray();
+    lines.push("1st");
+    lines.push("2nd");
+    lines.push("3rd");
+  }
+
+  @Test
+  public void testNoAdvance() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(0), iter.advance(0));
+  }
+
+  @Test
+  public void testSimpleAdvance() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(0, 1), iter.advance(1));
+  }
+
+  @Test
+  public void testEndsBeforeNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(0, 3), iter.advance(3));
+  }
+
+  @Test
+  public void testEndsOnNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(1), iter.advance(4));
+  }
+
+  @Test
+  public void testAcrossNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(1, 1), iter.advance(5));
+  }
+
+  @Test
+  public void testContinueFromBeforeNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    iter.advance(3);
+    assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(7));
+  }
+
+  @Test
+  public void testContinueFromAfterNewline() {
+    EditIterator iter = new EditIterator(lines, 0);
+    iter.advance(4);
+    assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(6));
+  }
+
+  @Test
+  public void testAcrossMultipleLines() {
+    EditIterator iter = new EditIterator(lines, 0);
+    assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(10));
+  }
+}
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/LineMapperTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/LineMapperTest.java
new file mode 100644
index 0000000..96ba2b7
--- /dev/null
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/LineMapperTest.java
@@ -0,0 +1,106 @@
+// 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.client.diff;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
+import com.google.gerrit.common.changes.Side;
+
+import org.junit.Test;
+
+/** Unit tests for LineMapper */
+public class LineMapperTest {
+
+  @Test
+  public void testAppendCommon() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendCommon(10);
+    assertEquals(10, mapper.getLineA());
+    assertEquals(10, mapper.getLineB());
+  }
+
+  @Test
+  public void testAppendInsert() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendInsert(10);
+    assertEquals(0, mapper.getLineA());
+    assertEquals(10, mapper.getLineB());
+  }
+
+  @Test
+  public void testAppendDelete() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendDelete(10);
+    assertEquals(10, mapper.getLineA());
+    assertEquals(0, mapper.getLineB());
+  }
+
+  @Test
+  public void testFindInCommon() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendCommon(10);
+    assertEquals(new LineOnOtherInfo(9, true),
+        mapper.lineOnOther(Side.PARENT, 9));
+    assertEquals(new LineOnOtherInfo(9, true),
+        mapper.lineOnOther(Side.REVISION, 9));
+  }
+
+  @Test
+  public void testFindAfterCommon() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendCommon(10);
+    assertEquals(new LineOnOtherInfo(10, true),
+        mapper.lineOnOther(Side.PARENT, 10));
+    assertEquals(new LineOnOtherInfo(10, true),
+        mapper.lineOnOther(Side.REVISION, 10));
+  }
+
+  @Test
+  public void testFindInInsertGap() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendInsert(10);
+    assertEquals(new LineOnOtherInfo(-1, false),
+        mapper.lineOnOther(Side.REVISION, 9));
+  }
+
+  @Test
+  public void testFindAfterInsertGap() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendInsert(10);
+    assertEquals(new LineOnOtherInfo(0, true),
+        mapper.lineOnOther(Side.REVISION, 10));
+    assertEquals(new LineOnOtherInfo(10, true),
+        mapper.lineOnOther(Side.PARENT, 0));
+  }
+
+  @Test
+  public void testFindInDeleteGap() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendDelete(10);
+    assertEquals(new LineOnOtherInfo(-1, false),
+        mapper.lineOnOther(Side.PARENT, 9));
+  }
+
+  @Test
+  public void testFindAfterDeleteGap() {
+    LineMapper mapper = new LineMapper();
+    mapper.appendDelete(10);
+    assertEquals(new LineOnOtherInfo(0, true),
+        mapper.lineOnOther(Side.PARENT, 10));
+    assertEquals(new LineOnOtherInfo(10, true),
+        mapper.lineOnOther(Side.REVISION, 0));
+  }
+}
diff --git a/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties b/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties
new file mode 100644
index 0000000..c0cbb30
--- /dev/null
+++ b/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties
@@ -0,0 +1 @@
+com.google.gerrit.GerritGwtUI = gwt-module
diff --git a/gerrit-httpd/BUCK b/gerrit-httpd/BUCK
new file mode 100644
index 0000000..c906459
--- /dev/null
+++ b/gerrit-httpd/BUCK
@@ -0,0 +1,67 @@
+SRCS = glob(['src/main/java/**/*.java'])
+RESOURCES = glob(['src/main/resources/**/*'])
+
+java_library2(
+  name = 'httpd',
+  srcs = SRCS,
+  resources = RESOURCES,
+  deps = [
+    '//gerrit-antlr:query_exception',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-gwtexpui:linker_server',
+    '//gerrit-gwtexpui:server',
+    '//gerrit-patch-jgit:server',
+    '//gerrit-prettify:server',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//gerrit-util-cli:cli',
+    '//lib:args4j',
+    '//lib:gson',
+    '//lib:guava',
+    '//lib:gwtjsonrpc',
+    '//lib:gwtorm',
+    '//lib:jsch',
+    '//lib:jsr305',
+    '//lib:mime-util',
+    '//lib/commons:codec',
+    '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
+    '//lib/guice:guice-servlet',
+    '//lib/jgit:jgit',
+    '//lib/jgit:jgit-servlet',
+    '//lib/log:api',
+  ],
+  compile_deps = ['//lib:servlet-api-3_0'],
+  visibility = ['PUBLIC'],
+)
+
+java_sources(
+  name = 'httpd-src',
+  srcs = SRCS + RESOURCES,
+  visibility = ['PUBLIC'],
+)
+
+java_test(
+  name = 'httpd_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  deps = [
+    ':httpd',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//lib:easymock',
+    '//lib:junit',
+    '//lib:gson',
+    '//lib:gwtorm',
+    '//lib:guava',
+    '//lib:servlet-api-3_0',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
+    '//lib/jgit:junit',
+  ],
+  source_under_test = [':httpd'],
+  # TODO(sop) Remove after Buck supports Eclipse
+  visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-httpd/pom.xml b/gerrit-httpd/pom.xml
deleted file mode 100644
index d2189a2..0000000
--- a/gerrit-httpd/pom.xml
+++ /dev/null
@@ -1,95 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-httpd</artifactId>
-  <name>Gerrit Code Review - HTTPd</name>
-
-  <description>
-    Servlet context for components run inside of an HTTP environment.
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>servlet-api</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.code.findbugs</groupId>
-      <artifactId>jsr305</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.http.server</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.junit</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>eu.medsea.mimeutil</groupId>
-      <artifactId>mime-util</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>gwtjsonrpc</groupId>
-      <artifactId>gwtjsonrpc</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-server</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-prettify</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 96792f0..9968e2a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -185,6 +185,7 @@
   public void setUserAccountId(Account.Id id) {
     key = new Key("id:" + id);
     val = new Val(id, 0, false, null, 0, null, null);
+    user = identified.runAs(id, user);
   }
 
   @Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index 3d9f4c8..d87e4af 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -189,6 +189,7 @@
     }
 
     @Override
+    @Deprecated
     public void setStatus(int sc, String sm) {
       status(sc);
       super.setStatus(sc, sm);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index c38425d..f1bddeb 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -332,6 +332,7 @@
     }
 
     @Override
+    @Deprecated
     public void setStatus(int sc, String sm) {
       status(sc);
       super.setStatus(sc, sm);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
new file mode 100644
index 0000000..e0bef35
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
@@ -0,0 +1,123 @@
+// 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.httpd;
+
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.servlet.ServletModule;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** Allows running a request as another user account. */
+@Singleton
+class RunAsFilter implements Filter {
+  private static final Logger log = LoggerFactory.getLogger(RunAsFilter.class);
+  private static final String RUN_AS = "X-Gerrit-RunAs";
+
+  static class Module extends ServletModule {
+    @Override
+    protected void configureServlets() {
+      filter("/*").through(RunAsFilter.class);
+    }
+  }
+
+  private final boolean enabled;
+  private final Provider<WebSession> session;
+  private final AccountResolver accountResolver;
+
+  @Inject
+  RunAsFilter(AuthConfig config,
+      Provider<WebSession> session,
+      AccountResolver accountResolver) {
+    this.enabled = config.isRunAsEnabled();
+    this.session = session;
+    this.accountResolver = accountResolver;
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest req = (HttpServletRequest) request;
+    HttpServletResponse res = (HttpServletResponse) response;
+
+    String runas = req.getHeader(RUN_AS);
+    if (runas != null) {
+      if (!enabled) {
+        RestApiServlet.replyError(req, res,
+            SC_FORBIDDEN,
+            RUN_AS + " disabled by auth.enableRunAs = false");
+        return;
+      }
+
+      CurrentUser self = session.get().getCurrentUser();
+      if (!self.getCapabilities().canRunAs()) {
+        RestApiServlet.replyError(req, res,
+            SC_FORBIDDEN,
+            "not permitted to use " + RUN_AS);
+        return;
+      }
+
+      Account target;
+      try {
+        target = accountResolver.find(runas);
+      } catch (OrmException e) {
+        log.warn("cannot resolve account for " + RUN_AS, e);
+        RestApiServlet.replyError(req, res,
+            SC_INTERNAL_SERVER_ERROR,
+            "cannot resolve " + RUN_AS);
+        return;
+      }
+      if (target == null) {
+        RestApiServlet.replyError(req, res,
+            SC_FORBIDDEN,
+            "no account matches " + RUN_AS);
+        return;
+      }
+      session.get().setUserAccountId(target.getId());
+    }
+
+    chain.doFilter(req, res);
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) {
+  }
+
+  @Override
+  public void destroy() {
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index bd25faf..05b059b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -24,9 +24,11 @@
 import com.google.gerrit.httpd.raw.SshInfoServlet;
 import com.google.gerrit.httpd.raw.StaticServlet;
 import com.google.gerrit.httpd.raw.ToolServlet;
+import com.google.gerrit.httpd.rpc.access.AccessRestApiServlet;
 import com.google.gerrit.httpd.rpc.account.AccountsRestApiServlet;
 import com.google.gerrit.httpd.rpc.change.ChangesRestApiServlet;
 import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
+import com.google.gerrit.httpd.rpc.config.ConfigRestApiServlet;
 import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
 import com.google.gerrit.httpd.rpc.project.ProjectsRestApiServlet;
 import com.google.gerrit.reviewdb.client.Change;
@@ -100,8 +102,10 @@
 
     filter("/a/*").through(RequireIdentifiedUserFilter.class);
     serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class);
+    serveRegex("^/(?:a/)?access/(.*)$").with(AccessRestApiServlet.class);
     serveRegex("^/(?:a/)?accounts/(.*)$").with(AccountsRestApiServlet.class);
     serveRegex("^/(?:a/)?changes/(.*)$").with(ChangesRestApiServlet.class);
+    serveRegex("^/(?:a/)?config/(.*)$").with(ConfigRestApiServlet.class);
     serveRegex("^/(?:a/)?groups/(.*)?$").with(GroupsRestApiServlet.class);
     serveRegex("^/(?:a/)?projects/(.*)?$").with(ProjectsRestApiServlet.class);
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index f7ba6df..efd8e24 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -29,8 +29,6 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.CmdLineParserModule;
 import com.google.gerrit.server.RemotePeer;
-import com.google.gerrit.server.account.ClearPassword;
-import com.google.gerrit.server.account.GeneratePassword;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.FactoryModule;
@@ -85,6 +83,7 @@
     if (wantSSL) {
       install(new RequireSslFilter.Module());
     }
+    install(new RunAsFilter.Module());
 
     switch (authConfig.getAuthType()) {
       case HTTP:
@@ -130,10 +129,8 @@
     bind(GerritConfig.class).toProvider(GerritConfigProvider.class);
     DynamicSet.setOf(binder(), WebUiPlugin.class);
 
-    factory(ClearPassword.Factory.class);
     install(new AsyncReceiveCommits.Module());
     install(new CmdLineParserModule());
-    factory(GeneratePassword.Factory.class);
 
     bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
         HttpRemotePeerProvider.class).in(RequestScoped.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
index fe7aa23..d2a25a8 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -36,7 +36,6 @@
 
 import java.io.IOException;
 
-import javax.annotation.Nullable;
 import javax.servlet.ServletException;
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServlet;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
index 9a2a7da..037c7bb 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
 import com.google.gerrit.server.auth.AuthenticationUnavailableException;
-import com.google.gerrit.server.config.SitePaths;
 import com.google.gwtexpui.server.CacheHeaders;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index 3be0cd3..3281190 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -619,7 +619,6 @@
     }, "GitWeb-ErrorLogger").start();
   }
 
-  @SuppressWarnings("unchecked")
   private static Enumeration<String> enumerateHeaderNames(HttpServletRequest req) {
     return req.getHeaderNames();
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 549c239..4b3084a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -44,8 +44,10 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
@@ -63,6 +65,7 @@
 
 import javax.servlet.FilterChain;
 import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
@@ -172,7 +175,7 @@
       }
 
       try {
-        WrappedContext ctx = new WrappedContext(plugin, base + name);
+        ServletContext ctx = PluginServletContext.create(plugin, base + name);
         filter.init(new WrappedFilterConfig(ctx));
       } catch (ServletException e) {
         log.warn(String.format("Plugin %s failed to initialize HTTP", name), e);
@@ -299,11 +302,37 @@
     }
   }
 
+  private void appendEntriesSection(JarFile jar, List<JarEntry> entries,
+      String sectionTitle, StringBuilder md, String prefix,
+      int nameOffset) throws IOException {
+    if (!entries.isEmpty()) {
+      md.append("## " + sectionTitle +  " ##\n");
+      for(JarEntry entry : entries) {
+        String rsrc = entry.getName().substring(prefix.length());
+        String entryTitle;
+        if (rsrc.endsWith(".html")) {
+          entryTitle = rsrc.substring(nameOffset, rsrc.length() - 5).replace('-', ' ');
+        } else if (rsrc.endsWith(".md")) {
+          entryTitle = extractTitleFromMarkdown(jar, entry);
+          if (Strings.isNullOrEmpty(entryTitle)) {
+            entryTitle = rsrc.substring(nameOffset, rsrc.length() - 3).replace('-', ' ');
+          }
+          rsrc = rsrc.substring(0, rsrc.length() - 3) + ".html";
+        } else {
+          entryTitle = rsrc.substring(nameOffset).replace('-', ' ');
+        }
+        md.append(String.format("* [%s](%s)\n", entryTitle, rsrc));
+      }
+      md.append("\n");
+    }
+  }
+
   private void sendAutoIndex(JarFile jar,
       String prefix, String pluginName,
       ResourceKey cacheKey, HttpServletResponse res) throws IOException {
     List<JarEntry> cmds = Lists.newArrayList();
     List<JarEntry> docs = Lists.newArrayList();
+    JarEntry about = null;
     Enumeration<JarEntry> entries = jar.entries();
     while (entries.hasMoreElements()) {
       JarEntry entry = entries.nextElement();
@@ -313,8 +342,13 @@
           && (name.endsWith(".md")
               || name.endsWith(".html"))
           && 0 < size && size <= SMALL_RESOURCE) {
-        if (name.substring(prefix.length()).startsWith("cmd-")) {
+        name = name.substring(prefix.length());
+        if (name.startsWith("cmd-")) {
           cmds.add(entry);
+        } else if (name.startsWith("about.")) {
+          if (about == null) {
+            about = entry;
+          }
         } else {
           docs.add(entry);
         }
@@ -338,47 +372,30 @@
     md.append("\n");
     appendPluginInfoTable(md, jar.getManifest().getMainAttributes());
 
-    if (!docs.isEmpty()) {
-      md.append("## Documentation ##\n");
-      for(JarEntry entry : docs) {
-        String rsrc = entry.getName().substring(prefix.length());
-        String title;
-        if (rsrc.endsWith(".html")) {
-          title = rsrc.substring(0, rsrc.length() - 5).replace('-', ' ');
-        } else if (rsrc.endsWith(".md")) {
-          title = extractTitleFromMarkdown(jar, entry);
-          if (Strings.isNullOrEmpty(title)) {
-            title = rsrc.substring(0, rsrc.length() - 3).replace('-', ' ');
-          }
-          rsrc = rsrc.substring(0, rsrc.length() - 3) + ".html";
+    if (about != null) {
+      InputStreamReader isr = new InputStreamReader(jar.getInputStream(about));
+      BufferedReader reader = new BufferedReader(isr);
+      StringBuilder aboutContent = new StringBuilder();
+      String line;
+      while ((line = reader.readLine()) != null) {
+        line = line.trim();
+        if (line.isEmpty()) {
+          aboutContent.append("\n");
         } else {
-          title = rsrc.replace('-', ' ');
+          aboutContent.append(line).append("\n");
         }
-        md.append(String.format("* [%s](%s)\n", title, rsrc));
       }
-      md.append("\n");
+      reader.close();
+
+      // Only append the About section if there was anything in it
+      if (aboutContent.toString().trim().length() > 0) {
+        md.append("## About ##\n");
+        md.append("\n").append(aboutContent);
+      }
     }
 
-    if (!cmds.isEmpty()) {
-      md.append("## Commands ##\n");
-      for(JarEntry entry : cmds) {
-        String rsrc = entry.getName().substring(prefix.length());
-        String title;
-        if (rsrc.endsWith(".html")) {
-          title = rsrc.substring(4, rsrc.length() - 5).replace('-', ' ');
-        } else if (rsrc.endsWith(".md")) {
-          title = extractTitleFromMarkdown(jar, entry);
-          if (Strings.isNullOrEmpty(title)) {
-            title = rsrc.substring(4, rsrc.length() - 3).replace('-', ' ');
-          }
-          rsrc = rsrc.substring(0, rsrc.length() - 3) + ".html";
-        } else {
-          title = rsrc.substring(4).replace('-', ' ');
-        }
-        md.append(String.format("* [%s](%s)\n", title, rsrc));
-      }
-      md.append("\n");
-    }
+    appendEntriesSection(jar, docs, "Documentation", md, prefix, 0);
+    appendEntriesSection(jar, cmds, "Commands", md, prefix, "cmd-".length());
 
     sendMarkdownAsHtml(md.toString(), pluginName, cacheKey, res);
   }
@@ -623,8 +640,16 @@
 
     @Override
     public String getServletPath() {
-      return ((HttpServletRequest) getRequest()).getRequestURI().substring(
-          contextPath.length());
+      return getRequestURI().substring(contextPath.length());
+    }
+
+    @Override
+    public String getRequestURI() {
+      String uri = super.getRequestURI();
+      if (uri.startsWith("/a/")) {
+        uri = uri.substring(2);
+      }
+      return uri;
     }
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginServletContext.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginServletContext.java
new file mode 100644
index 0000000..c3395b5
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/PluginServletContext.java
@@ -0,0 +1,241 @@
+// 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.httpd.plugins;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.common.Version;
+import com.google.gerrit.server.plugins.Plugin;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+
+class PluginServletContext {
+  private static final Logger log = LoggerFactory.getLogger("plugin");
+
+  static ServletContext create(Plugin plugin, String contextPath) {
+    return (ServletContext) Proxy.newProxyInstance(
+        PluginServletContext.class.getClassLoader(),
+        new Class[] {ServletContext.class, API.class},
+        new Handler(plugin, contextPath));
+  }
+
+  private PluginServletContext() {
+  }
+
+  private static class Handler implements InvocationHandler, API {
+    private final Plugin plugin;
+    private final String contextPath;
+    private final ConcurrentMap<String, Object> attributes;
+
+    Handler(Plugin plugin, String contextPath) {
+      this.plugin = plugin;
+      this.contextPath = contextPath;
+      this.attributes = Maps.newConcurrentMap();
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args)
+        throws Throwable {
+      Method handler;
+      try {
+        handler = API.class.getDeclaredMethod(
+            method.getName(),
+            method.getParameterTypes());
+      } catch (NoSuchMethodException e) {
+        throw new NoSuchMethodError(String.format(
+            "%s does not implement%s",
+            PluginServletContext.class,
+            method.toGenericString()));
+      }
+      return handler.invoke(this, args);
+    }
+
+    @Override
+    public String getContextPath() {
+      return contextPath;
+    }
+
+    @Override
+    public String getInitParameter(String name) {
+      return null;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Enumeration getInitParameterNames() {
+      return Collections.enumeration(Collections.emptyList());
+    }
+
+    @Override
+    public ServletContext getContext(String name) {
+      return null;
+    }
+
+    @Override
+    public RequestDispatcher getNamedDispatcher(String name) {
+      return null;
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String name) {
+      return null;
+    }
+
+    @Override
+    public URL getResource(String name) {
+      return null;
+    }
+
+    @Override
+    public InputStream getResourceAsStream(String name) {
+      return null;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Set getResourcePaths(String name) {
+      return null;
+    }
+
+    @Override
+    public Servlet getServlet(String name) {
+      return null;
+    }
+
+    @Override
+    public String getRealPath(String name) {
+      return null;
+    }
+
+    @Override
+    public String getServletContextName() {
+      return plugin.getName();
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Enumeration getServletNames() {
+      return Collections.enumeration(Collections.emptyList());
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Enumeration getServlets() {
+      return Collections.enumeration(Collections.emptyList());
+    }
+
+    @Override
+    public void log(Exception reason, String msg) {
+      log(msg, reason);
+    }
+
+    @Override
+    public void log(String msg) {
+      log(msg, null);
+    }
+
+    @Override
+    public void log(String msg, Throwable reason) {
+      log.warn(String.format("[plugin %s] %s", plugin.getName(), msg), reason);
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+      return attributes.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+      return Collections.enumeration(attributes.keySet());
+    }
+
+    @Override
+    public void setAttribute(String name, Object value) {
+      attributes.put(name, value);
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+      attributes.remove(name);
+    }
+
+    @Override
+    public String getMimeType(String file) {
+      return null;
+    }
+
+    @Override
+    public int getMajorVersion() {
+      return 2;
+    }
+
+    @Override
+    public int getMinorVersion() {
+      return 5;
+    }
+
+    @Override
+    public String getServerInfo() {
+      String v = Version.getVersion();
+      return "Gerrit Code Review/" + (v != null ? v : "dev");
+    }
+  }
+
+  static interface API {
+    String getContextPath();
+    String getInitParameter(String name);
+    @SuppressWarnings("rawtypes")
+    Enumeration getInitParameterNames();
+    ServletContext getContext(String name);
+    RequestDispatcher getNamedDispatcher(String name);
+    RequestDispatcher getRequestDispatcher(String name);
+    URL getResource(String name);
+    InputStream getResourceAsStream(String name);
+    @SuppressWarnings("rawtypes")
+    Set getResourcePaths(String name);
+    Servlet getServlet(String name);
+    String getRealPath(String name);
+    String getServletContextName();
+    @SuppressWarnings("rawtypes")
+    Enumeration getServletNames();
+    @SuppressWarnings("rawtypes")
+    Enumeration getServlets();
+    void log(Exception reason, String msg);
+    void log(String msg);
+    void log(String msg, Throwable reason);
+    Object getAttribute(String name);
+    Enumeration<String> getAttributeNames();
+    void setAttribute(String name, Object value);
+    void removeAttribute(String name);
+    String getMimeType(String file);
+    int getMajorVersion();
+    int getMinorVersion();
+    String getServerInfo();
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/WrappedContext.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/WrappedContext.java
deleted file mode 100644
index daeb6ff..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/WrappedContext.java
+++ /dev/null
@@ -1,178 +0,0 @@
-// 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.httpd.plugins;
-
-import com.google.common.collect.Maps;
-import com.google.gerrit.common.Version;
-import com.google.gerrit.server.plugins.Plugin;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.Servlet;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-
-class WrappedContext implements ServletContext {
-  private static final Logger log = LoggerFactory.getLogger("plugin");
-  private final Plugin plugin;
-  private final String contextPath;
-  private final ConcurrentMap<String, Object> attributes;
-
-  WrappedContext(Plugin plugin, String contextPath) {
-    this.plugin = plugin;
-    this.contextPath = contextPath;
-    this.attributes = Maps.newConcurrentMap();
-  }
-
-  @Override
-  public String getContextPath() {
-    return contextPath;
-  }
-
-  @Override
-  public String getInitParameter(String name) {
-    return null;
-  }
-
-  @SuppressWarnings("rawtypes")
-  @Override
-  public Enumeration getInitParameterNames() {
-    return Collections.enumeration(Collections.emptyList());
-  }
-
-  @Override
-  public ServletContext getContext(String name) {
-    return null;
-  }
-
-  @Override
-  public RequestDispatcher getNamedDispatcher(String name) {
-    return null;
-  }
-
-  @Override
-  public RequestDispatcher getRequestDispatcher(String name) {
-    return null;
-  }
-
-  @Override
-  public URL getResource(String name) throws MalformedURLException {
-    return null;
-  }
-
-  @Override
-  public InputStream getResourceAsStream(String name) {
-    return null;
-  }
-
-  @SuppressWarnings("rawtypes")
-  @Override
-  public Set getResourcePaths(String name) {
-    return null;
-  }
-
-  @Override
-  public Servlet getServlet(String name) throws ServletException {
-    return null;
-  }
-
-  @Override
-  public String getRealPath(String name) {
-    return null;
-  }
-
-  @Override
-  public String getServletContextName() {
-    return plugin.getName();
-  }
-
-  @SuppressWarnings("rawtypes")
-  @Override
-  public Enumeration getServletNames() {
-    return Collections.enumeration(Collections.emptyList());
-  }
-
-  @SuppressWarnings("rawtypes")
-  @Override
-  public Enumeration getServlets() {
-    return Collections.enumeration(Collections.emptyList());
-  }
-
-  @Override
-  public void log(Exception reason, String msg) {
-    log(msg, reason);
-  }
-
-  @Override
-  public void log(String msg) {
-    log(msg, null);
-  }
-
-  @Override
-  public void log(String msg, Throwable reason) {
-    log.warn(String.format("[plugin %s] %s", plugin.getName(), msg), reason);
-  }
-
-  @Override
-  public Object getAttribute(String name) {
-    return attributes.get(name);
-  }
-
-  @Override
-  public Enumeration<String> getAttributeNames() {
-    return Collections.enumeration(attributes.keySet());
-  }
-
-  @Override
-  public void setAttribute(String name, Object value) {
-    attributes.put(name, value);
-  }
-
-  @Override
-  public void removeAttribute(String name) {
-    attributes.remove(name);
-  }
-
-  @Override
-  public String getMimeType(String file) {
-    return null;
-  }
-
-  @Override
-  public int getMajorVersion() {
-    return 2;
-  }
-
-  @Override
-  public int getMinorVersion() {
-    return 5;
-  }
-
-  @Override
-  public String getServerInfo() {
-    String v = Version.getVersion();
-    return "Gerrit Code Review/" + (v != null ? v : "dev");
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/WrappedFilterConfig.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/WrappedFilterConfig.java
index c9107dc..04e49c9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/WrappedFilterConfig.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/WrappedFilterConfig.java
@@ -23,9 +23,9 @@
 import javax.servlet.ServletContext;
 
 class WrappedFilterConfig implements FilterConfig {
-  private final WrappedContext context;
+  private final ServletContext context;
 
-  WrappedFilterConfig(WrappedContext context) {
+  WrappedFilterConfig(ServletContext context) {
     this.context = context;
   }
 
@@ -39,7 +39,7 @@
     return null;
   }
 
-  @SuppressWarnings("rawtypes")
+  @SuppressWarnings({"rawtypes", "unchecked"})
   @Override
   public Enumeration getInitParameterNames() {
     return Collections.enumeration(Collections.emptyList());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index ea2168a..b1fd412 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -14,10 +14,11 @@
 
 package com.google.gerrit.httpd.raw;
 
+import com.google.common.collect.Lists;
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
-import com.google.common.collect.Lists;
 import com.google.common.primitives.Bytes;
+import com.google.gerrit.common.Version;
 import com.google.gerrit.common.data.GerritConfig;
 import com.google.gerrit.common.data.HostPageData;
 import com.google.gerrit.extensions.registration.DynamicSet;
@@ -275,6 +276,7 @@
       footer = injectXmlFile(hostDoc, "gerrit_footer", site.site_footer);
 
       final HostPageData pageData = new HostPageData();
+      pageData.version = Version.getVersion();
       pageData.config = config;
 
       final StringWriter w = new StringWriter();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
index dabffc0..9183d5c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
@@ -33,6 +33,7 @@
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
+import com.google.gwtexpui.server.CacheHeaders;
 import com.google.inject.Inject;
 
 import org.kohsuke.args4j.CmdLineException;
@@ -67,7 +68,7 @@
       clp.parseOptionMap(in);
     } catch (CmdLineException e) {
       if (!clp.wasHelpRequestedByOption()) {
-        replyError(res, SC_BAD_REQUEST, e.getMessage());
+        replyError(req, res, SC_BAD_REQUEST, e.getMessage());
         return false;
       }
     }
@@ -79,6 +80,7 @@
       msg.write('\n');
       clp.printUsage(msg, null);
       msg.write('\n');
+      CacheHeaders.setNotCacheable(res);
       replyBinaryResult(req, res,
           BinaryResult.create(msg.toString()).setContentType("text/plain"));
       return false;
@@ -159,7 +161,6 @@
    */
   static JsonObject formToJson(HttpServletRequest req)
       throws BadRequestException {
-    @SuppressWarnings("unchecked")
     Map<String, String[]> map = req.getParameterMap();
     return formToJson(map, query(req));
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 1040da3..c671ec7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.Charsets.UTF_8;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.math.RoundingMode.CEILING;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
 import static javax.servlet.http.HttpServletResponse.SC_CREATED;
@@ -26,6 +27,7 @@
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
 
+import com.google.common.base.Charsets;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
@@ -38,6 +40,8 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
+import com.google.common.io.BaseEncoding;
+import com.google.common.math.IntMath;
 import com.google.common.net.HttpHeaders;
 import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.audit.HttpAuditEvent;
@@ -48,11 +52,12 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.CacheControl;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.PreconditionFailedException;
-import com.google.gerrit.extensions.restapi.PutInput;
+import com.google.gerrit.extensions.restapi.RawInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -61,7 +66,6 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.extensions.restapi.StreamingResponse;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.httpd.WebSession;
@@ -181,7 +185,6 @@
   protected final void service(HttpServletRequest req, HttpServletResponse res)
       throws ServletException, IOException {
     long auditStartTs = System.currentTimeMillis();
-    CacheHeaders.setNotCacheable(res);
     res.setHeader("Content-Disposition", "attachment");
     res.setHeader("X-Content-Type-Options", "nosniff");
     int status = SC_OK;
@@ -212,7 +215,9 @@
         IdString id = path.remove(0);
         try {
           rsrc = rc.parse(rsrc, id);
-          checkPreconditions(req, rsrc);
+          if (path.isEmpty()) {
+            checkPreconditions(req, rsrc);
+          }
         } catch (ResourceNotFoundException e) {
           if (rc instanceof AcceptsCreate
               && path.isEmpty()
@@ -295,19 +300,19 @@
 
       if (result instanceof Response) {
         @SuppressWarnings("rawtypes")
-        Response r = (Response) result;
+        Response<?> r = (Response) result;
         status = r.statusCode();
+        configureCaching(req, res, r.caching());
       } else if (result instanceof Response.Redirect) {
+        CacheHeaders.setNotCacheable(res);
         res.sendRedirect(((Response.Redirect) result).location());
         return;
+      } else {
+        CacheHeaders.setNotCacheable(res);
       }
       res.setStatus(status);
 
-      if (result instanceof StreamingResponse) {
-        StreamingResponse r = (StreamingResponse) result;
-        res.setContentType(r.getContentType());
-        r.stream(res.getOutputStream());
-      } else if (result != Response.none()) {
+      if (result != Response.none()) {
         result = Response.unwrap(result);
         if (result instanceof BinaryResult) {
           replyBinaryResult(req, res, (BinaryResult) result);
@@ -316,27 +321,27 @@
         }
       }
     } catch (AuthException e) {
-      replyError(res, status = SC_FORBIDDEN, e.getMessage());
+      replyError(req, res, status = SC_FORBIDDEN, e.getMessage(), e.caching());
     } catch (BadRequestException e) {
-      replyError(res, status = SC_BAD_REQUEST, e.getMessage());
+      replyError(req, res, status = SC_BAD_REQUEST, e.getMessage(), e.caching());
     } catch (MethodNotAllowedException e) {
-      replyError(res, status = SC_METHOD_NOT_ALLOWED, "Method not allowed");
+      replyError(req, res, status = SC_METHOD_NOT_ALLOWED, "Method not allowed", e.caching());
     } catch (ResourceConflictException e) {
-      replyError(res, status = SC_CONFLICT, e.getMessage());
+      replyError(req, res, status = SC_CONFLICT, e.getMessage(), e.caching());
     } catch (PreconditionFailedException e) {
-      replyError(res, status = SC_PRECONDITION_FAILED,
-          Objects.firstNonNull(e.getMessage(), "Precondition failed"));
+      replyError(req, res, status = SC_PRECONDITION_FAILED,
+          Objects.firstNonNull(e.getMessage(), "Precondition failed"), e.caching());
     } catch (ResourceNotFoundException e) {
-      replyError(res, status = SC_NOT_FOUND, "Not found");
+      replyError(req, res, status = SC_NOT_FOUND, "Not found", e.caching());
     } catch (UnprocessableEntityException e) {
-      replyError(res, status = 422,
-          Objects.firstNonNull(e.getMessage(), "Unprocessable Entity"));
+      replyError(req, res, status = 422,
+          Objects.firstNonNull(e.getMessage(), "Unprocessable Entity"), e.caching());
     } catch (AmbiguousViewException e) {
-      replyError(res, status = SC_NOT_FOUND, e.getMessage());
+      replyError(req, res, status = SC_NOT_FOUND, e.getMessage());
     } catch (MalformedJsonException e) {
-      replyError(res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request");
+      replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request");
     } catch (JsonParseException e) {
-      replyError(res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request");
+      replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request");
     } catch (Exception e) {
       status = SC_INTERNAL_SERVER_ERROR;
       handleException(e, req, res);
@@ -348,6 +353,26 @@
     }
   }
 
+  private static <T> void configureCaching(HttpServletRequest req,
+      HttpServletResponse res, CacheControl c) {
+    if ("GET".equals(req.getMethod())) {
+      switch (c.getType()) {
+        case NONE:
+        default:
+          CacheHeaders.setNotCacheable(res);
+          break;
+        case PRIVATE:
+          CacheHeaders.setCacheablePrivate(res, c.getAge(), c.getUnit());
+          break;
+        case PUBLIC:
+          CacheHeaders.setCacheable(req, res, c.getAge(), c.getUnit());
+          break;
+      }
+    } else {
+      CacheHeaders.setNotCacheable(res);
+    }
+  }
+
   private void checkPreconditions(HttpServletRequest req, RestResource rsrc)
       throws PreconditionFailedException {
     if ("*".equals(req.getHeader("If-None-Match"))) {
@@ -414,8 +439,9 @@
       } finally {
         br.close();
       }
-    } else if ("PUT".equals(req.getMethod()) && acceptsPutInput(type)) {
-      return parsePutInput(req, type);
+    } else if (("PUT".equals(req.getMethod()) || "POST".equals(req.getMethod()))
+        && acceptsRawInput(type)) {
+      return parseRawInput(req, type);
     } else if ("DELETE".equals(req.getMethod()) && hasNoBody(req)) {
       return null;
     } else if (hasNoBody(req)) {
@@ -451,10 +477,10 @@
   }
 
   @SuppressWarnings("rawtypes")
-  private static boolean acceptsPutInput(Type type) {
+  private static boolean acceptsRawInput(Type type) {
     if (type instanceof Class) {
       for (Field f : ((Class) type).getDeclaredFields()) {
-        if (f.getType() == PutInput.class) {
+        if (f.getType() == RawInput.class) {
           return true;
         }
       }
@@ -462,15 +488,15 @@
     return false;
   }
 
-  private Object parsePutInput(final HttpServletRequest req, Type type)
+  private Object parseRawInput(final HttpServletRequest req, Type type)
       throws SecurityException, NoSuchMethodException,
       IllegalArgumentException, InstantiationException, IllegalAccessException,
       InvocationTargetException, MethodNotAllowedException {
     Object obj = createInstance(type);
     for (Field f : obj.getClass().getDeclaredFields()) {
-      if (f.getType() == PutInput.class) {
+      if (f.getType() == RawInput.class) {
         f.setAccessible(true);
-        f.set(obj, new PutInput() {
+        f.set(obj, new RawInput() {
           @Override
           public String getContentType() {
             return req.getContentType();
@@ -534,7 +560,7 @@
       Multimap<String, String> config,
       Object result)
       throws IOException {
-    final TemporaryBuffer.Heap buf = heap(Integer.MAX_VALUE);
+    TemporaryBuffer.Heap buf = heap(Integer.MAX_VALUE);
     buf.write(JSON_MAGIC);
     Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8));
     Gson gson = newGson(config, req);
@@ -545,18 +571,9 @@
     }
     w.write('\n');
     w.flush();
-
-    replyBinaryResult(req, res, new BinaryResult() {
-      @Override
-      public long getContentLength() {
-        return buf.length();
-      }
-
-      @Override
-      public void writeTo(OutputStream os) throws IOException {
-        buf.writeTo(os, null);
-      }
-    }.setContentType(JSON_TYPE).setCharacterEncoding(UTF_8.name()));
+    replyBinaryResult(req, res, asBinaryResult(buf)
+      .setContentType(JSON_TYPE)
+      .setCharacterEncoding(UTF_8.name()));
   }
 
   private static Gson newGson(Multimap<String, String> config,
@@ -626,44 +643,78 @@
       @Nullable HttpServletRequest req,
       HttpServletResponse res,
       BinaryResult bin) throws IOException {
+    final BinaryResult appResult = bin;
     try {
+      if (bin.isBase64()) {
+        bin = stackBase64(res, bin);
+      }
+      if (bin.canGzip() && acceptsGzip(req)) {
+        bin = stackGzip(res, bin);
+      }
+
       res.setContentType(bin.getContentType());
+      long len = bin.getContentLength();
+      if (0 <= len && len < Integer.MAX_VALUE) {
+        res.setContentLength((int) len);
+      } else if (0 <= len) {
+        res.setHeader("Content-Length", Long.toString(len));
+      }
+
       OutputStream dst = res.getOutputStream();
       try {
-        long len = bin.getContentLength();
-        boolean gzip = bin.canGzip() && acceptsGzip(req);
-        if (gzip && 256 <= len && len <= (10 << 20)) {
-          TemporaryBuffer.Heap buf = compress(bin);
-          if (buf.length() < len) {
-            res.setContentLength((int) buf.length());
-            res.setHeader("Content-Encoding", "gzip");
-            buf.writeTo(dst, null);
-          } else {
-            replyUncompressed(res, dst, bin, len);
-          }
-        } else if (gzip) {
-          res.setHeader("Content-Encoding", "gzip");
-          dst = new GZIPOutputStream(dst);
-          bin.writeTo(dst);
-        } else {
-          replyUncompressed(res, dst, bin, len);
-        }
+        bin.writeTo(dst);
       } finally {
         dst.close();
       }
     } finally {
-      bin.close();
+      appResult.close();
     }
   }
 
-  private static void replyUncompressed(HttpServletResponse res,
-      OutputStream dst, BinaryResult bin, long len) throws IOException {
-    if (0 <= len && len < Integer.MAX_VALUE) {
-      res.setContentLength((int) len);
-    } else if (0 <= len) {
-      res.setHeader("Content-Length", Long.toString(len));
+  private static BinaryResult stackBase64(HttpServletResponse res,
+      final BinaryResult src) throws IOException {
+    BinaryResult b64;
+    long len = src.getContentLength();
+    if (0 <= len && len <= (7 << 20)) {
+      b64 = base64(src);
+    } else {
+      b64 = new BinaryResult() {
+        @Override
+        public void writeTo(OutputStream out) throws IOException {
+          OutputStream e = BaseEncoding.base64().encodingStream(
+              new OutputStreamWriter(out, Charsets.ISO_8859_1));
+          src.writeTo(e);
+          e.flush();
+        }
+      };
     }
-    bin.writeTo(dst);
+    res.setHeader("X-FYI-Content-Encoding", "base64");
+    res.setHeader("X-FYI-Content-Type", src.getContentType());
+    return b64.setContentType("text/plain").setCharacterEncoding("ISO-8859-1");
+  }
+
+  private static BinaryResult stackGzip(HttpServletResponse res,
+      final BinaryResult src) throws IOException {
+    BinaryResult gz;
+    long len = src.getContentLength();
+    if (256 <= len && len <= (10 << 20)) {
+      gz = compress(src);
+      if (len <= gz.getContentLength()) {
+        return src;
+      }
+    } else {
+      gz = new BinaryResult() {
+        @Override
+        public void writeTo(OutputStream out) throws IOException {
+          GZIPOutputStream gz = new GZIPOutputStream(out);
+          src.writeTo(gz);
+          gz.finish();
+          gz.flush();
+        }
+      };
+    }
+    res.setHeader("Content-Encoding", "gzip");
+    return gz.setContentType(src.getContentType());
   }
 
   private RestView<RestResource> view(
@@ -786,13 +837,20 @@
 
     if (!res.isCommitted()) {
       res.reset();
-      replyError(res, SC_INTERNAL_SERVER_ERROR, "Internal server error");
+      replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error");
     }
   }
 
-  static void replyError(HttpServletResponse res, int statusCode, String msg)
-      throws IOException {
+  public static void replyError(HttpServletRequest req,
+      HttpServletResponse res, int statusCode, String msg) throws IOException {
+    replyError(req, res, statusCode, msg, CacheControl.NONE);
+  }
+
+  public static void replyError(HttpServletRequest req,
+      HttpServletResponse res, int statusCode, String msg,
+      CacheControl c) throws IOException {
     res.setStatus(statusCode);
+    configureCaching(req, res, c);
     replyText(null, res, msg);
   }
 
@@ -842,14 +900,33 @@
     return false;
   }
 
-  private static TemporaryBuffer.Heap compress(BinaryResult bin)
+  private static BinaryResult base64(BinaryResult bin)
+      throws IOException {
+    int max = 4 * IntMath.divide((int) bin.getContentLength(), 3, CEILING);
+    TemporaryBuffer.Heap buf = heap(max);
+    OutputStream encoded = BaseEncoding.base64().encodingStream(
+        new OutputStreamWriter(buf, Charsets.ISO_8859_1));
+    bin.writeTo(encoded);
+    encoded.close();
+    return asBinaryResult(buf);
+  }
+
+  private static BinaryResult compress(BinaryResult bin)
       throws IOException {
     TemporaryBuffer.Heap buf = heap(20 << 20);
     GZIPOutputStream gz = new GZIPOutputStream(buf);
     bin.writeTo(gz);
-    gz.finish();
-    gz.flush();
-    return buf;
+    gz.close();
+    return asBinaryResult(buf).setContentType(bin.getContentType());
+  }
+
+  private static BinaryResult asBinaryResult(final TemporaryBuffer.Heap buf) {
+    return new BinaryResult() {
+      @Override
+      public void writeTo(OutputStream os) throws IOException {
+        buf.writeTo(os, null);
+      }
+    }.setContentLength(buf.length());
   }
 
   private static Heap heap(int max) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java
index 059b54c..c0c4535 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java
@@ -28,7 +28,7 @@
     super(response);
   }
 
-  int getStatus() {
+  public int getStatus() {
     return status;
   }
 
@@ -39,6 +39,7 @@
   }
 
   @Override
+  @Deprecated
   public void setStatus(int sc, String sm) {
     super.setStatus(sc, sm);
     this.status = sc;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index 22546a7..7690f5c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -46,6 +46,7 @@
 
 import org.eclipse.jgit.lib.Config;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -218,7 +219,8 @@
       final String query, final int limit,
       final AsyncCallback<List<ReviewerInfo>> callback) {
     run(callback, new Action<List<ReviewerInfo>>() {
-      public List<ReviewerInfo> run(final ReviewDb db) throws OrmException {
+      public List<ReviewerInfo> run(final ReviewDb db)
+          throws OrmException, Failure {
         final ChangeControl changeControl;
         try {
           changeControl = changeControlFactory.controlFor(change);
@@ -273,7 +275,7 @@
   }
 
   private boolean suggestGroupAsReviewer(final Project.NameKey project,
-      final GroupReference group) throws OrmException {
+      final GroupReference group) throws OrmException, Failure {
     if (!PostReviewers.isLegalReviewerGroup(group.getUUID())) {
       return false;
     }
@@ -296,6 +298,8 @@
       return false;
     } catch (NoSuchProjectException e) {
       return false;
+    } catch (IOException e) {
+      throw new Failure(e);
     }
 
     return true;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/access/AccessRestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/access/AccessRestApiServlet.java
new file mode 100644
index 0000000..fda6416
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/access/AccessRestApiServlet.java
@@ -0,0 +1,32 @@
+// 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.httpd.rpc.access;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.access.AccessCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+public class AccessRestApiServlet extends RestApiServlet {
+  private static final long serialVersionUID = 1L;
+
+  @Inject
+  AccessRestApiServlet(RestApiServlet.Globals globals,
+      Provider<AccessCollection> access) {
+    super(globals, access);
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
index bef6316..d0fb504 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountModule.java
@@ -17,7 +17,6 @@
 import com.google.gerrit.httpd.rpc.RpcServletModule;
 import com.google.gerrit.httpd.rpc.UiRpcModule;
 import com.google.gerrit.server.config.FactoryModule;
-import com.google.gerrit.server.mail.RegisterNewEmailSender;
 
 public class AccountModule extends RpcServletModule {
   public AccountModule() {
@@ -32,7 +31,6 @@
         factory(AgreementInfoFactory.Factory.class);
         factory(DeleteExternalIds.Factory.class);
         factory(ExternalIdDetailFactory.Factory.class);
-        factory(RegisterNewEmailSender.Factory.class);
       }
     });
     rpc(AccountSecurityImpl.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
index 6d183b8..d3e0f84 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -19,8 +19,6 @@
 import com.google.gerrit.common.data.AccountSecurity;
 import com.google.gerrit.common.data.ContributorAgreement;
 import com.google.gerrit.common.errors.ContactInformationStoreException;
-import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.common.errors.InvalidSshKeyException;
 import com.google.gerrit.common.errors.NoSuchEntityException;
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
@@ -30,8 +28,6 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupMember;
 import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.AccountSshKey;
-import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.client.ContactInformation;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
@@ -40,49 +36,34 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountException;
 import com.google.gerrit.server.account.AccountManager;
-import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.ChangeUserName;
-import com.google.gerrit.server.account.ClearPassword;
-import com.google.gerrit.server.account.GeneratePassword;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.Realm;
-import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.contact.ContactStore;
 import com.google.gerrit.server.mail.EmailTokenVerifier;
-import com.google.gerrit.server.mail.RegisterNewEmailSender;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.ssh.SshKeyCache;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.VoidResult;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
 class AccountSecurityImpl extends BaseServiceImplementation implements
     AccountSecurity {
-  private final Logger log = LoggerFactory.getLogger(getClass());
   private final ContactStore contactStore;
-  private final AuthConfig authConfig;
   private final Realm realm;
   private final ProjectCache projectCache;
   private final Provider<IdentifiedUser> user;
   private final EmailTokenVerifier emailTokenVerifier;
-  private final RegisterNewEmailSender.Factory registerNewEmailFactory;
-  private final SshKeyCache sshKeyCache;
   private final AccountByEmailCache byEmailCache;
   private final AccountCache accountCache;
   private final AccountManager accountManager;
   private final boolean useContactInfo;
 
-  private final ClearPassword.Factory clearPasswordFactory;
-  private final GeneratePassword.Factory generatePasswordFactory;
   private final ChangeUserName.CurrentUser changeUserNameFactory;
   private final DeleteExternalIds.Factory deleteExternalIdsFactory;
   private final ExternalIdDetailFactory.Factory externalIdDetailFactory;
@@ -93,34 +74,26 @@
   @Inject
   AccountSecurityImpl(final Provider<ReviewDb> schema,
       final Provider<CurrentUser> currentUser, final ContactStore cs,
-      final AuthConfig ac, final Realm r, final Provider<IdentifiedUser> u,
+      final Realm r, final Provider<IdentifiedUser> u,
       final EmailTokenVerifier etv, final ProjectCache pc,
-      final RegisterNewEmailSender.Factory esf, final SshKeyCache skc,
       final AccountByEmailCache abec, final AccountCache uac,
       final AccountManager am,
-      final ClearPassword.Factory clearPasswordFactory,
-      final GeneratePassword.Factory generatePasswordFactory,
       final ChangeUserName.CurrentUser changeUserNameFactory,
       final DeleteExternalIds.Factory deleteExternalIdsFactory,
       final ExternalIdDetailFactory.Factory externalIdDetailFactory,
       final ChangeHooks hooks, final GroupCache groupCache) {
     super(schema, currentUser);
     contactStore = cs;
-    authConfig = ac;
     realm = r;
     user = u;
     emailTokenVerifier = etv;
     projectCache = pc;
-    registerNewEmailFactory = esf;
-    sshKeyCache = skc;
     byEmailCache = abec;
     accountCache = uac;
     accountManager = am;
 
     useContactInfo = contactStore != null && contactStore.isEnabled();
 
-    this.clearPasswordFactory = clearPasswordFactory;
-    this.generatePasswordFactory = generatePasswordFactory;
     this.changeUserNameFactory = changeUserNameFactory;
     this.deleteExternalIdsFactory = deleteExternalIdsFactory;
     this.externalIdDetailFactory = externalIdDetailFactory;
@@ -128,60 +101,6 @@
     this.groupCache = groupCache;
   }
 
-  public void mySshKeys(final AsyncCallback<List<AccountSshKey>> callback) {
-    run(callback, new Action<List<AccountSshKey>>() {
-      public List<AccountSshKey> run(ReviewDb db) throws OrmException {
-        IdentifiedUser u = user.get();
-        return db.accountSshKeys().byAccount(u.getAccountId()).toList();
-      }
-    });
-  }
-
-  public void addSshKey(final String keyText,
-      final AsyncCallback<AccountSshKey> callback) {
-    run(callback, new Action<AccountSshKey>() {
-      public AccountSshKey run(final ReviewDb db) throws OrmException, Failure {
-        int max = 0;
-        final Account.Id me = user.get().getAccountId();
-        for (final AccountSshKey k : db.accountSshKeys().byAccount(me)) {
-          max = Math.max(max, k.getKey().get());
-        }
-
-        final AccountSshKey key;
-        try {
-          key = sshKeyCache.create(new AccountSshKey.Id(me, max + 1), keyText);
-        } catch (InvalidSshKeyException e) {
-          throw new Failure(e);
-        }
-        db.accountSshKeys().insert(Collections.singleton(key));
-        uncacheSshKeys();
-        return key;
-      }
-    });
-  }
-
-  public void deleteSshKeys(final Set<AccountSshKey.Id> ids,
-      final AsyncCallback<VoidResult> callback) {
-    run(callback, new Action<VoidResult>() {
-      public VoidResult run(final ReviewDb db) throws OrmException, Failure {
-        final Account.Id me = user.get().getAccountId();
-        for (final AccountSshKey.Id keyId : ids) {
-          if (!me.equals(keyId.getParentKey()))
-            throw new Failure(new NoSuchEntityException());
-        }
-
-        db.accountSshKeys().deleteKeys(ids);
-        uncacheSshKeys();
-
-        return VoidResult.INSTANCE;
-      }
-    });
-  }
-
-  private void uncacheSshKeys() {
-    sshKeyCache.evict(user.get().getUserName());
-  }
-
   @Override
   public void changeUserName(final String newName,
       final AsyncCallback<VoidResult> callback) {
@@ -193,18 +112,6 @@
     }
   }
 
-  @Override
-  public void generatePassword(AccountExternalId.Key key,
-      AsyncCallback<AccountExternalId> callback) {
-    Handler.wrap(generatePasswordFactory.create(key)).to(callback);
-  }
-
-  @Override
-  public void clearPassword(AccountExternalId.Key key,
-      AsyncCallback<AccountExternalId> callback) {
-    Handler.wrap(clearPasswordFactory.create(key)).to(callback);
-  }
-
   public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
     externalIdDetailFactory.create().to(callback);
   }
@@ -302,31 +209,6 @@
     });
   }
 
-  public void registerEmail(final String address,
-      final AsyncCallback<Account> cb) {
-    if (authConfig.getAuthType() == AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT) {
-      try {
-        accountManager.link(user.get().getAccountId(),
-            AuthRequest.forEmail(address));
-        cb.onSuccess(user.get().getAccount());
-      } catch (AccountException e) {
-        cb.onFailure(e);
-      }
-    } else {
-      try {
-        final RegisterNewEmailSender sender;
-        sender = registerNewEmailFactory.create(address);
-        sender.send();
-      } catch (EmailException e) {
-        log.error("Cannot send email verification message to " + address, e);
-        cb.onFailure(e);
-      } catch (RuntimeException e) {
-        log.error("Cannot send email verification message to " + address, e);
-        cb.onFailure(e);
-      }
-    }
-  }
-
   public void validateEmail(final String tokenString,
       final AsyncCallback<VoidResult> callback) {
     try {
@@ -342,6 +224,8 @@
       callback.onFailure(e);
     } catch (AccountException e) {
       callback.onFailure(e);
+    } catch (OrmException e) {
+      callback.onFailure(e);
     }
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
index 2fe3124..0a9ed28 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
@@ -154,7 +154,7 @@
         if (filter != null) {
           try {
             ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
-            builder.setAllowFile(true);
+            builder.setAllowFileRegex(true);
             builder.parse(filter);
           } catch (QueryParseException badFilter) {
             throw new InvalidQueryException(badFilter.getMessage(), filter);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
index 557e017..568971d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -135,7 +135,7 @@
         changeId));
 
     detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet());
-
+    detail.setCanCherryPick(control.getProjectControl().canUpload());
     detail.setCanEdit(control.getRefControl().canWrite());
     detail.setCanEditCommitMessage(change.getStatus().isOpen() && control.canAddPatchSet());
     detail.setCanEditTopicName(control.canEditTopicName());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java
index ba50417..2706279 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.httpd.rpc.changedetail;
 
-import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.common.errors.NoSuchEntityException;
@@ -25,18 +24,14 @@
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.config.TrackingFooters;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.validators.CommitValidators;
 import com.google.gerrit.server.mail.CommitMessageEditedSender;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.ssh.NoSshInfo;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -61,21 +56,12 @@
   private final IdentifiedUser currentUser;
   private final ChangeDetailFactory.Factory changeDetailFactory;
   private final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory;
-
-  private final GitReferenceUpdated gitRefUpdated;
-
   private final PatchSet.Id patchSetId;
   @Nullable
   private final String message;
-
-  private final ChangeHooks hooks;
-  private final CommitValidators.Factory commitValidatorsFactory;
-
   private final GitRepositoryManager gitManager;
-  private final PatchSetInfoFactory patchSetInfoFactory;
-
   private final PersonIdent myIdent;
-  private final TrackingFooters trackingFooters;
+  private final PatchSetInserter.Factory patchSetInserterFactory;
 
   @Inject
   EditCommitMessageHandler(final ChangeControl.Factory changeControlFactory,
@@ -83,29 +69,20 @@
       final ChangeDetailFactory.Factory changeDetailFactory,
       final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory,
       @Assisted final PatchSet.Id patchSetId,
-      @Assisted @Nullable final String message, final ChangeHooks hooks,
-      final CommitValidators.Factory commitValidatorsFactory,
+      @Assisted @Nullable final String message,
       final GitRepositoryManager gitManager,
-      final PatchSetInfoFactory patchSetInfoFactory,
-      final GitReferenceUpdated gitRefUpdated,
       @GerritPersonIdent final PersonIdent myIdent,
-      TrackingFooters trackingFooters) {
+      final PatchSetInserter.Factory patchSetInserterFactory) {
     this.changeControlFactory = changeControlFactory;
     this.db = db;
     this.currentUser = currentUser;
     this.changeDetailFactory = changeDetailFactory;
     this.commitMessageEditedSenderFactory = commitMessageEditedSenderFactory;
-
     this.patchSetId = patchSetId;
     this.message = message;
-    this.hooks = hooks;
-    this.commitValidatorsFactory = commitValidatorsFactory;
     this.gitManager = gitManager;
-
-    this.patchSetInfoFactory = patchSetInfoFactory;
-    this.gitRefUpdated = gitRefUpdated;
     this.myIdent = myIdent;
-    this.trackingFooters = trackingFooters;
+    this.patchSetInserterFactory = patchSetInserterFactory;
   }
 
   @Override
@@ -128,13 +105,9 @@
       throw new NoSuchChangeException(changeId, e);
     }
     try {
-      CommitValidators commitValidators =
-          commitValidatorsFactory.create(control.getRefControl(), new NoSshInfo(), git);
-
-      ChangeUtil.editCommitMessage(patchSetId, control.getRefControl(), commitValidators, currentUser, message, db,
-          commitMessageEditedSenderFactory, hooks, git, patchSetInfoFactory, gitRefUpdated, myIdent,
-          trackingFooters);
-
+      ChangeUtil.editCommitMessage(patchSetId, control.getRefControl(),
+          currentUser, message, db, commitMessageEditedSenderFactory, git,
+          myIdent, patchSetInserterFactory);
       return changeDetailFactory.create(changeId).call();
     } finally {
       git.close();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
index 8e81dd3..461a263 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
@@ -14,20 +14,29 @@
 
 package com.google.gerrit.httpd.rpc.changedetail;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.PatchSetDetail;
+import com.google.gerrit.common.data.UiCommandDetail;
 import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.client.AccountPatchReview;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.change.Revisions;
+import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListKey;
@@ -67,6 +76,7 @@
   private final ReviewDb db;
   private final PatchListCache patchListCache;
   private final ChangeControl.Factory changeControlFactory;
+  private final Revisions revisions;
 
   private Project.NameKey projectKey;
   private final PatchSet.Id psIdBase;
@@ -83,6 +93,7 @@
   PatchSetDetailFactory(final PatchSetInfoFactory psif, final ReviewDb db,
       final PatchListCache patchListCache,
       final ChangeControl.Factory changeControlFactory,
+      final Revisions revisions,
       @Assisted("psIdBase") @Nullable final PatchSet.Id psIdBase,
       @Assisted("psIdNew") final PatchSet.Id psIdNew,
       @Assisted @Nullable final AccountDiffPreference diffPrefs) {
@@ -90,6 +101,7 @@
     this.db = db;
     this.patchListCache = patchListCache;
     this.changeControlFactory = changeControlFactory;
+    this.revisions = revisions;
 
     this.psIdBase = psIdBase;
     this.psIdNew = psIdNew;
@@ -164,6 +176,22 @@
       }
     }
 
+    detail.setCommands(Lists.newArrayList(Iterables.transform(
+        UiActions.sorted(UiActions.plugins(UiActions.from(
+          revisions,
+          new RevisionResource(new ChangeResource(control), patchSet)))),
+        new Function<UiAction.Description, UiCommandDetail>() {
+          @Override
+          public UiCommandDetail apply(UiAction.Description in) {
+            UiCommandDetail r = new UiCommandDetail();
+            r.method = in.getMethod();
+            r.id = in.getId();
+            r.label = in.getLabel();
+            r.title = in.getTitle();
+            r.enabled = in.isEnabled();
+            return r;
+          }
+        })));
     return detail;
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
index b9acfa9..8558fe8 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
@@ -62,7 +62,7 @@
       EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
       MissingObjectException, IncorrectObjectTypeException, IOException,
       InvalidChangeOperationException, NoSuchProjectException {
-    rebaseChange.rebase(patchSetId, currentUser.getAccountId());
+    rebaseChange.rebase(patchSetId, currentUser);
     return changeDetailFactory.create(patchSetId.getParentKey()).call();
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java
new file mode 100644
index 0000000..f951ad3
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java
@@ -0,0 +1,32 @@
+// 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.httpd.rpc.config;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.config.ConfigCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+public class ConfigRestApiServlet extends RestApiServlet {
+  private static final long serialVersionUID = 1L;
+
+  @Inject
+  ConfigRestApiServlet(RestApiServlet.Globals globals,
+      Provider<ConfigCollection> configCollection) {
+    super(globals, configCollection);
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
index 3174757..12289aa 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.data.ReviewResult;
 import com.google.gerrit.common.errors.NoSuchEntityException;
 import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
+import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.Change;
@@ -29,7 +30,9 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
+import com.google.gerrit.server.patch.PatchScriptFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gwtjsonrpc.common.AsyncCallback;
@@ -49,6 +52,7 @@
   private final PatchScriptFactory.Factory patchScriptFactoryFactory;
   private final SaveDraft.Factory saveDraftFactory;
   private final ChangeDetailFactory.Factory changeDetailFactory;
+  private final ChangeControl.Factory changeControlFactory;
 
   @Inject
   PatchDetailServiceImpl(final Provider<ReviewDb> schema,
@@ -56,13 +60,15 @@
       final DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory,
       final PatchScriptFactory.Factory patchScriptFactoryFactory,
       final SaveDraft.Factory saveDraftFactory,
-      final ChangeDetailFactory.Factory changeDetailFactory) {
+      final ChangeDetailFactory.Factory changeDetailFactory,
+      final ChangeControl.Factory changeControlFactory) {
     super(schema, currentUser);
 
     this.deleteDraftPatchSetFactory = deleteDraftPatchSetFactory;
     this.patchScriptFactoryFactory = patchScriptFactoryFactory;
     this.saveDraftFactory = saveDraftFactory;
     this.changeDetailFactory = changeDetailFactory;
+    this.changeControlFactory = changeControlFactory;
   }
 
   public void patchScript(final Patch.Key patchKey, final PatchSet.Id psa,
@@ -72,7 +78,16 @@
       callback.onFailure(new NoSuchEntityException());
       return;
     }
-    patchScriptFactoryFactory.create(patchKey, psa, psb, dp).to(callback);
+
+    new Handler<PatchScript>() {
+      @Override
+      public PatchScript call() throws Exception {
+        Change.Id changeId = patchKey.getParentKey().getParentKey();
+        ChangeControl control = changeControlFactory.validateFor(changeId);
+        return patchScriptFactoryFactory.create(
+            control, patchKey.getFileName(), psa, psb, dp).call();
+      }
+    }.to(callback);
   }
 
   public void saveDraft(final PatchLineComment comment,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
index d1f5b24..4e69b14 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
@@ -28,11 +28,9 @@
     install(new FactoryModule() {
       @Override
       protected void configure() {
-        factory(PatchScriptFactory.Factory.class);
         factory(SaveDraft.Factory.class);
       }
     });
-    bind(PatchScriptBuilder.class);
     rpc(PatchDetailServiceImpl.class);
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
deleted file mode 100644
index d26501e..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright (C) 2009 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.httpd.rpc.project;
-
-import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.data.AddBranchResult;
-import com.google.gerrit.common.errors.InvalidRevisionException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.RefControl;
-import com.google.gerrit.server.util.MagicBranch;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-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.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.ObjectWalk;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-class AddBranch extends Handler<AddBranchResult> {
-  private static final Logger log = LoggerFactory.getLogger(AddBranch.class);
-
-  interface Factory {
-    AddBranch create(@Assisted Project.NameKey projectName,
-        @Assisted("branchName") String branchName,
-        @Assisted("startingRevision") String startingRevision);
-  }
-
-  private final ProjectControl.Factory projectControlFactory;
-  private final ListBranches.Factory listBranchesFactory;
-  private final IdentifiedUser identifiedUser;
-  private final GitRepositoryManager repoManager;
-  private final GitReferenceUpdated referenceUpdated;
-  private final ChangeHooks hooks;
-
-  private final Project.NameKey projectName;
-  private final String branchName;
-  private final String startingRevision;
-
-  @Inject
-  AddBranch(final ProjectControl.Factory projectControlFactory,
-      final ListBranches.Factory listBranchesFactory,
-      final IdentifiedUser identifiedUser,
-      final GitRepositoryManager repoManager,
-      GitReferenceUpdated referenceUpdated,
-      final ChangeHooks hooks,
-
-      @Assisted Project.NameKey projectName,
-      @Assisted("branchName") String branchName,
-      @Assisted("startingRevision") String startingRevision) {
-    this.projectControlFactory = projectControlFactory;
-    this.listBranchesFactory = listBranchesFactory;
-    this.identifiedUser = identifiedUser;
-    this.repoManager = repoManager;
-    this.referenceUpdated = referenceUpdated;
-    this.hooks = hooks;
-
-    this.projectName = projectName;
-    this.branchName = branchName;
-    this.startingRevision = startingRevision;
-  }
-
-  @Override
-  public AddBranchResult call() throws NoSuchProjectException, IOException {
-    final ProjectControl projectControl =
-        projectControlFactory.controlFor(projectName);
-
-    String refname = branchName;
-    while (refname.startsWith("/")) {
-      refname = refname.substring(1);
-    }
-    if (!refname.startsWith(Constants.R_REFS)) {
-      refname = Constants.R_HEADS + refname;
-    }
-    if (!Repository.isValidRefName(refname)) {
-      return new AddBranchResult(new AddBranchResult.Error(
-          AddBranchResult.Error.Type.INVALID_NAME, refname));
-    }
-    if (MagicBranch.isMagicBranch(refname)) {
-      return new AddBranchResult(
-          new AddBranchResult.Error(
-              AddBranchResult.Error.Type.BRANCH_CREATION_NOT_ALLOWED_UNDER_REFNAME_PREFIX,
-              MagicBranch.getMagicRefNamePrefix(refname)));
-    }
-
-    final Branch.NameKey name = new Branch.NameKey(projectName, refname);
-    final RefControl refControl = projectControl.controlForRef(name);
-    final Repository repo = repoManager.openRepository(projectName);
-    try {
-      final ObjectId revid = parseStartingRevision(repo);
-      final RevWalk rw = verifyConnected(repo, revid);
-      RevObject object = rw.parseAny(revid);
-
-      if (refname.startsWith(Constants.R_HEADS)) {
-        // Ensure that what we start the branch from is a commit. If we
-        // were given a tag, deference to the commit instead.
-        //
-        try {
-          object = rw.parseCommit(object);
-        } catch (IncorrectObjectTypeException notCommit) {
-          throw new IllegalStateException(startingRevision + " not a commit");
-        }
-      }
-
-      if (!refControl.canCreate(rw, object)) {
-        throw new IllegalStateException("Cannot create " + refname);
-      }
-
-      try {
-        final RefUpdate u = repo.updateRef(refname);
-        u.setExpectedOldObjectId(ObjectId.zeroId());
-        u.setNewObjectId(object.copy());
-        u.setRefLogIdent(identifiedUser.newRefLogIdent());
-        u.setRefLogMessage("created via web from " + startingRevision, false);
-        final RefUpdate.Result result = u.update(rw);
-        switch (result) {
-          case FAST_FORWARD:
-          case NEW:
-          case NO_CHANGE:
-            referenceUpdated.fire(name.getParentKey(), u);
-            hooks.doRefUpdatedHook(name, u, identifiedUser.getAccount());
-            break;
-          case LOCK_FAILURE:
-            if (repo.getRef(refname) != null) {
-              return new AddBranchResult(new AddBranchResult.Error(
-                  AddBranchResult.Error.Type.BRANCH_ALREADY_EXISTS, refname));
-            }
-            String refPrefix = getRefPrefix(refname);
-            while (!Constants.R_HEADS.equals(refPrefix)) {
-              if (repo.getRef(refPrefix) != null) {
-                return new AddBranchResult(new AddBranchResult.Error(
-                    AddBranchResult.Error.Type.BRANCH_CREATION_CONFLICT, refPrefix));
-              }
-              refPrefix = getRefPrefix(refPrefix);
-            }
-          default: {
-            throw new IOException(result.name());
-          }
-        }
-      } catch (IOException err) {
-        log.error("Cannot create branch " + name, err);
-        throw err;
-      }
-    } catch (InvalidRevisionException e) {
-      return new AddBranchResult(new AddBranchResult.Error(
-          AddBranchResult.Error.Type.INVALID_REVISION));
-    } finally {
-      repo.close();
-    }
-
-    return new AddBranchResult(listBranchesFactory.create(projectName).call());
-  }
-
-  private static String getRefPrefix(final String refName) {
-    final int i = refName.lastIndexOf('/');
-    if (i > Constants.R_HEADS.length() - 1) {
-      return refName.substring(0, i);
-    }
-    return Constants.R_HEADS;
-  }
-
-  private ObjectId parseStartingRevision(final Repository repo)
-      throws InvalidRevisionException {
-    try {
-      final ObjectId revid = repo.resolve(startingRevision);
-      if (revid == null) {
-        throw new InvalidRevisionException();
-      }
-      return revid;
-    } catch (IOException err) {
-      log.error("Cannot resolve \"" + startingRevision + "\" in project \""
-          + projectName + "\"", err);
-      throw new InvalidRevisionException();
-    }
-  }
-
-  private RevWalk verifyConnected(final Repository repo, final ObjectId revid)
-      throws InvalidRevisionException {
-    try {
-      final ObjectWalk rw = new ObjectWalk(repo);
-      try {
-        rw.markStart(rw.parseCommit(revid));
-      } catch (IncorrectObjectTypeException err) {
-        throw new InvalidRevisionException();
-      }
-      for (final Ref r : repo.getAllRefs().values()) {
-        try {
-          rw.markUninteresting(rw.parseAny(r.getObjectId()));
-        } catch (MissingObjectException err) {
-          continue;
-        }
-      }
-      rw.checkConnectivity();
-      return rw;
-    } catch (IncorrectObjectTypeException err) {
-      throw new InvalidRevisionException();
-    } catch (MissingObjectException err) {
-      throw new InvalidRevisionException();
-    } catch (IOException err) {
-      log.error("Repository \"" + repo.getDirectory()
-          + "\" may be corrupt; suggest running git fsck", err);
-      throw new InvalidRevisionException();
-    }
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
deleted file mode 100644
index 8f5430d..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (C) 2009 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.httpd.rpc.project;
-
-import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-class DeleteBranches extends Handler<Set<Branch.NameKey>> {
-  private static final Logger log =
-      LoggerFactory.getLogger(DeleteBranches.class);
-
-  interface Factory {
-    DeleteBranches create(@Assisted Project.NameKey name,
-        @Assisted Set<Branch.NameKey> toRemove);
-  }
-
-  private final ProjectControl.Factory projectControlFactory;
-  private final GitRepositoryManager repoManager;
-  private final GitReferenceUpdated gitRefUpdated;
-  private final IdentifiedUser identifiedUser;
-  private final ChangeHooks hooks;
-  private final ReviewDb db;
-
-  private final Project.NameKey projectName;
-  private final Set<Branch.NameKey> toRemove;
-
-  @Inject
-  DeleteBranches(final ProjectControl.Factory projectControlFactory,
-      final GitRepositoryManager repoManager,
-      final GitReferenceUpdated gitRefUpdated,
-      final IdentifiedUser identifiedUser,
-      final ChangeHooks hooks,
-      final ReviewDb db,
-
-      @Assisted Project.NameKey name, @Assisted Set<Branch.NameKey> toRemove) {
-    this.projectControlFactory = projectControlFactory;
-    this.repoManager = repoManager;
-    this.gitRefUpdated = gitRefUpdated;
-    this.identifiedUser = identifiedUser;
-    this.hooks = hooks;
-    this.db = db;
-
-    this.projectName = name;
-    this.toRemove = toRemove;
-  }
-
-  @Override
-  public Set<Branch.NameKey> call() throws NoSuchProjectException,
-      RepositoryNotFoundException, OrmException, IOException {
-    final ProjectControl projectControl =
-        projectControlFactory.controlFor(projectName);
-
-    final Iterator<Branch.NameKey> branchIt = toRemove.iterator();
-    while (branchIt.hasNext()) {
-      final Branch.NameKey k = branchIt.next();
-      if (!projectName.equals(k.getParentKey())) {
-        throw new IllegalArgumentException("All keys must be from same project");
-      }
-      if (!projectControl.controlForRef(k).canDelete()) {
-        throw new IllegalStateException("Cannot delete " + k.getShortName());
-      }
-
-      if (db.changes().byBranchOpenAll(k).iterator().hasNext()) {
-        branchIt.remove();
-      }
-    }
-
-    final Set<Branch.NameKey> deleted = new HashSet<Branch.NameKey>();
-    final Repository r = repoManager.openRepository(projectName);
-    try {
-      for (final Branch.NameKey branchKey : toRemove) {
-        final String refname = branchKey.get();
-        final RefUpdate.Result result;
-        final RefUpdate u;
-        try {
-          u = r.updateRef(refname);
-          u.setForceUpdate(true);
-          result = u.delete();
-        } catch (IOException e) {
-          log.error("Cannot delete " + branchKey, e);
-          continue;
-        }
-
-        switch (result) {
-          case NEW:
-          case NO_CHANGE:
-          case FAST_FORWARD:
-          case FORCED:
-            deleted.add(branchKey);
-            gitRefUpdated.fire(projectName, u);
-            hooks.doRefUpdatedHook(branchKey, u, identifiedUser.getAccount());
-            break;
-
-          case REJECTED_CURRENT_BRANCH:
-            log.warn("Cannot delete " + branchKey + ": " + result.name());
-            break;
-
-          default:
-            log.error("Cannot delete " + branchKey + ": " + result.name());
-            break;
-        }
-      }
-    } finally {
-      r.close();
-    }
-    return deleted;
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
deleted file mode 100644
index 2366423..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ListBranches.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright (C) 2009 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.httpd.rpc.project;
-
-import com.google.gerrit.common.data.ListBranchesResult;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.RefControl;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-class ListBranches extends Handler<ListBranchesResult> {
-  interface Factory {
-    ListBranches create(@Assisted Project.NameKey name);
-  }
-
-  private final ProjectControl.Factory projectControlFactory;
-  private final GitRepositoryManager repoManager;
-
-  private final Project.NameKey projectName;
-
-  @Inject
-  ListBranches(final ProjectControl.Factory projectControlFactory,
-      final GitRepositoryManager repoManager,
-
-      @Assisted final Project.NameKey name) {
-    this.projectControlFactory = projectControlFactory;
-    this.repoManager = repoManager;
-
-    this.projectName = name;
-  }
-
-  @Override
-  public ListBranchesResult call() throws NoSuchProjectException, IOException {
-    final ProjectControl pctl = projectControlFactory.validateFor( //
-        projectName, //
-        ProjectControl.OWNER | ProjectControl.VISIBLE);
-
-    final List<Branch> branches = new ArrayList<Branch>();
-    Branch headBranch = null;
-    Branch configBranch = null;
-    final Set<String> targets = new HashSet<String>();
-
-    final Repository db;
-    try {
-      db = repoManager.openRepository(projectName);
-    } catch (RepositoryNotFoundException noGitRepository) {
-      return new ListBranchesResult(branches, false, true);
-    }
-    try {
-      final Map<String, Ref> all = db.getAllRefs();
-
-      if (!all.containsKey(Constants.HEAD)) {
-        // The branch pointed to by HEAD doesn't exist yet, so getAllRefs
-        // filtered it out. If we ask for it individually we can find the
-        // underlying target and put it into the map anyway.
-        //
-        try {
-          Ref head = db.getRef(Constants.HEAD);
-          if (head != null) {
-            all.put(Constants.HEAD, head);
-          }
-        } catch (IOException e) {
-          // Ignore the failure reading HEAD.
-        }
-      }
-
-      for (final Ref ref : all.values()) {
-        if (ref.isSymbolic()) {
-          targets.add(ref.getTarget().getName());
-        }
-      }
-
-      for (final Ref ref : all.values()) {
-        if (ref.isSymbolic()) {
-          // A symbolic reference to another branch, instead of
-          // showing the resolved value, show the name it references.
-          //
-          String target = ref.getTarget().getName();
-          RefControl targetRefControl = pctl.controlForRef(target);
-          if (!targetRefControl.isVisible()) {
-            continue;
-          }
-          if (target.startsWith(Constants.R_HEADS)) {
-            target = target.substring(Constants.R_HEADS.length());
-          }
-
-          Branch b = createBranch(ref.getName());
-          b.setRevision(new RevId(target));
-
-          if (Constants.HEAD.equals(ref.getName())) {
-            b.setCanDelete(false);
-            headBranch = b;
-          } else {
-            b.setCanDelete(targetRefControl.canDelete());
-            branches.add(b);
-          }
-          continue;
-        }
-
-        final RefControl refControl = pctl.controlForRef(ref.getName());
-        if (refControl.isVisible()) {
-          if (ref.getName().startsWith(Constants.R_HEADS)) {
-            branches.add(createBranch(ref, refControl, targets));
-          } else if (GitRepositoryManager.REF_CONFIG.equals(ref.getName())) {
-            configBranch = createBranch(ref, refControl, targets);
-          }
-        }
-      }
-    } finally {
-      db.close();
-    }
-    Collections.sort(branches, new Comparator<Branch>() {
-      @Override
-      public int compare(final Branch a, final Branch b) {
-        return a.getName().compareTo(b.getName());
-      }
-    });
-    if (configBranch != null) {
-      branches.add(0, configBranch);
-    }
-    if (headBranch != null) {
-      branches.add(0, headBranch);
-    }
-    return new ListBranchesResult(branches, pctl.canAddRefs(), false);
-  }
-
-  private Branch createBranch(final Ref ref, final RefControl refControl,
-      final Set<String> targets) {
-    final Branch b = createBranch(ref.getName());
-    if (ref.getObjectId() != null) {
-      b.setRevision(new RevId(ref.getObjectId().name()));
-    }
-    b.setCanDelete(!targets.contains(ref.getName()) && refControl.canDelete());
-    return b;
-  }
-
-  private Branch createBranch(final String name) {
-    return new Branch(new Branch.NameKey(projectName, name));
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
index 0e46bc3..d946147 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
@@ -15,12 +15,9 @@
 package com.google.gerrit.httpd.rpc.project;
 
 import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.AddBranchResult;
-import com.google.gerrit.common.data.ListBranchesResult;
 import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.common.data.ProjectAdminService;
 import com.google.gerrit.common.data.ProjectDetail;
-import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwtjsonrpc.common.AsyncCallback;
@@ -29,35 +26,25 @@
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.util.List;
-import java.util.Set;
 
 class ProjectAdminServiceImpl implements ProjectAdminService {
-  private final AddBranch.Factory addBranchFactory;
   private final ChangeProjectAccess.Factory changeProjectAccessFactory;
   private final ReviewProjectAccess.Factory reviewProjectAccessFactory;
   private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
-  private final DeleteBranches.Factory deleteBranchesFactory;
-  private final ListBranches.Factory listBranchesFactory;
   private final VisibleProjectDetails.Factory visibleProjectDetailsFactory;
   private final ProjectAccessFactory.Factory projectAccessFactory;
   private final ProjectDetailFactory.Factory projectDetailFactory;
 
   @Inject
-  ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
-      final ChangeProjectAccess.Factory changeProjectAccessFactory,
+  ProjectAdminServiceImpl(final ChangeProjectAccess.Factory changeProjectAccessFactory,
       final ReviewProjectAccess.Factory reviewProjectAccessFactory,
       final ChangeProjectSettings.Factory changeProjectSettingsFactory,
-      final DeleteBranches.Factory deleteBranchesFactory,
-      final ListBranches.Factory listBranchesFactory,
       final VisibleProjectDetails.Factory visibleProjectDetailsFactory,
       final ProjectAccessFactory.Factory projectAccessFactory,
       final ProjectDetailFactory.Factory projectDetailFactory) {
-    this.addBranchFactory = addBranchFactory;
     this.changeProjectAccessFactory = changeProjectAccessFactory;
     this.reviewProjectAccessFactory = reviewProjectAccessFactory;
     this.changeProjectSettingsFactory = changeProjectSettingsFactory;
-    this.deleteBranchesFactory = deleteBranchesFactory;
-    this.listBranchesFactory = listBranchesFactory;
     this.visibleProjectDetailsFactory = visibleProjectDetailsFactory;
     this.projectAccessFactory = projectAccessFactory;
     this.projectDetailFactory = projectDetailFactory;
@@ -106,25 +93,4 @@
       AsyncCallback<Change.Id> cb) {
     reviewProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
   }
-
-  @Override
-  public void listBranches(final Project.NameKey projectName,
-      final AsyncCallback<ListBranchesResult> callback) {
-    listBranchesFactory.create(projectName).to(callback);
-  }
-
-  @Override
-  public void deleteBranch(final Project.NameKey projectName,
-      final Set<Branch.NameKey> toRemove,
-      final AsyncCallback<Set<Branch.NameKey>> callback) {
-    deleteBranchesFactory.create(projectName, toRemove).to(callback);
-  }
-
-  @Override
-  public void addBranch(final Project.NameKey projectName,
-      final String branchName, final String startingRevision,
-      final AsyncCallback<AddBranchResult> callback) {
-    addBranchFactory.create(projectName, branchName, startingRevision).to(
-        callback);
-  }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
index 2533feb..19d7a31 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
@@ -69,6 +69,7 @@
     detail.setCanModifyDescription(userIsOwner);
     detail.setCanModifyMergeType(userIsOwner);
     detail.setCanModifyState(userIsOwner);
+    detail.setCanModifyMaxObjectSizeLimit(userIsOwner);
 
     final InheritedBoolean useContributorAgreements = new InheritedBoolean();
     final InheritedBoolean useSignedOffBy = new InheritedBoolean();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
index 2d4f210..abea6f4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
@@ -28,12 +28,9 @@
     install(new FactoryModule() {
       @Override
       protected void configure() {
-        factory(AddBranch.Factory.class);
         factory(ChangeProjectAccess.Factory.class);
         factory(ReviewProjectAccess.Factory.class);
         factory(ChangeProjectSettings.Factory.class);
-        factory(DeleteBranches.Factory.class);
-        factory(ListBranches.Factory.class);
         factory(VisibleProjectDetails.Factory.class);
         factory(ProjectAccessFactory.Factory.class);
         factory(ProjectDetailFactory.Factory.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index 251249f..e209e29 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectControl;
@@ -63,17 +64,19 @@
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final Provider<PostReviewers> reviewersProvider;
   private final ChangeControl.GenericFactory changeFactory;
+  private final ChangeIndexer indexer;
 
   @Inject
   ReviewProjectAccess(final ProjectControl.Factory projectControlFactory,
-      final GroupBackend groupBackend,
-      final MetaDataUpdate.User metaDataUpdateFactory, final ReviewDb db,
-      final IdentifiedUser user, final PatchSetInfoFactory patchSetInfoFactory,
-      final Provider<PostReviewers> reviewersProvider,
-      final ChangeControl.GenericFactory changeFactory,
+      GroupBackend groupBackend,
+      MetaDataUpdate.User metaDataUpdateFactory, ReviewDb db,
+      IdentifiedUser user, PatchSetInfoFactory patchSetInfoFactory,
+      Provider<PostReviewers> reviewersProvider,
+      ChangeControl.GenericFactory changeFactory,
+      ChangeIndexer indexer,
 
-      @Assisted final Project.NameKey projectName,
-      @Nullable @Assisted final ObjectId base,
+      @Assisted Project.NameKey projectName,
+      @Nullable @Assisted ObjectId base,
       @Assisted List<AccessSection> sectionList,
       @Nullable @Assisted String message) {
     super(projectControlFactory, groupBackend, metaDataUpdateFactory,
@@ -83,6 +86,7 @@
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.reviewersProvider = reviewersProvider;
     this.changeFactory = changeFactory;
+    this.indexer = indexer;
   }
 
   @Override
@@ -122,6 +126,7 @@
     } finally {
       db.rollback();
     }
+    indexer.index(change);
     return changeId;
   }
 
diff --git a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/HostPage.html b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/HostPage.html
index ce100a5..9f3fa1e 100644
--- a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/HostPage.html
+++ b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/HostPage.html
@@ -44,9 +44,9 @@
       </noscript>
     </div>
     <div id="gerrit_body"></div>
-    <div style="clear: both; margin-top: 15px; padding-top: 2px; margin-bottom: 15px;">
+    <div style="clear: both">
       <div id="gerrit_footer"></div>
-      <div id="gerrit_btmmenu" style="clear: both;"></div>
+      <div id="gerrit_btmmenu"></div>
     </div>
     <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
     <script id="gerrit_module"></script>
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
deleted file mode 100644
index 5cfde6a..0000000
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/rpc/project/ListBranchesTest.java
+++ /dev/null
@@ -1,374 +0,0 @@
-// Copyright (C) 2009 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.httpd.rpc.project;
-
-import static org.easymock.EasyMock.createStrictMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-import static org.eclipse.jgit.lib.Constants.HEAD;
-import static org.eclipse.jgit.lib.Constants.R_HEADS;
-import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.google.gerrit.common.data.ListBranchesResult;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.RefControl;
-import com.google.gwtorm.client.KeyUtil;
-import com.google.gwtorm.server.StandardKeyEncoder;
-
-import org.easymock.IExpectationSetters;
-import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.SymbolicRef;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class ListBranchesTest extends LocalDiskRepositoryTestCase {
-  static {
-    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
-  }
-
-  private ObjectId idA;
-  private Project.NameKey name;
-  private Repository realDb;
-  private Repository mockDb;
-  private ProjectControl.Factory pcf;
-  private ProjectControl pc;
-  private GitRepositoryManager grm;
-  private List<RefControl> refMocks;
-
-  @Override
-  @Before
-  public void setUp() throws Exception {
-    super.setUp();
-
-    idA = ObjectId.fromString("df84c2f4f7ce7e0b25cdeac84b8870bcff319885");
-    name = new Project.NameKey("test");
-    realDb = createBareRepository();
-
-    mockDb = createStrictMock(Repository.class);
-    pc = createStrictMock(ProjectControl.class);
-    pcf = createStrictMock(ProjectControl.Factory.class);
-    grm = createStrictMock(GitRepositoryManager.class);
-    refMocks = new ArrayList<RefControl>();
-  }
-
-  private IExpectationSetters<ProjectControl> validate()
-      throws NoSuchProjectException {
-    return expect(pcf.validateFor(eq(name), //
-        eq(ProjectControl.OWNER | ProjectControl.VISIBLE)));
-  }
-
-  private void doReplay() {
-    replay(mockDb, pc, pcf, grm);
-    replay(refMocks.toArray());
-  }
-
-  private void doVerify() {
-    verify(mockDb, pc, pcf, grm);
-    verify(refMocks.toArray());
-  }
-
-  private void set(String branch, ObjectId id) throws IOException {
-    final RefUpdate u = realDb.updateRef(R_HEADS + branch);
-    u.setForceUpdate(true);
-    u.setNewObjectId(id);
-    switch (u.update()) {
-      case NEW:
-      case FAST_FORWARD:
-      case FORCED:
-        break;
-      default:
-        fail("unexpected update failure " + branch + " " + u.getResult());
-    }
-  }
-
-  @Test
-  public void testProjectNotVisible() throws Exception {
-    final NoSuchProjectException err = new NoSuchProjectException(name);
-    validate().andThrow(err);
-    doReplay();
-    try {
-      new ListBranches(pcf, grm, name).call();
-      fail("did not throw when expected not authorized");
-    } catch (NoSuchProjectException e2) {
-      assertSame(err, e2);
-    }
-    doVerify();
-  }
-
-
-  private ListBranchesResult permitted(boolean getHead)
-      throws NoSuchProjectException, IOException {
-    Map<String, Ref> refs = realDb.getAllRefs();
-
-    validate().andReturn(pc);
-
-    expect(grm.openRepository(eq(name))).andReturn(mockDb);
-    expect(mockDb.getAllRefs()).andDelegateTo(realDb);
-    if (getHead) {
-      expect(mockDb.getRef(HEAD)).andDelegateTo(realDb);
-      if (!refs.containsKey(HEAD) && realDb.getRef(HEAD) != null) {
-        refs.put(HEAD, realDb.getRef(HEAD));
-      }
-    }
-
-    Set<String> targets = targets(refs);
-    for (Ref ref : refs.values()) {
-      assumeVisible(ref, true, targets);
-    }
-
-    mockDb.close();
-
-    expect(pc.canAddRefs()).andReturn(true);
-
-    expectLastCall();
-
-    doReplay();
-    final ListBranchesResult r = new ListBranches(pcf, grm, name).call();
-    doVerify();
-    assertNotNull(r);
-    assertNotNull(r.getBranches());
-    return r;
-  }
-
-  private Set<String> targets(Map<String, Ref> refs) {
-    Set<String> targets = new HashSet<String>();
-    for (Ref ref : refs.values()) {
-      if (ref.isSymbolic()) {
-        targets.add(ref.getLeaf().getName());
-      }
-    }
-    return targets;
-  }
-
-  private void assumeVisible(Ref ref, boolean visible, Set<String> targets) {
-    RefControl rc = createStrictMock(RefControl.class);
-    refMocks.add(rc);
-    expect(rc.isVisible()).andReturn(visible);
-    if (visible && !ref.isSymbolic() && !targets.contains(ref.getName())) {
-      expect(rc.canDelete()).andReturn(true);
-    }
-
-    if (ref.isSymbolic()) {
-      expect(pc.controlForRef(ref.getTarget().getName())).andReturn(rc);
-    } else {
-      expect(pc.controlForRef(ref.getName())).andReturn(rc);
-    }
-  }
-
-  @Test
-  public void testEmptyProject() throws Exception {
-    ListBranchesResult r = permitted(true);
-
-    assertEquals(1, r.getBranches().size());
-
-    Branch b = r.getBranches().get(0);
-    assertNotNull(b);
-
-    assertNotNull(b.getNameKey());
-    assertSame(name, b.getNameKey().getParentKey());
-    assertEquals(HEAD, b.getNameKey().get());
-
-    assertEquals(HEAD, b.getName());
-    assertEquals(HEAD, b.getShortName());
-
-    assertNotNull(b.getRevision());
-    assertEquals("master", b.getRevision().get());
-  }
-
-  @Test
-  public void testMasterBranch() throws Exception {
-    set("master", idA);
-
-    ListBranchesResult r = permitted(false);
-    assertEquals(2, r.getBranches().size());
-
-    Branch b = r.getBranches().get(0);
-    assertNotNull(b);
-
-    assertNotNull(b.getNameKey());
-    assertSame(name, b.getNameKey().getParentKey());
-    assertEquals(HEAD, b.getNameKey().get());
-
-    assertEquals(HEAD, b.getName());
-    assertEquals(HEAD, b.getShortName());
-
-    assertNotNull(b.getRevision());
-    assertEquals("master", b.getRevision().get());
-
-    b = r.getBranches().get(1);
-    assertNotNull(b);
-
-    assertNotNull(b.getNameKey());
-    assertSame(name, b.getNameKey().getParentKey());
-    assertEquals(R_HEADS + "master", b.getNameKey().get());
-
-    assertEquals(R_HEADS + "master", b.getName());
-    assertEquals("master", b.getShortName());
-
-    assertNotNull(b.getRevision());
-    assertEquals(idA.name(), b.getRevision().get());
-  }
-
-  @Test
-  public void testBranchNotHead() throws Exception {
-    set("foo", idA);
-
-    ListBranchesResult r = permitted(true);
-    assertEquals(2, r.getBranches().size());
-
-    Branch b = r.getBranches().get(0);
-    assertNotNull(b);
-
-    assertNotNull(b.getNameKey());
-    assertSame(name, b.getNameKey().getParentKey());
-    assertEquals(HEAD, b.getNameKey().get());
-
-    assertEquals(HEAD, b.getName());
-    assertEquals(HEAD, b.getShortName());
-
-    assertNotNull(b.getRevision());
-    assertEquals("master", b.getRevision().get());
-    assertFalse(b.getCanDelete());
-
-    b = r.getBranches().get(1);
-    assertNotNull(b);
-
-    assertNotNull(b.getNameKey());
-    assertSame(name, b.getNameKey().getParentKey());
-    assertEquals(R_HEADS + "foo", b.getNameKey().get());
-
-    assertEquals(R_HEADS + "foo", b.getName());
-    assertEquals("foo", b.getShortName());
-
-    assertNotNull(b.getRevision());
-    assertEquals(idA.name(), b.getRevision().get());
-    assertTrue(b.getCanDelete());
-  }
-
-  @Test
-  public void testSortByName() throws Exception {
-    Map<String, Ref> u = new LinkedHashMap<String, Ref>();
-    u.put("foo", new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "foo", idA));
-    u.put("bar", new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "bar", idA));
-    u.put(HEAD, new SymbolicRef(HEAD, new ObjectIdRef.Unpeeled(LOOSE, R_HEADS
-        + "master", null)));
-
-    validate().andReturn(pc);
-    expect(grm.openRepository(eq(name))).andReturn(mockDb);
-    expect(mockDb.getAllRefs()).andReturn(u);
-    for (Ref ref : u.values()) {
-      assumeVisible(ref, true, targets(u));
-    }
-    expect(pc.canAddRefs()).andReturn(true);
-    mockDb.close();
-    expectLastCall();
-
-    doReplay();
-    final ListBranchesResult r = new ListBranches(pcf, grm, name).call();
-    doVerify();
-    assertNotNull(r);
-
-    assertEquals(3, r.getBranches().size());
-    assertEquals(HEAD, r.getBranches().get(0).getShortName());
-    assertEquals("bar", r.getBranches().get(1).getShortName());
-    assertEquals("foo", r.getBranches().get(2).getShortName());
-  }
-
-  @Test
-  public void testHeadNotVisible() throws Exception {
-    ObjectIdRef.Unpeeled bar =
-        new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "bar", idA);
-    Map<String, Ref> u = new LinkedHashMap<String, Ref>();
-    u.put(bar.getName(), bar);
-    u.put(HEAD, new SymbolicRef(HEAD, bar));
-
-    validate().andReturn(pc);
-    expect(grm.openRepository(eq(name))).andReturn(mockDb);
-    expect(mockDb.getAllRefs()).andReturn(u);
-    assumeVisible(bar, false, targets(u));
-    assumeVisible(bar, false, targets(u));
-    expect(pc.canAddRefs()).andReturn(true);
-    mockDb.close();
-    expectLastCall();
-
-    doReplay();
-    final ListBranchesResult r = new ListBranches(pcf, grm, name).call();
-    doVerify();
-    assertNotNull(r);
-    assertTrue(r.getBranches().isEmpty());
-  }
-
-  @Test
-  public void testHeadVisibleButBranchHidden() throws Exception {
-    ObjectIdRef.Unpeeled bar =
-        new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "bar", idA);
-    ObjectIdRef.Unpeeled foo =
-        new ObjectIdRef.Unpeeled(LOOSE, R_HEADS + "foo", idA);
-
-    Map<String, Ref> u = new LinkedHashMap<String, Ref>();
-    u.put(bar.getName(), bar);
-    u.put(HEAD, new SymbolicRef(HEAD, bar));
-    u.put(foo.getName(), foo);
-
-    validate().andReturn(pc);
-    expect(grm.openRepository(eq(name))).andReturn(mockDb);
-    expect(mockDb.getAllRefs()).andReturn(u);
-    assumeVisible(bar, true, targets(u));
-    assumeVisible(bar, true, targets(u));
-    assumeVisible(foo, false, targets(u));
-    expect(pc.canAddRefs()).andReturn(true);
-    mockDb.close();
-    expectLastCall();
-
-    doReplay();
-    final ListBranchesResult r = new ListBranches(pcf, grm, name).call();
-    doVerify();
-    assertNotNull(r);
-
-    assertEquals(2, r.getBranches().size());
-
-    assertEquals(HEAD, r.getBranches().get(0).getShortName());
-    assertFalse(r.getBranches().get(0).getCanDelete());
-
-    assertEquals("bar", r.getBranches().get(1).getShortName());
-    assertFalse(r.getBranches().get(1).getCanDelete());
-  }
-}
diff --git a/gerrit-launcher/BUCK b/gerrit-launcher/BUCK
new file mode 100644
index 0000000..8e6cb4c
--- /dev/null
+++ b/gerrit-launcher/BUCK
@@ -0,0 +1,9 @@
+java_library(
+  name = 'launcher',
+  srcs = glob(['src/main/java/**/*.java']),
+  visibility = [
+    '//gerrit-acceptance-tests:',
+    '//gerrit-main:main_lib',
+    '//gerrit-pgm:',
+  ],
+)
diff --git a/gerrit-launcher/pom.xml b/gerrit-launcher/pom.xml
deleted file mode 100644
index 396c09c..0000000
--- a/gerrit-launcher/pom.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-launcher</artifactId>
-  <name>Gerrit Code Review - Launcher</name>
-
-  <description>
-    Bootstraps the rest of our classpath after Main
-  </description>
-</project>
diff --git a/gerrit-lucene/BUCK b/gerrit-lucene/BUCK
new file mode 100644
index 0000000..b5e5d07
--- /dev/null
+++ b/gerrit-lucene/BUCK
@@ -0,0 +1,39 @@
+QUERY_BUILDER = [
+  'src/main/java/com/google/gerrit/lucene/QueryBuilder.java',
+]
+
+java_library(
+  name = 'query_builder',
+  srcs = QUERY_BUILDER,
+  deps = [
+    '//gerrit-antlr:query_exception',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//lib:gwtorm',
+    '//lib:guava',
+    '//lib/lucene:core',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'lucene',
+  srcs = glob(['src/main/java/**/*.java'], excludes = QUERY_BUILDER),
+  deps = [
+    ':query_builder',
+    '//gerrit-antlr:query_exception',
+    '//gerrit-extension-api:api',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:jsr305',
+    '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
+    '//lib/jgit:jgit',
+    '//lib/log:api',
+    '//lib/lucene:analyzers-common',
+    '//lib/lucene:core',
+  ],
+  visibility = ['PUBLIC'],
+)
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
new file mode 100644
index 0000000..2494e2c
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -0,0 +1,397 @@
+// 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.lucene;
+
+import static com.google.gerrit.server.index.IndexRewriteImpl.CLOSED_STATUSES;
+import static com.google.gerrit.server.index.IndexRewriteImpl.OPEN_STATUSES;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
+import com.google.gerrit.server.index.FieldType;
+import com.google.gerrit.server.index.IndexExecutor;
+import com.google.gerrit.server.index.IndexRewriteImpl;
+import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.IntField;
+import org.apache.lucene.document.LongField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.SearcherManager;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.util.Version;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import javax.annotation.Nullable;
+
+/**
+ * Secondary index implementation using Apache Lucene.
+ * <p>
+ * Writes are managed using a single {@link IndexWriter} per process, committed
+ * aggressively. Reads use {@link SearcherManager} and periodically refresh,
+ * though there may be some lag between a committed write and it showing up to
+ * other threads' searchers.
+ */
+public class LuceneChangeIndex implements ChangeIndex {
+  private static final Logger log =
+      LoggerFactory.getLogger(LuceneChangeIndex.class);
+
+  public static final Version LUCENE_VERSION = Version.LUCENE_43;
+  public static final String CHANGES_OPEN = "open";
+  public static final String CHANGES_CLOSED = "closed";
+  private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
+
+  static interface Factory {
+    LuceneChangeIndex create(Schema<ChangeData> schema, String base);
+  }
+
+  private static IndexWriterConfig getIndexWriterConfig(Config cfg, String name) {
+    IndexWriterConfig writerConfig = new IndexWriterConfig(LUCENE_VERSION,
+        new StandardAnalyzer(LUCENE_VERSION));
+    writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
+    double m = 1 << 20;
+    writerConfig.setRAMBufferSizeMB(cfg.getLong("index", name, "ramBufferSize",
+          (long) (IndexWriterConfig.DEFAULT_RAM_BUFFER_SIZE_MB * m)) / m);
+    writerConfig.setMaxBufferedDocs(cfg.getInt("index", name, "maxBufferedDocs",
+          IndexWriterConfig.DEFAULT_MAX_BUFFERED_DOCS));
+    return writerConfig;
+  }
+
+  private final SitePaths sitePaths;
+  private final FillArgs fillArgs;
+  private final ExecutorService executor;
+  private final File dir;
+  private final Schema<ChangeData> schema;
+  private final SubIndex openIndex;
+  private final SubIndex closedIndex;
+
+  @AssistedInject
+  LuceneChangeIndex(
+      @GerritServerConfig Config cfg,
+      SitePaths sitePaths,
+      @IndexExecutor ListeningScheduledExecutorService executor,
+      FillArgs fillArgs,
+      @Assisted Schema<ChangeData> schema,
+      @Assisted @Nullable String base) throws IOException {
+    this.sitePaths = sitePaths;
+    this.fillArgs = fillArgs;
+    this.executor = executor;
+    this.schema = schema;
+
+    if (base == null) {
+      dir = LuceneVersionManager.getDir(sitePaths, schema);
+    } else {
+      dir = new File(base);
+    }
+    openIndex = new SubIndex(new File(dir, CHANGES_OPEN),
+        getIndexWriterConfig(cfg, "changes_open"));
+    closedIndex = new SubIndex(new File(dir, CHANGES_CLOSED),
+        getIndexWriterConfig(cfg, "changes_closed"));
+  }
+
+  @Override
+  public void close() {
+    List<Future<?>> closeFutures = Lists.newArrayListWithCapacity(2);
+    closeFutures.add(executor.submit(new Runnable() {
+      @Override
+      public void run() {
+        openIndex.close();
+      }
+    }));
+    closeFutures.add(executor.submit(new Runnable() {
+      @Override
+      public void run() {
+        closedIndex.close();
+      }
+    }));
+    for (Future<?> future : closeFutures) {
+      Futures.getUnchecked(future);
+    }
+  }
+
+  @Override
+  public Schema<ChangeData> getSchema() {
+    return schema;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public ListenableFuture<Void> insert(ChangeData cd) throws IOException {
+    Term id = QueryBuilder.idTerm(cd);
+    Document doc = toDocument(cd);
+    if (cd.getChange().getStatus().isOpen()) {
+      return allOf(
+          closedIndex.delete(id),
+          openIndex.insert(doc));
+    } else {
+      return allOf(
+          openIndex.delete(id),
+          closedIndex.insert(doc));
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public ListenableFuture<Void> replace(ChangeData cd) throws IOException {
+    Term id = QueryBuilder.idTerm(cd);
+    Document doc = toDocument(cd);
+    if (cd.getChange().getStatus().isOpen()) {
+      return allOf(
+          closedIndex.delete(id),
+          openIndex.replace(id, doc));
+    } else {
+      return allOf(
+          openIndex.delete(id),
+          closedIndex.replace(id, doc));
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public ListenableFuture<Void> delete(ChangeData cd) throws IOException {
+    Term id = QueryBuilder.idTerm(cd);
+    return allOf(
+        openIndex.delete(id),
+        closedIndex.delete(id));
+  }
+
+  private static <V> ListenableFuture<Void> allOf(ListenableFuture<V>... f) {
+    return Futures.transform(
+        Futures.allAsList(f),
+        new Function<List<V>, Void>() {
+          @Override
+          public Void apply(List<V> input) {
+            return null;
+          }
+        });
+  }
+
+  @Override
+  public void deleteAll() throws IOException {
+    openIndex.deleteAll();
+    closedIndex.deleteAll();
+  }
+
+  @Override
+  public ChangeDataSource getSource(Predicate<ChangeData> p)
+      throws QueryParseException {
+    Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
+    List<SubIndex> indexes = Lists.newArrayListWithCapacity(2);
+    if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
+      indexes.add(openIndex);
+    }
+    if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
+      indexes.add(closedIndex);
+    }
+    return new QuerySource(indexes, QueryBuilder.toQuery(p));
+  }
+
+  @Override
+  public void markReady(boolean ready) throws IOException {
+    try {
+      FileBasedConfig cfg = LuceneVersionManager.loadGerritIndexConfig(sitePaths);
+      LuceneVersionManager.setReady(cfg, schema.getVersion(), ready);
+      cfg.save();
+    } catch (ConfigInvalidException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private static class QuerySource implements ChangeDataSource {
+    // TODO(dborowitz): Push limit down from predicate tree.
+    private static final int LIMIT = 1000;
+    private static final ImmutableSet<String> FIELDS = ImmutableSet.of(ID_FIELD);
+
+    private final List<SubIndex> indexes;
+    private final Query query;
+
+    public QuerySource(List<SubIndex> indexes, Query query) {
+      this.indexes = indexes;
+      this.query = query;
+    }
+
+    @Override
+    public int getCardinality() {
+      return 10; // TODO(dborowitz): estimate from Lucene?
+    }
+
+    @Override
+    public boolean hasChange() {
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return query.toString();
+    }
+
+    @Override
+    public ResultSet<ChangeData> read() throws OrmException {
+      IndexSearcher[] searchers = new IndexSearcher[indexes.size()];
+      Sort sort = new Sort(
+          new SortField(
+              ChangeField.UPDATED.getName(),
+              SortField.Type.INT,
+              true /* descending */));
+      try {
+        TopDocs[] hits = new TopDocs[indexes.size()];
+        for (int i = 0; i < indexes.size(); i++) {
+          searchers[i] = indexes.get(i).acquire();
+          hits[i] = searchers[i].search(query, LIMIT, sort);
+        }
+        TopDocs docs = TopDocs.merge(sort, LIMIT, hits);
+
+        List<ChangeData> result =
+            Lists.newArrayListWithCapacity(docs.scoreDocs.length);
+        for (ScoreDoc sd : docs.scoreDocs) {
+          Document doc = searchers[sd.shardIndex].doc(sd.doc, FIELDS);
+          Number v = doc.getField(ID_FIELD).numericValue();
+          result.add(new ChangeData(new Change.Id(v.intValue())));
+        }
+
+        final List<ChangeData> r = Collections.unmodifiableList(result);
+        return new ResultSet<ChangeData>() {
+          @Override
+          public Iterator<ChangeData> iterator() {
+            return r.iterator();
+          }
+
+          @Override
+          public List<ChangeData> toList() {
+            return r;
+          }
+
+          @Override
+          public void close() {
+            // Do nothing.
+          }
+        };
+      } catch (IOException e) {
+        throw new OrmException(e);
+      } finally {
+        for (int i = 0; i < indexes.size(); i++) {
+          if (searchers[i] != null) {
+            try {
+              indexes.get(i).release(searchers[i]);
+            } catch (IOException e) {
+              log.warn("cannot release Lucene searcher", e);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private Document toDocument(ChangeData cd) throws IOException {
+    try {
+      Document result = new Document();
+      for (FieldDef<ChangeData, ?> f : schema.getFields().values()) {
+        if (f.isRepeatable()) {
+          add(result, f, (Iterable<?>) f.get(cd, fillArgs));
+        } else {
+          Object val = f.get(cd, fillArgs);
+          if (val != null) {
+            add(result, f, Collections.singleton(val));
+          }
+        }
+      }
+      return result;
+    } catch (OrmException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private void add(Document doc, FieldDef<ChangeData, ?> f,
+      Iterable<?> values) throws OrmException {
+    String name = f.getName();
+    Store store = store(f);
+
+    if (f.getType() == FieldType.INTEGER) {
+      for (Object value : values) {
+        doc.add(new IntField(name, (Integer) value, store));
+      }
+    } else if (f.getType() == FieldType.LONG) {
+      for (Object value : values) {
+        doc.add(new LongField(name, (Long) value, store));
+      }
+    } else if (f.getType() == FieldType.TIMESTAMP) {
+      for (Object v : values) {
+        int t = QueryBuilder.toIndexTime((Timestamp) v);
+        doc.add(new IntField(name, t, store));
+      }
+    } else if (f.getType() == FieldType.EXACT
+        || f.getType() == FieldType.PREFIX) {
+      for (Object value : values) {
+        doc.add(new StringField(name, (String) value, store));
+      }
+    } else if (f.getType() == FieldType.FULL_TEXT) {
+      for (Object value : values) {
+        doc.add(new TextField(name, (String) value, store));
+      }
+    } else {
+      throw QueryBuilder.badFieldType(f.getType());
+    }
+  }
+
+  private static Field.Store store(FieldDef<?, ?> f) {
+    return f.isStored() ? Field.Store.YES : Field.Store.NO;
+  }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
new file mode 100644
index 0000000..4e8f3f9
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -0,0 +1,115 @@
+// 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.lucene;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeSchemas;
+import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.IndexModule;
+import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+
+public class LuceneIndexModule extends LifecycleModule {
+  private final Integer singleVersion;
+  private final int threads;
+  private final String base;
+
+  public LuceneIndexModule() {
+    this(null, 0, null);
+  }
+
+  public LuceneIndexModule(Integer singleVersion, int threads,
+      String base) {
+    this.singleVersion = singleVersion;
+    this.threads = threads;
+    this.base = base;
+  }
+
+  @Override
+  protected void configure() {
+    install(new FactoryModule() {
+      @Override
+      public void configure() {
+        factory(LuceneChangeIndex.Factory.class);
+      }
+    });
+    install(new IndexModule(threads));
+    if (singleVersion == null && base == null) {
+      install(new MultiVersionModule());
+    } else {
+      install(new SingleVersionModule());
+    }
+  }
+
+  private class MultiVersionModule extends LifecycleModule {
+    @Override
+    public void configure() {
+      install(new FactoryModule() {
+        @Override
+        public void configure() {
+          factory(OnlineReindexer.Factory.class);
+        }
+      });
+      listener().to(LuceneVersionManager.class);
+    }
+  }
+
+  private class SingleVersionModule extends LifecycleModule {
+    @Override
+    public void configure() {
+      listener().to(SingleVersionListener.class);
+    }
+
+    @Provides
+    @Singleton
+    LuceneChangeIndex getIndex(LuceneChangeIndex.Factory factory,
+        SitePaths sitePaths) {
+      Schema<ChangeData> schema = singleVersion != null
+          ? ChangeSchemas.get(singleVersion)
+          : ChangeSchemas.getLatest();
+      return factory.create(schema, base);
+    }
+  }
+
+  @Singleton
+  static class SingleVersionListener implements LifecycleListener {
+    private final IndexCollection indexes;
+    private final LuceneChangeIndex index;
+
+    @Inject
+    SingleVersionListener(IndexCollection indexes,
+        LuceneChangeIndex index) {
+      this.indexes = indexes;
+      this.index = index;
+    }
+
+    @Override
+    public void start() {
+      indexes.setSearchIndex(index);
+      indexes.addWriteIndex(index);
+    }
+
+    @Override
+    public void stop() {
+      index.close();
+    }
+  }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
new file mode 100644
index 0000000..91dd015
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
@@ -0,0 +1,217 @@
+// 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.git;
+
+package com.google.gerrit.lucene;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeSchemas;
+import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.TreeMap;
+
+@Singleton
+class LuceneVersionManager implements LifecycleListener {
+  private static final Logger log = LoggerFactory
+      .getLogger(LuceneVersionManager.class);
+
+  private static final String CHANGES_PREFIX = "changes_";
+
+  private static class Version {
+    private final Schema<ChangeData> schema;
+    private final int version;
+    private final boolean exists;
+    private final boolean ready;
+
+    private Version(Schema<ChangeData> schema, int version, boolean exists,
+        boolean ready) {
+      checkArgument(schema == null || schema.getVersion() == version);
+      this.schema = schema;
+      this.version = version;
+      this.exists = exists;
+      this.ready = ready;
+    }
+  }
+
+  static File getDir(SitePaths sitePaths, Schema<ChangeData> schema) {
+    return new File(sitePaths.index_dir, String.format("%s%04d",
+        CHANGES_PREFIX, schema.getVersion()));
+  }
+
+  static FileBasedConfig loadGerritIndexConfig(SitePaths sitePaths)
+      throws ConfigInvalidException, IOException {
+    FileBasedConfig cfg = new FileBasedConfig(
+        new File(sitePaths.index_dir, "gerrit_index.config"), FS.detect());
+    cfg.load();
+    return cfg;
+  }
+
+  static void setReady(Config cfg, int version, boolean ready) {
+    cfg.setBoolean("index", Integer.toString(version), "ready", ready);
+  }
+
+  private static boolean getReady(Config cfg, int version) {
+    return cfg.getBoolean("index", Integer.toString(version), "ready", false);
+  }
+
+  private final SitePaths sitePaths;
+  private final LuceneChangeIndex.Factory indexFactory;
+  private final IndexCollection indexes;
+  private final OnlineReindexer.Factory reindexerFactory;
+
+  @Inject
+  LuceneVersionManager(
+      SitePaths sitePaths,
+      LuceneChangeIndex.Factory indexFactory,
+      IndexCollection indexes,
+      OnlineReindexer.Factory reindexerFactory) {
+    this.sitePaths = sitePaths;
+    this.indexFactory = indexFactory;
+    this.indexes = indexes;
+    this.reindexerFactory = reindexerFactory;
+  }
+
+  @Override
+  public void start() {
+    FileBasedConfig cfg;
+    try {
+      cfg = loadGerritIndexConfig(sitePaths);
+    } catch (ConfigInvalidException e) {
+      throw fail(e);
+    } catch (IOException e) {
+      throw fail(e);
+    }
+
+    TreeMap<Integer, Version> versions = scanVersions(cfg);
+    // Search from the most recent ready version.
+    // Write to the most recent ready version and the most recent version.
+    Version search = null;
+    List<Version> write = Lists.newArrayListWithCapacity(2);
+    for (Version v : versions.descendingMap().values()) {
+      if (v.schema == null) {
+        continue;
+      }
+      if (write.isEmpty()) {
+        write.add(v);
+      }
+      if (v.ready) {
+        search = v;
+        if (!write.contains(v)) {
+          write.add(v);
+        }
+        break;
+      }
+    }
+    if (search == null) {
+      throw new ProvisionException("No index versions ready; run Reindex");
+    }
+
+    markNotReady(cfg, versions.values(), write);
+    LuceneChangeIndex searchIndex = indexFactory.create(search.schema, null);
+    indexes.setSearchIndex(searchIndex);
+    for (Version v : write) {
+      if (v.schema != null) {
+        if (v.version != search.version) {
+          indexes.addWriteIndex(indexFactory.create(v.schema, null));
+        } else {
+          indexes.addWriteIndex(searchIndex);
+        }
+      }
+    }
+
+    int latest = write.get(0).version;
+    if (latest != search.version) {
+      reindexerFactory.create(latest).start();
+    }
+  }
+
+  private TreeMap<Integer, Version> scanVersions(Config cfg) {
+    TreeMap<Integer, Version> versions = Maps.newTreeMap();
+    for (Schema<ChangeData> schema : ChangeSchemas.ALL.values()) {
+      File f = getDir(sitePaths, schema);
+      boolean exists = f.exists() && f.isDirectory();
+      if (exists && !f.isDirectory()) {
+        log.warn("Not a directory: %s", f.getAbsolutePath());
+      }
+      int v = schema.getVersion();
+      versions.put(v, new Version(schema, v, exists, getReady(cfg, v)));
+    }
+
+    for (File f : sitePaths.index_dir.listFiles()) {
+      if (!f.getName().startsWith(CHANGES_PREFIX)) {
+        continue;
+      }
+      String versionStr = f.getName().substring(CHANGES_PREFIX.length());
+      Integer v = Ints.tryParse(versionStr);
+      if (v == null || versionStr.length() != 4) {
+        log.warn("Unrecognized version in index directory: {}",
+            f.getAbsolutePath());
+        continue;
+      }
+      if (!versions.containsKey(v)) {
+        versions.put(v, new Version(null, v, true, getReady(cfg, v)));
+      }
+    }
+    return versions;
+  }
+
+  private void markNotReady(FileBasedConfig cfg, Iterable<Version> versions,
+      Collection<Version> inUse) {
+    boolean dirty = false;
+    for (Version v : versions) {
+      if (!inUse.contains(v) && v.exists) {
+        setReady(cfg, v.version, false);
+        dirty = true;
+      }
+    }
+    if (dirty) {
+      try {
+        cfg.save();
+      } catch (IOException e) {
+        throw fail(e);
+      }
+    }
+  }
+
+  private ProvisionException fail(Throwable t) {
+    ProvisionException e = new ProvisionException("Error scanning indexes");
+    e.initCause(t);
+    throw e;
+  }
+
+  @Override
+  public void stop() {
+  }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
new file mode 100644
index 0000000..08338eb
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/OnlineReindexer.java
@@ -0,0 +1,109 @@
+// 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.git;
+
+package com.google.gerrit.lucene;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.server.index.ChangeBatchIndexer;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+public class OnlineReindexer {
+  private static final Logger log = LoggerFactory
+      .getLogger(OnlineReindexer.class);
+
+  public interface Factory {
+    OnlineReindexer create(int version);
+  }
+
+  private final IndexCollection indexes;
+  private final ChangeBatchIndexer batchIndexer;
+  private final ProjectCache projectCache;
+  private final int version;
+
+  @Inject
+  OnlineReindexer(
+      IndexCollection indexes,
+      ChangeBatchIndexer batchIndexer,
+      ProjectCache projectCache,
+      @Assisted int version) {
+    this.indexes = indexes;
+    this.batchIndexer = batchIndexer;
+    this.projectCache = projectCache;
+    this.version = version;
+  }
+
+  public void start() {
+    Thread t = new Thread() {
+      @Override
+      public void run() {
+        reindex();
+      }
+    };
+    t.setName(String.format("Reindex v%d-v%d",
+        version(indexes.getSearchIndex()), version));
+    t.start();
+  }
+
+  private static int version(ChangeIndex i) {
+    return i.getSchema().getVersion();
+  }
+
+  private void reindex() {
+    ChangeIndex index = checkNotNull(indexes.getWriteIndex(version),
+        "not an active write schema version: %s", version);
+    log.info("Starting online reindex from schema version {} to {}",
+        version(indexes.getSearchIndex()), version(index));
+    ChangeBatchIndexer.Result result = batchIndexer.indexAll(
+        index, projectCache.all(), -1, -1, null, null);
+    if (!result.success()) {
+      log.error("Online reindex of schema version {} failed", version(index));
+      return;
+    }
+
+    indexes.setSearchIndex(index);
+    log.info("Reindex complete, using schema version {}", version(index));
+    try {
+      index.markReady(true);
+    } catch (IOException e) {
+      log.warn("Error activating new schema version {}", version(index));
+    }
+
+    List<ChangeIndex> toRemove = Lists.newArrayListWithExpectedSize(1);
+    for (ChangeIndex i : indexes.getWriteIndexes()) {
+      if (version(i) != version(index)) {
+        toRemove.add(i);
+      }
+    }
+    for (ChangeIndex i : toRemove) {
+      try {
+        i.markReady(false);
+        indexes.removeWriteIndex(version(i));
+      } catch (IOException e) {
+        log.warn("Error deactivating old schema version {}", version(i));
+      }
+    }
+  }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
new file mode 100644
index 0000000..f491bc2
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -0,0 +1,232 @@
+// 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.lucene;
+
+import static org.apache.lucene.search.BooleanClause.Occur.MUST;
+import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
+import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.FieldType;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.RegexPredicate;
+import com.google.gerrit.server.index.TimestampRangePredicate;
+import com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.NotPredicate;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.SortKeyPredicate;
+
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.FuzzyQuery;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.NumericRangeQuery;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.RegexpQuery;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+public class QueryBuilder {
+  private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
+
+  public static Term idTerm(ChangeData cd) {
+    return intTerm(ID_FIELD, cd.getId().get());
+  }
+
+  public static Query toQuery(Predicate<ChangeData> p)
+      throws QueryParseException {
+    if (p instanceof AndPredicate) {
+      return and(p);
+    } else if (p instanceof OrPredicate) {
+      return or(p);
+    } else if (p instanceof NotPredicate) {
+      return not(p);
+    } else if (p instanceof IndexPredicate) {
+      return fieldQuery((IndexPredicate<ChangeData>) p);
+    } else {
+      throw new QueryParseException("cannot create query for index: " + p);
+    }
+  }
+
+  private static Query or(Predicate<ChangeData> p) throws QueryParseException {
+    try {
+      BooleanQuery q = new BooleanQuery();
+      for (int i = 0; i < p.getChildCount(); i++) {
+        q.add(toQuery(p.getChild(i)), SHOULD);
+      }
+      return q;
+    } catch (BooleanQuery.TooManyClauses e) {
+      throw new QueryParseException("cannot create query for index: " + p, e);
+    }
+  }
+
+  private static Query and(Predicate<ChangeData> p) throws QueryParseException {
+    try {
+      BooleanQuery b = new BooleanQuery();
+      List<Query> not = Lists.newArrayListWithCapacity(p.getChildCount());
+      for (int i = 0; i < p.getChildCount(); i++) {
+        Predicate<ChangeData> c = p.getChild(i);
+        if (c instanceof NotPredicate) {
+          Predicate<ChangeData> n = c.getChild(0);
+          if (n instanceof TimestampRangePredicate) {
+            b.add(notTimestamp((TimestampRangePredicate<ChangeData>) n), MUST);
+          } else {
+            not.add(toQuery(n));
+          }
+        } else {
+          b.add(toQuery(c), MUST);
+        }
+      }
+      for (Query q : not) {
+        b.add(q, MUST_NOT);
+      }
+      return b;
+    } catch (BooleanQuery.TooManyClauses e) {
+      throw new QueryParseException("cannot create query for index: " + p, e);
+    }
+  }
+
+  private static Query not(Predicate<ChangeData> p) throws QueryParseException {
+    Predicate<ChangeData> n = p.getChild(0);
+    if (n instanceof TimestampRangePredicate) {
+      return notTimestamp((TimestampRangePredicate<ChangeData>) n);
+    }
+
+    // Lucene does not support negation, start with all and subtract.
+    BooleanQuery q = new BooleanQuery();
+    q.add(new MatchAllDocsQuery(), MUST);
+    q.add(toQuery(n), MUST_NOT);
+    return q;
+  }
+
+  private static Query fieldQuery(IndexPredicate<ChangeData> p)
+      throws QueryParseException {
+    if (p.getType() == FieldType.INTEGER) {
+      return intQuery(p);
+    } else if (p.getType() == FieldType.TIMESTAMP) {
+      return timestampQuery(p);
+    } else if (p.getType() == FieldType.EXACT) {
+      return exactQuery(p);
+    } else if (p.getType() == FieldType.PREFIX) {
+      return prefixQuery(p);
+    } else if (p.getType() == FieldType.FULL_TEXT) {
+      return fullTextQuery(p);
+    } else if (p instanceof SortKeyPredicate) {
+      return sortKeyQuery((SortKeyPredicate) p);
+    } else {
+      throw badFieldType(p.getType());
+    }
+  }
+
+  private static Term intTerm(String name, int value) {
+    BytesRef bytes = new BytesRef(NumericUtils.BUF_SIZE_INT);
+    NumericUtils.intToPrefixCodedBytes(value, 0, bytes);
+    return new Term(name, bytes);
+  }
+
+  private static Query intQuery(IndexPredicate<ChangeData> p)
+      throws QueryParseException {
+    int value;
+    try {
+      // Can't use IntPredicate because it and IndexPredicate are different
+      // subclasses of OperatorPredicate.
+      value = Integer.valueOf(p.getValue());
+    } catch (IllegalArgumentException e) {
+      throw new QueryParseException("not an integer: " + p.getValue());
+    }
+    return new TermQuery(intTerm(p.getField().getName(), value));
+  }
+
+  private static Query sortKeyQuery(SortKeyPredicate p) {
+    return NumericRangeQuery.newLongRange(
+        p.getField().getName(),
+        p.getMinValue() != Long.MIN_VALUE ? p.getMinValue() : null,
+        p.getMaxValue() != Long.MAX_VALUE ? p.getMaxValue() : null,
+        true, true);
+  }
+
+  private static Query timestampQuery(IndexPredicate<ChangeData> p)
+      throws QueryParseException {
+    if (p instanceof TimestampRangePredicate) {
+      TimestampRangePredicate<ChangeData> r =
+          (TimestampRangePredicate<ChangeData>) p;
+      return NumericRangeQuery.newIntRange(
+          r.getField().getName(),
+          toIndexTime(r.getMinTimestamp()),
+          toIndexTime(r.getMaxTimestamp()),
+          true, true);
+    }
+    throw new QueryParseException("not a timestamp: " + p);
+  }
+
+  private static Query notTimestamp(TimestampRangePredicate<ChangeData> r)
+      throws QueryParseException {
+    if (r.getMinTimestamp().getTime() == 0) {
+      return NumericRangeQuery.newIntRange(
+          r.getField().getName(),
+          toIndexTime(r.getMaxTimestamp()),
+          null,
+          true, true);
+    }
+    throw new QueryParseException("cannot negate: " + r);
+  }
+
+  private static Query exactQuery(IndexPredicate<ChangeData> p) {
+    if (p instanceof RegexPredicate<?>) {
+      return regexQuery(p);
+    } else {
+      return new TermQuery(new Term(p.getField().getName(), p.getValue()));
+    }
+  }
+
+  private static Query regexQuery(IndexPredicate<ChangeData> p) {
+    String re = p.getValue();
+    if (re.startsWith("^")) {
+      re = re.substring(1);
+    }
+    if (re.endsWith("$") && !re.endsWith("\\$")) {
+      re = re.substring(0, re.length() - 1);
+    }
+    return new RegexpQuery(new Term(p.getField().getName(), re));
+  }
+
+  private static Query prefixQuery(IndexPredicate<ChangeData> p) {
+    return new PrefixQuery(new Term(p.getField().getName(), p.getValue()));
+  }
+
+  private static Query fullTextQuery(IndexPredicate<ChangeData> p) {
+    return new FuzzyQuery(new Term(p.getField().getName(), p.getValue()));
+  }
+
+  public static int toIndexTime(Timestamp ts) {
+    return (int) (ts.getTime() / 60000);
+  }
+
+  public static IllegalArgumentException badFieldType(FieldType<?> t) {
+    return new IllegalArgumentException("unknown index field type " + t);
+  }
+
+  private QueryBuilder() {
+  }
+}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
new file mode 100644
index 0000000..60bc547
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
@@ -0,0 +1,196 @@
+// 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.lucene;
+
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.AbstractFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.NRTManager;
+import org.apache.lucene.search.NRTManager.TrackingIndexWriter;
+import org.apache.lucene.search.NRTManagerReopenThread;
+import org.apache.lucene.search.ReferenceManager.RefreshListener;
+import org.apache.lucene.search.SearcherFactory;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Piece of the change index that is implemented as a separate Lucene index. */
+class SubIndex {
+  private static final Logger log = LoggerFactory.getLogger(SubIndex.class);
+
+  private final Directory dir;
+  private final TrackingIndexWriter writer;
+  private final NRTManager nrtManager;
+  private final NRTManagerReopenThread reopenThread;
+  private final ConcurrentMap<RefreshListener, Boolean> refreshListeners;
+
+  SubIndex(File file, IndexWriterConfig writerConfig) throws IOException {
+    dir = FSDirectory.open(file);
+    writer = new NRTManager.TrackingIndexWriter(new IndexWriter(dir, writerConfig));
+    nrtManager = new NRTManager(writer, new SearcherFactory());
+
+    refreshListeners = Maps.newConcurrentMap();
+    nrtManager.addListener(new RefreshListener() {
+      @Override
+      public void beforeRefresh() throws IOException {
+      }
+
+      @Override
+      public void afterRefresh(boolean didRefresh) throws IOException {
+        for (RefreshListener l : refreshListeners.keySet()) {
+          l.afterRefresh(didRefresh);
+        }
+      }
+    });
+
+    reopenThread = new NRTManagerReopenThread(
+        nrtManager,
+        0.500 /* maximum stale age (seconds) */,
+        0.010 /* minimum stale age (seconds) */);
+    reopenThread.setName("NRT " + file.getName());
+    reopenThread.setPriority(Math.min(
+        Thread.currentThread().getPriority() + 2,
+        Thread.MAX_PRIORITY));
+    reopenThread.setDaemon(true);
+    reopenThread.start();
+  }
+
+  void close() {
+    reopenThread.close();
+    try {
+      nrtManager.close();
+    } catch (IOException e) {
+      log.warn("error closing Lucene searcher", e);
+    }
+    try {
+      writer.getIndexWriter().close();
+    } catch (IOException e) {
+      log.warn("error closing Lucene writer", e);
+    }
+    try {
+      dir.close();
+    } catch (IOException e) {
+      log.warn("error closing Lucene directory", e);
+    }
+  }
+
+  ListenableFuture<Void> insert(Document doc) throws IOException {
+    return new NrtFuture(writer.addDocument(doc));
+  }
+
+  ListenableFuture<Void> replace(Term term, Document doc) throws IOException {
+    return new NrtFuture(writer.updateDocument(term, doc));
+  }
+
+  ListenableFuture<Void> delete(Term term) throws IOException {
+    return new NrtFuture(writer.deleteDocuments(term));
+  }
+
+  void deleteAll() throws IOException {
+    writer.deleteAll();
+  }
+
+  IndexSearcher acquire() throws IOException {
+    return nrtManager.acquire();
+  }
+
+  void release(IndexSearcher searcher) throws IOException {
+    nrtManager.release(searcher);
+  }
+
+  private final class NrtFuture extends AbstractFuture<Void>
+      implements RefreshListener {
+    private final long gen;
+    private final AtomicBoolean hasListeners = new AtomicBoolean();
+
+    NrtFuture(long gen) {
+      this.gen = gen;
+    }
+
+    @Override
+    public Void get() throws InterruptedException, ExecutionException {
+      if (!isDone()) {
+        nrtManager.waitForGeneration(gen);
+        set(null);
+      }
+      return super.get();
+    }
+
+    @Override
+    public Void get(long timeout, TimeUnit unit) throws InterruptedException,
+        TimeoutException, ExecutionException {
+      if (!isDone()) {
+        nrtManager.waitForGeneration(gen, timeout, unit);
+        set(null);
+      }
+      return super.get(timeout, unit);
+    }
+
+    @Override
+    public boolean isDone() {
+      if (super.isDone()) {
+        return true;
+      } else if (gen <= nrtManager.getCurrentSearchingGen()) {
+        set(null);
+        return true;
+      }
+      return false;
+    }
+
+    @Override
+    public void addListener(Runnable listener, Executor executor) {
+      if (hasListeners.compareAndSet(false, true) && !isDone()) {
+        nrtManager.addListener(this);
+      }
+      super.addListener(listener, executor);
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+      if (hasListeners.get()) {
+        refreshListeners.put(this, true);
+      }
+      return super.cancel(mayInterruptIfRunning);
+    }
+
+    @Override
+    public void beforeRefresh() throws IOException {
+    }
+
+    @Override
+    public void afterRefresh(boolean didRefresh) throws IOException {
+      if (gen <= nrtManager.getCurrentSearchingGen()) {
+        refreshListeners.remove(this);
+        set(null);
+      }
+    }
+  }
+}
diff --git a/gerrit-main/BUCK b/gerrit-main/BUCK
new file mode 100644
index 0000000..da39eec
--- /dev/null
+++ b/gerrit-main/BUCK
@@ -0,0 +1,13 @@
+java_binary(
+  name = 'main_bin',
+  main_class = 'Main',
+  deps = [':main_lib'],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'main_lib',
+  srcs = ['src/main/java/Main.java'],
+  deps = ['//gerrit-launcher:launcher'],
+  visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-main/pom.xml b/gerrit-main/pom.xml
deleted file mode 100644
index 25c9401..0000000
--- a/gerrit-main/pom.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-main</artifactId>
-  <name>Gerrit Code Review - Main</name>
-
-  <description>
-    Main class to bootstrap out of a WAR
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-launcher</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.2</source>
-          <target>1.2</target>
-          <encoding>UTF-8</encoding>
-        </configuration>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-shade-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>shade</goal>
-            </goals>
-            <configuration>
-              <createDependencyReducedPom>false</createDependencyReducedPom>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-openid/BUCK b/gerrit-openid/BUCK
new file mode 100644
index 0000000..0801534
--- /dev/null
+++ b/gerrit-openid/BUCK
@@ -0,0 +1,23 @@
+java_library2(
+  name = 'openid',
+  srcs = glob(['src/main/java/**/*.java']),
+  resources = glob(['src/main/resources/**/*']),
+  deps = [
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-gwtexpui:server',
+    '//gerrit-httpd:httpd',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:jsr305',
+    '//lib/guice:guice',
+    '//lib/guice:guice-servlet',
+    '//lib/jgit:jgit',
+    '//lib/log:api',
+    '//lib/openid:consumer',
+  ],
+  compile_deps = ['//lib:servlet-api-3_0'],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-openid/pom.xml b/gerrit-openid/pom.xml
deleted file mode 100644
index a81cf17..0000000
--- a/gerrit-openid/pom.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-openid</artifactId>
-  <name>Gerrit Code Review - OpenID servlet and RPC</name>
-
-  <description>
-    OpenID
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>servlet-api</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.code.findbugs</groupId>
-      <artifactId>jsr305</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.http.server</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.openid4java</groupId>
-      <artifactId>openid4java</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-httpd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-server</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-patch-commonsnet/BUCK b/gerrit-patch-commonsnet/BUCK
new file mode 100644
index 0000000..53b382f
--- /dev/null
+++ b/gerrit-patch-commonsnet/BUCK
@@ -0,0 +1,11 @@
+java_library(
+  name = 'commons-net',
+  srcs = glob(['src/main/java/org/apache/commons/net/**/*.java']),
+  deps = [
+    '//gerrit-util-ssl:ssl',
+    '//lib/commons:codec',
+    '//lib/commons:net',
+    '//lib/log:api',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-patch-commonsnet/pom.xml b/gerrit-patch-commonsnet/pom.xml
deleted file mode 100644
index 7718721..0000000
--- a/gerrit-patch-commonsnet/pom.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-patch-commonsnet</artifactId>
-  <name>Gerrit Code Review - Patch commons-net</name>
-
-  <description>
-    Hacks to expose package-private data from commons-net to Gerrit
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>commons-net</groupId>
-      <artifactId>commons-net</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>commons-codec</groupId>
-      <artifactId>commons-codec</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-api</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-util-ssl</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/gerrit-patch-jgit/BUCK b/gerrit-patch-jgit/BUCK
new file mode 100644
index 0000000..18890ac
--- /dev/null
+++ b/gerrit-patch-jgit/BUCK
@@ -0,0 +1,32 @@
+SRC = 'src/main/java/org/eclipse/jgit/'
+
+gwt_module(
+  name = 'client',
+  srcs = [
+    SRC + 'diff/Edit_JsonSerializer.java',
+    SRC + 'diff/ReplaceEdit.java',
+  ],
+  gwtxml = SRC + 'JGit.gwt.xml',
+  deps = [
+    '//lib:gwtjsonrpc',
+    '//lib/gwt:user',
+    '//lib/jgit:jgit',
+    '//lib/jgit:Edit',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'server',
+  srcs = [
+    SRC + 'diff/EditDeserializer.java',
+    SRC + 'diff/ReplaceEdit.java',
+    SRC + 'internal/storage/file/WindowCacheStatAccessor.java',
+    SRC + 'lib/ObjectIdSerialization.java',
+  ],
+  deps = [
+    '//lib:gson',
+    '//lib/jgit:jgit',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-patch-jgit/pom.xml b/gerrit-patch-jgit/pom.xml
deleted file mode 100644
index 222d622..0000000
--- a/gerrit-patch-jgit/pom.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-patch-jgit</artifactId>
-  <name>Gerrit Code Review - Patch JGit</name>
-
-  <description>
-    Hacks to expose package-private data from JGit to Gerrit
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>gwtjsonrpc</groupId>
-      <artifactId>gwtjsonrpc</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-user</artifactId>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
new file mode 100644
index 0000000..64653c0
--- /dev/null
+++ b/gerrit-pgm/BUCK
@@ -0,0 +1,61 @@
+java_library2(
+  name = 'pgm',
+  srcs = glob(['src/main/java/**/*.java']),
+  resources = glob(['src/main/resources/**/*']),
+  deps = [
+    '//gerrit-cache-h2:cache-h2',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-gwtexpui:server',
+    '//gerrit-httpd:httpd',
+    '//gerrit-lucene:lucene',
+    '//gerrit-openid:openid',
+    '//gerrit-server:common_rules',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//gerrit-solr:solr',
+    '//gerrit-sshd:sshd',
+    '//gerrit-util-cli:cli',
+    '//lib:args4j',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:gwtjsonrpc',
+    '//lib:h2',
+    '//lib:jsr305',
+    '//lib:servlet-api-3_0',
+    '//lib/commons:dbcp',
+    '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
+    '//lib/guice:guice-servlet',
+    '//lib/jetty:server',
+    '//lib/jetty:servlet',
+    '//lib/jgit:jgit',
+    '//lib/log:api',
+    '//lib/log:log4j',
+    '//lib/lucene:core',
+    '//lib/mina:sshd',
+    '//lib/prolog:prolog-cafe',
+  ],
+  compile_deps = ['//gerrit-launcher:launcher'],
+  visibility = [
+    '//:',
+    '//gerrit-acceptance-tests:',
+    '//tools/eclipse:classpath',
+    '//Documentation:licenses.txt',
+  ],
+)
+
+java_test(
+  name = 'pgm_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  deps = [
+    ':pgm',
+    '//gerrit-server:server',
+    '//lib:junit',
+    '//lib:easymock',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
+    '//lib/jgit:junit',
+  ],
+  source_under_test = [':pgm'],
+)
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
deleted file mode 100644
index c7121c7a..0000000
--- a/gerrit-pgm/pom.xml
+++ /dev/null
@@ -1,109 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-pgm</artifactId>
-  <name>Gerrit Code Review - Pgm</name>
-
-  <description>
-    Command line executables
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>log4j</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.h2database</groupId>
-      <artifactId>h2</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>postgresql</groupId>
-      <artifactId>postgresql</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-main</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-util-cli</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-server</artifactId>
-      <version>${project.version}</version>
-      <exclusions>
-        <exclusion>
-          <groupId>org.apache.tomcat</groupId>
-          <artifactId>servlet-api</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-openid</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-sshd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-httpd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlet</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>tomcat-servlet-api</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.junit</artifactId>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index ca98a84..a10e7e4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.httpd.auth.openid.OpenIdModule;
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.lucene.LuceneIndexModule;
 import com.google.gerrit.pgm.http.jetty.GetUserFilter;
 import com.google.gerrit.pgm.http.jetty.JettyEnv;
 import com.google.gerrit.pgm.http.jetty.JettyModule;
@@ -38,44 +39,36 @@
 import com.google.gerrit.pgm.util.RuntimeShutdown;
 import com.google.gerrit.pgm.util.SiteProgram;
 import com.google.gerrit.reviewdb.client.AuthType;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.AuthConfigModule;
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
 import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.GerritGlobalModule;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.MasterNodeStartup;
 import com.google.gerrit.server.contact.HttpContactStoreConnection;
 import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
 import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.index.IndexModule;
+import com.google.gerrit.server.index.NoIndexModule;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.patch.IntraLineWorkerPool;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 import com.google.gerrit.server.plugins.PluginModule;
-import com.google.gerrit.server.schema.SchemaUpdater;
 import com.google.gerrit.server.schema.SchemaVersionCheck;
-import com.google.gerrit.server.schema.UpdateUI;
 import com.google.gerrit.server.ssh.NoSshKeyCache;
 import com.google.gerrit.server.ssh.NoSshModule;
+import com.google.gerrit.solr.SolrIndexModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
 import com.google.gerrit.sshd.commands.MasterCommandModule;
 import com.google.gerrit.sshd.commands.SlaveCommandModule;
-import com.google.gwtorm.jdbc.JdbcExecutor;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.gwtorm.server.StatementExecutor;
 import com.google.inject.AbstractModule;
-import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Module;
 import com.google.inject.Provider;
 
-import org.eclipse.jgit.lib.Config;
 import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -121,6 +114,10 @@
   @Option(name = "--headless", usage = "Don't start the UI frontend")
   private boolean headless;
 
+  @Option(name = "--init", aliases = {"-i"},
+      usage = "Init site before starting the daemon")
+  private boolean doInit;
+
   private final LifecycleManager manager = new LifecycleManager();
   private Injector dbInjector;
   private Injector cfgInjector;
@@ -141,6 +138,13 @@
 
   @Override
   public int run() throws Exception {
+    if (doInit) {
+      try {
+        new Init(getSitePath()).run();
+      } catch (Exception e) {
+        throw die("Init failed", e);
+      }
+    }
     mustHaveValidSite();
     Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
       @Override
@@ -176,7 +180,6 @@
       sysInjector = createSysInjector();
       sysInjector.getInstance(PluginGuiceEnvironment.class)
         .setCfgInjector(cfgInjector);
-      sysInjector.getInstance(SchemaUpgrade.class).upgradeSchema();
       manager.add(dbInjector, cfgInjector, sysInjector);
 
       if (sshd) {
@@ -228,74 +231,6 @@
     }
   }
 
-  static class SchemaUpgrade {
-
-    private final Config config;
-    private final SchemaUpdater updater;
-    private final SchemaFactory<ReviewDb> schema;
-
-    @Inject
-    SchemaUpgrade(@GerritServerConfig Config config, SchemaUpdater updater,
-        SchemaFactory<ReviewDb> schema) {
-      this.config = config;
-      this.updater = updater;
-      this.schema = schema;
-    }
-
-    void upgradeSchema() throws OrmException {
-      SchemaUpgradePolicy policy =
-          config.getEnum("site", null, "upgradeSchemaOnStartup",
-              SchemaUpgradePolicy.OFF);
-      if (policy == SchemaUpgradePolicy.AUTO
-          || policy == SchemaUpgradePolicy.AUTO_NO_PRUNE) {
-        final List<String> pruneList = new ArrayList<String>();
-        updater.update(new UpdateUI() {
-          @Override
-          public void message(String msg) {
-            log.info(msg);
-          }
-
-          @Override
-          public boolean yesno(boolean def, String msg) {
-            return true;
-          }
-
-          @Override
-          public boolean isBatch() {
-            return true;
-          }
-
-          @Override
-          public void pruneSchema(StatementExecutor e, List<String> prune) {
-            for (String p : prune) {
-              if (!pruneList.contains(p)) {
-                pruneList.add(p);
-              }
-            }
-          }
-        });
-
-        if (!pruneList.isEmpty() && policy == SchemaUpgradePolicy.AUTO) {
-          log.info("Pruning: " + pruneList.toString());
-          final JdbcSchema db = (JdbcSchema) schema.open();
-          try {
-            final JdbcExecutor e = new JdbcExecutor(db);
-            try {
-              for (String sql : pruneList) {
-                e.execute(sql);
-              }
-            } finally {
-              e.close();
-            }
-          } finally {
-            db.close();
-          }
-        }
-      }
-    }
-  }
-
-
   private String myVersion() {
     return com.google.gerrit.common.Version.getVersion();
   }
@@ -319,6 +254,18 @@
     modules.add(new SmtpEmailSender.Module());
     modules.add(new SignedTokenEmailTokenVerifier.Module());
     modules.add(new PluginModule());
+    AbstractModule changeIndexModule;
+    switch (IndexModule.getIndexType(cfgInjector)) {
+      case LUCENE:
+        changeIndexModule = new LuceneIndexModule();
+        break;
+      case SOLR:
+        changeIndexModule = new SolrIndexModule();
+        break;
+      default:
+        changeIndexModule = new NoIndexModule();
+    }
+    modules.add(changeIndexModule);
     if (httpd) {
       modules.add(new CanonicalWebUrlModule() {
         @Override
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 3c7822c..5a1192a 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
@@ -62,6 +62,15 @@
   @Option(name = "--no-auto-start", usage = "Don't automatically start daemon after init")
   private boolean noAutoStart;
 
+  public Init() {
+  }
+
+  public Init(File sitePath) {
+    super(sitePath);
+    batchMode = true;
+    noAutoStart = true;
+  }
+
   @Override
   public int run() throws Exception {
     ErrorLogFile.errorOnlyConsole();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
new file mode 100644
index 0000000..f740fba
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -0,0 +1,212 @@
+// 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.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.lucene.LuceneIndexModule;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.index.ChangeBatchIndexer;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.ChangeSchemas;
+import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.IndexModule;
+import com.google.gerrit.server.index.IndexModule.IndexType;
+import com.google.gerrit.server.index.NoIndexModule;
+import com.google.gerrit.server.patch.PatchListCacheImpl;
+import com.google.gerrit.solr.SolrIndexModule;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+import com.google.inject.TypeLiteral;
+
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.util.io.NullOutputStream;
+import org.kohsuke.args4j.Option;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+public class Reindex extends SiteProgram {
+  @Option(name = "--threads", usage = "Number of threads to use for indexing")
+  private int threads = Runtime.getRuntime().availableProcessors();
+
+  @Option(name = "--schema-version",
+      usage = "Schema version to reindex; default is most recent version")
+  private Integer version;
+
+  @Option(name = "--output", usage = "Prefix for output; path for local disk index, or prefix for remote index")
+  private String outputBase;
+
+  @Option(name = "--verbose", usage = "Output debug information for each change")
+  private boolean verbose;
+
+  @Option(name = "--dry-run", usage = "Dry run: don't write anything to index")
+  private boolean dryRun;
+
+  private Injector dbInjector;
+  private Injector sysInjector;
+  private ChangeIndex index;
+
+  @Override
+  public int run() throws Exception {
+    mustHaveValidSite();
+    dbInjector = createDbInjector(MULTI_USER);
+    if (IndexModule.getIndexType(dbInjector) == IndexType.SQL) {
+      throw die("index.type must be configured (or not SQL)");
+    }
+    if (version == null) {
+      version = ChangeSchemas.getLatest().getVersion();
+    }
+    LifecycleManager dbManager = new LifecycleManager();
+    dbManager.add(dbInjector);
+    dbManager.start();
+
+    sysInjector = createSysInjector();
+    LifecycleManager sysManager = new LifecycleManager();
+    sysManager.add(sysInjector);
+    sysManager.start();
+
+    index = sysInjector.getInstance(IndexCollection.class).getSearchIndex();
+    index.markReady(false);
+    index.deleteAll();
+    int result = indexAll();
+    index.markReady(true);
+
+    sysManager.stop();
+    dbManager.stop();
+    return result;
+  }
+
+  private Injector createSysInjector() {
+    List<Module> modules = Lists.newArrayList();
+    modules.add(PatchListCacheImpl.module());
+    AbstractModule changeIndexModule;
+    switch (IndexModule.getIndexType(dbInjector)) {
+      case LUCENE:
+        changeIndexModule = new LuceneIndexModule(version, threads, outputBase);
+        break;
+      case SOLR:
+        changeIndexModule = new SolrIndexModule(false, threads, outputBase);
+        break;
+      default:
+        changeIndexModule = new NoIndexModule();
+    }
+    modules.add(changeIndexModule);
+    modules.add(new ReviewDbModule());
+    modules.add(new AbstractModule() {
+      @SuppressWarnings("rawtypes")
+      @Override
+      protected void configure() {
+        // Plugins are not loaded and we're just running through each change
+        // once, so don't worry about cache removal.
+        bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
+            .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
+        install(new DefaultCacheFactory.Module());
+      }
+    });
+    return dbInjector.createChildInjector(modules);
+  }
+
+  private class ReviewDbModule extends LifecycleModule {
+    @Override
+    protected void configure() {
+      final SchemaFactory<ReviewDb> schema = dbInjector.getInstance(
+          Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
+      final List<ReviewDb> dbs = Collections.synchronizedList(
+          Lists.<ReviewDb> newArrayListWithCapacity(threads + 1));
+      final ThreadLocal<ReviewDb> localDb = new ThreadLocal<ReviewDb>();
+
+      bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
+        @Override
+        public ReviewDb get() {
+          ReviewDb db = localDb.get();
+          if (db == null) {
+            try {
+              db = schema.open();
+              dbs.add(db);
+              localDb.set(db);
+            } catch (OrmException e) {
+              throw new ProvisionException("unable to open ReviewDb", e);
+            }
+          }
+          return db;
+        }
+      });
+      listener().toInstance(new LifecycleListener() {
+        @Override
+        public void start() {
+          // Do nothing.
+        }
+
+        @Override
+        public void stop() {
+          for (ReviewDb db : dbs) {
+            db.close();
+          }
+        }
+      });
+    }
+  }
+
+  private int indexAll() throws Exception {
+    ReviewDb db = sysInjector.getInstance(ReviewDb.class);
+    ProgressMonitor pm = new TextProgressMonitor();
+    pm.start(1);
+    pm.beginTask("Collecting projects", ProgressMonitor.UNKNOWN);
+    Set<Project.NameKey> projects = Sets.newTreeSet();
+    int changeCount = 0;
+    try {
+      for (Change change : db.changes().all()) {
+        changeCount++;
+        if (projects.add(change.getProject())) {
+          pm.update(1);
+        }
+      }
+    } finally {
+      db.close();
+    }
+    pm.endTask();
+
+    ChangeBatchIndexer batchIndexer =
+        sysInjector.getInstance(ChangeBatchIndexer.class);
+    ChangeBatchIndexer.Result result = batchIndexer.indexAll(
+      index, projects, projects.size(), changeCount, System.err,
+      verbose ? System.out : NullOutputStream.INSTANCE);
+    int n = result.doneCount() + result.failedCount();
+    double t = result.elapsed(TimeUnit.MILLISECONDS) / 1000d;
+    System.out.format("Reindexed %d changes in %.01fs (%.01f/s)\n", n, t, n/t);
+    return result.success() ? 0 : 1;
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java
deleted file mode 100644
index 67f5c91..0000000
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.pgm;
-
-/** Policy for auto upgrading schema on server startup */
-public enum SchemaUpgradePolicy {
-
-  /** Perform schema migration if necessary and prune unused objects */
-  AUTO,
-
-  /** Like AUTO but don't prune unused objects */
-  AUTO_NO_PRUNE,
-
-  /** No automatic schema upgrade */
-  OFF
-}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
new file mode 100644
index 0000000..e086e6a
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
@@ -0,0 +1,83 @@
+// 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.pgm.http.jetty;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.gwtexpui.server.CacheHeaders;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+class HiddenErrorHandler extends ErrorHandler {
+  private static final Logger log = LoggerFactory.getLogger(HiddenErrorHandler.class);
+
+  public void handle(String target, Request baseRequest,
+      HttpServletRequest req, HttpServletResponse res) throws IOException {
+    AbstractHttpConnection conn = AbstractHttpConnection.getCurrentConnection();
+    conn.getRequest().setHandled(true);
+    try {
+      log(req);
+    } finally {
+      reply(conn, res);
+    }
+  }
+
+  private void reply(AbstractHttpConnection conn, HttpServletResponse res)
+      throws IOException {
+    byte[] msg = message(conn);
+    res.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=ISO-8859-1");
+    res.setContentLength(msg.length);
+    try {
+      CacheHeaders.setNotCacheable(res);
+    } finally {
+      ServletOutputStream out = res.getOutputStream();
+      try {
+        out.write(msg);
+      } finally {
+        out.close();
+      }
+    }
+  }
+
+  private static byte[] message(AbstractHttpConnection conn) {
+    String msg = conn.getResponse().getReason();
+    if (msg == null)
+      msg = HttpStatus.getMessage(conn.getResponse().getStatus());
+    return msg.getBytes(Charsets.ISO_8859_1);
+  }
+
+  private static void log(HttpServletRequest req) {
+    Throwable err = (Throwable)req.getAttribute("javax.servlet.error.exception");
+    if (err != null) {
+      String uri = req.getRequestURI();
+      if (!Strings.isNullOrEmpty(req.getQueryString())) {
+        uri += "?" + req.getQueryString();
+      }
+      log.error(String.format("Error in %s %s", req.getMethod(), uri), err);
+    }
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 3a7a874..e167605 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -17,6 +17,8 @@
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
+import com.google.common.base.Objects;
+import com.google.common.io.ByteStreams;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.launcher.GerritLauncher;
 import com.google.gerrit.reviewdb.client.AuthType;
@@ -49,12 +51,16 @@
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.util.thread.ThreadPool;
 import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InterruptedIOException;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -64,14 +70,24 @@
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Properties;
 import java.util.Set;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 
 @Singleton
 public class JettyServer {
+  private static final Logger log = LoggerFactory.getLogger(JettyServer.class);
+
   static class Lifecycle implements LifecycleListener {
     private final JettyServer server;
 
@@ -296,7 +312,7 @@
 
     final List<ContextHandler> all = new ArrayList<ContextHandler>();
     for (String path : paths) {
-      all.add(makeContext(path, env));
+      all.add(makeContext(path, env, cfg));
     }
 
     if (all.size() == 1) {
@@ -316,13 +332,14 @@
   }
 
   private ContextHandler makeContext(final String contextPath,
-      final JettyEnv env) throws MalformedURLException, IOException {
+      final JettyEnv env, final Config cfg) throws MalformedURLException, IOException {
     final ServletContextHandler app = new ServletContextHandler();
 
     // This enables the use of sessions in Jetty, feature available
     // for Gerrit plug-ins to enable user-level sessions.
     //
     app.setSessionHandler(new SessionHandler());
+    app.setErrorHandler(new HiddenErrorHandler());
 
     // This is the path we are accessed by clients within our domain.
     //
@@ -332,7 +349,28 @@
     // need to unpack them into yet another temporary directory prior to
     // serving to clients.
     //
-    app.setBaseResource(getBaseResource());
+    app.setBaseResource(getBaseResource(app));
+
+    // HTTP front-end filter to be used as surrogate of Apache HTTP
+    // reverse-proxy filtering.
+    // It is meant to be used as simpler tiny deployment of custom-made
+    // security enforcement (Security tokens, IP-based security filtering, others)
+    String filterClassName = cfg.getString("httpd", null, "filterClass");
+    if (filterClassName != null) {
+      try {
+        @SuppressWarnings("unchecked")
+        Class<? extends Filter> filterClass =
+            (Class<? extends Filter>) Class.forName(filterClassName);
+        Filter filter = env.webInjector.getInstance(filterClass);
+        app.addFilter(new FilterHolder(filter), "/*",
+            EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
+      } catch (Throwable e) {
+        String errorMessage =
+            "Unable to instantiate front-end HTTP Filter " + filterClassName;
+        log.error(errorMessage, e);
+        throw new IllegalArgumentException(errorMessage, e);
+      }
+    }
 
     // Perform the same binding as our web.xml would do, but instead
     // of using the listener to create the injector pass the one we
@@ -365,13 +403,14 @@
     return app;
   }
 
-  private Resource getBaseResource() throws IOException {
+  private Resource getBaseResource(ServletContextHandler app)
+      throws IOException {
     if (baseResource == null) {
       try {
-        baseResource = unpackWar();
+        baseResource = unpackWar(GerritLauncher.getDistributionArchive());
       } catch (FileNotFoundException err) {
         if (err.getMessage() == GerritLauncher.NOT_ARCHIVED) {
-          baseResource = useDeveloperBuild();
+          baseResource = useDeveloperBuild(app);
         } else {
           throw err;
         }
@@ -380,9 +419,13 @@
     return baseResource;
   }
 
-  private Resource unpackWar() throws IOException {
-    final File srcwar = GerritLauncher.getDistributionArchive();
+  private static Resource unpackWar(File srcwar) throws IOException {
+    File dstwar = makeWarTempDir();
+    unpack(srcwar, dstwar);
+    return Resource.newResource(dstwar.toURI());
+  }
 
+  private static File makeWarTempDir() throws IOException {
     // Obtain our local temporary directory, but it comes back as a file
     // so we have to switch it to be a directory post creation.
     //
@@ -395,11 +438,13 @@
     // a security feature. Try to resolve out any symlinks in the path.
     //
     try {
-      dstwar = dstwar.getCanonicalFile();
+      return dstwar.getCanonicalFile();
     } catch (IOException e) {
-      dstwar = dstwar.getAbsoluteFile();
+      return dstwar.getAbsoluteFile();
     }
+  }
 
+  private static void unpack(File srcwar, File dstwar) throws IOException {
     final ZipFile zf = new ZipFile(srcwar);
     try {
       final Enumeration<? extends ZipEntry> e = zf.entries();
@@ -436,11 +481,9 @@
     } finally {
       zf.close();
     }
-
-    return Resource.newResource(dstwar.toURI());
   }
 
-  private void mkdir(final File dir) throws IOException {
+  private static void mkdir(File dir) throws IOException {
     if (!dir.isDirectory()) {
       mkdir(dir.getParentFile());
       if (!dir.mkdir())
@@ -449,7 +492,8 @@
     }
   }
 
-  private Resource useDeveloperBuild() throws IOException {
+  private Resource useDeveloperBuild(ServletContextHandler app)
+      throws IOException {
     // Find ourselves in the CLASSPATH. We should be a loose class file.
     //
     URL u = getClass().getResource(getClass().getSimpleName() + ".class");
@@ -474,15 +518,119 @@
       dir = dir.getParentFile();
     }
 
-    // We should be in a Maven style output, that is $jar/target/classes.
-    //
     if (!dir.getName().equals("classes")) {
       throw new FileNotFoundException("Cannot find web root from " + u);
     }
     dir = dir.getParentFile(); // pop classes
-    if (!dir.getName().equals("target")) {
+
+    if ("buck-out".equals(dir.getName())) {
+      final File dstwar = makeWarTempDir();
+      String pkg = "gerrit-gwtui";
+      String target = targetForBrowser(System.getProperty("gerrit.browser"));
+      final File gen = new File(dir, "gen");
+      String out = new File(new File(gen, pkg), target).getAbsolutePath();
+      final File zip = new File(out + ".zip");
+      final File root = dir.getParentFile();
+      final String name = "//" + pkg + ":" + target;
+
+      File ui = new File(dstwar, "gerrit_ui");
+      File p = new File(ui, "permutations");
+      mkdir(ui);
+      p.createNewFile();
+      p.deleteOnExit();
+
+      app.addFilter(new FilterHolder(new Filter() {
+        private long last;
+
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse res,
+            FilterChain chain) throws IOException, ServletException {
+          HttpServletRequest req = (HttpServletRequest) request;
+          build(root, gen, name);
+          if (last != zip.lastModified()) {
+            last = zip.lastModified();
+            unpack(zip, dstwar);
+          }
+          chain.doFilter(req, res);
+        }
+
+        @Override
+        public void init(FilterConfig config) {
+        }
+        @Override
+        public void destroy() {
+        }
+      }), "/", EnumSet.of(DispatcherType.REQUEST));
+      return Resource.newResource(dstwar.toURI());
+    } else if ("target".equals(dir.getName())) {
+      return useMavenDeveloperBuild(dir);
+    } else {
       throw new FileNotFoundException("Cannot find web root from " + u);
     }
+  }
+
+  private static String targetForBrowser(String browser) {
+    if (browser == null || browser.isEmpty()) {
+      return "ui_dbg";
+    } else if (browser.startsWith("ui_")) {
+      return browser;
+    } else {
+      return "ui_" + browser;
+    }
+  }
+
+  private static void build(File root, File gen, String target)
+      throws IOException {
+    log.info("buck build " + target);
+    Properties properties = loadBuckProperties(gen);
+    String buck = Objects.firstNonNull(properties.getProperty("buck"), "buck");
+    ProcessBuilder proc = new ProcessBuilder(buck, "build", target)
+        .directory(root)
+        .redirectErrorStream(true);
+    if (properties.contains("PATH")) {
+      proc.environment().put("PATH", properties.getProperty("PATH"));
+    }
+    long start = System.currentTimeMillis();
+    Process rebuild = proc.start();
+    byte[] out;
+    InputStream in = rebuild.getInputStream();
+    try {
+      out = ByteStreams.toByteArray(in);
+    } finally {
+      rebuild.getOutputStream().close();
+      in.close();
+    }
+
+    int status;
+    try {
+      status = rebuild.waitFor();
+    } catch (InterruptedException e) {
+      throw new InterruptedIOException("interrupted waiting for " + buck);
+    }
+    if (status != 0) {
+      System.err.write(out);
+      System.err.println();
+      System.exit(status);
+    }
+
+    long time = System.currentTimeMillis() - start;
+    log.info(String.format("UPDATED    %s in %.3fs", target, time / 1000.0));
+  }
+
+  private static Properties loadBuckProperties(File gen)
+      throws FileNotFoundException, IOException {
+    Properties properties = new Properties();
+    InputStream in = new FileInputStream(
+        new File(new File(gen, "tools"), "buck.properties"));
+    try {
+      properties.load(in);
+    } finally {
+      in.close();
+    }
+    return properties;
+  }
+
+  private Resource useMavenDeveloperBuild(File dir) throws IOException {
     dir = dir.getParentFile(); // pop target
     dir = dir.getParentFile(); // pop the module we are in
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java
index 8e3948e..96ab103 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java
@@ -39,12 +39,15 @@
   }
 
   public void open(final String link) throws Exception {
-    String url = cfg.getString("httpd", null, "listenUrl");
+    String url = cfg.getString("gerrit", null, "canonicalWebUrl");
     if (url == null) {
-      return;
-    }
-    if (url.startsWith("proxy-")) {
-      url = url.substring("proxy-".length());
+      url = cfg.getString("httpd", null, "listenUrl");
+      if (url == null) {
+        return;
+      }
+      if (url.startsWith("proxy-")) {
+        url = url.substring("proxy-".length());
+      }
     }
 
     final URI uri;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
index 7658701..3f0f7cf 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
@@ -64,9 +64,11 @@
 
   private InitStep loadInitStep(File jar) {
     try {
+      @SuppressWarnings("resource")
       ClassLoader pluginLoader =
           new URLClassLoader(new URL[] {jar.toURI().toURL()},
               InitPluginStepsLoader.class.getClassLoader());
+      @SuppressWarnings("resource")
       JarFile jarFile = new JarFile(jar);
       Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes();
       String initClassName = jarFileAttributes.getValue("Gerrit-InitStep");
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 bf0af7f..e71f3d1 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
@@ -91,10 +91,10 @@
     extractMailExample("Comment.vm");
     extractMailExample("CommentFooter.vm");
     extractMailExample("CommitMessageEdited.vm");
+    extractMailExample("Footer.vm");
     extractMailExample("Merged.vm");
     extractMailExample("MergeFail.vm");
     extractMailExample("NewChange.vm");
-    extractMailExample("RebasedPatchSet.vm");
     extractMailExample("RegisterNewEmail.vm");
     extractMailExample("ReplacePatchSet.vm");
     extractMailExample("Restored.vm");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
index e28af7c..f750748a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
@@ -57,6 +57,7 @@
     if (!(cl instanceof URLClassLoader)) {
       throw noAddURL("Not loaded by URLClassLoader", null);
     }
+    @SuppressWarnings("resource")
     URLClassLoader urlClassLoader = (URLClassLoader) cl;
 
     Method addURL;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index aae5b48..f4f0bd2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -51,6 +51,13 @@
   @Option(name = "--site-path", aliases = {"-d"}, usage = "Local directory containing site data")
   private File sitePath = new File(".");
 
+  protected SiteProgram() {
+  }
+
+  protected SiteProgram(File sitePath) {
+    this.sitePath = sitePath;
+  }
+
   /** @return the site path specified on the command line. */
   protected File getSitePath() {
     File path = sitePath.getAbsoluteFile();
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
index f1ecadd..a5150e6 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 
+# Version should match lib/bouncycastle/BUCK
 [library "bouncyCastle"]
   name = Bouncy Castle Crypto v144
   url = http://www.bouncycastle.org/download/bcprov-jdk16-144.jar
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
deleted file mode 100644
index 569ed50..0000000
--- a/gerrit-plugin-api/pom.xml
+++ /dev/null
@@ -1,122 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-plugin-api</artifactId>
-  <name>Gerrit Code Review - Plugin API</name>
-
-  <description>
-    API for tightly coupled plugins to compile against
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-sshd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-httpd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-pgm</artifactId>
-      <version>${project.version}</version>
-      <exclusions>
-        <exclusion>
-          <groupId>org.eclipse.jetty</groupId>
-          <artifactId>jetty-servlet</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>servlet-api</artifactId>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-shade-plugin</artifactId>
-        <configuration>
-          <createSourcesJar>true</createSourcesJar>
-          <artifactSet>
-            <excludes>
-              <exclude>gwtjsonrpc:gwtjsonrpc</exclude>
-              <exclude>com.google.gerrit:gerrit-gwtexpui</exclude>
-              <exclude>com.google.gerrit:gerrit-prettify</exclude>
-              <exclude>com.google.gerrit:gerrit-patch-commonsnet</exclude>
-              <exclude>com.google.gerrit:gerrit-patch-jgit</exclude>
-              <exclude>com.google.gerrit:gerrit-util-ssl</exclude>
-
-              <exclude>com.googlecode.juniversalchardet:juniversalchardet</exclude>
-              <exclude>com.googlecode.prolog-cafe:PrologCafe</exclude>
-              <exclude>org.slf4j:slf4j-log4j12</exclude>
-              <exclude>log4j:log4j</exclude>
-
-              <exclude>commons-collections:commons-collections</exclude>
-              <exclude>commons-codec:commons-codec</exclude>
-              <exclude>commons-dbcp:commons-dbcp</exclude>
-              <exclude>commons-lang:commons-lang</exclude>
-              <exclude>commons-net:commons-net</exclude>
-              <exclude>commons-pool:commons-pool</exclude>
-
-              <exclude>asm:asm</exclude>
-              <exclude>eu.medsea.mimeutil:mime-util</exclude>
-              <exclude>org.antlr:antlr</exclude>
-              <exclude>org.antlr:antlr-runtime</exclude>
-              <exclude>org.apache.mina:mina-core</exclude>
-              <exclude>oro:oro</exclude>
-            </excludes>
-          </artifactSet>
-          <filters>
-            <filter>
-              <artifact>com.google.gerrit:gerrit-server</artifact>
-              <excludes>
-                <exclude>gerrit/**</exclude>
-              </excludes>
-            </filter>
-          </filters>
-        </configuration>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>shade</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml
index 9b954f9..f1d6b4d 100644
--- a/gerrit-plugin-archetype/pom.xml
+++ b/gerrit-plugin-archetype/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
+    <version>2.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-plugin-archetype</artifactId>
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
index 92099fa..2b8dfb2 100644
--- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
@@ -94,9 +94,9 @@
     <repository>
       <id>gerrit-api-repository</id>
 #if ($gerritApiVersion.endsWith("SNAPSHOT"))
-      <url>https://gerrit-api.commondatastorage.googleapis.com/snapshot/</url>
+      <url>https://gerrit-api.storage.googleapis.com/snapshot/</url>
 #else
-      <url>https://gerrit-api.commondatastorage.googleapis.com/release/</url>
+      <url>https://gerrit-api.storage.googleapis.com/release/</url>
 #end
     </repository>
   </repositories>
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml
index 66f5a8f..65db8c5 100644
--- a/gerrit-plugin-gwt-archetype/pom.xml
+++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
+    <version>2.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-plugin-gwt-archetype</artifactId>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
index 6e1ba21..ddfdac0 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
@@ -120,9 +120,9 @@
     <repository>
       <id>gerrit-api-repository</id>
 #if ($gerritApiVersion.endsWith("SNAPSHOT"))
-      <url>https://gerrit-api.commondatastorage.googleapis.com/snapshot/</url>
+      <url>https://gerrit-api.storage.googleapis.com/snapshot/</url>
 #else
-      <url>https://gerrit-api.commondatastorage.googleapis.com/release/</url>
+      <url>https://gerrit-api.storage.googleapis.com/release/</url>
 #end
     </repository>
   </repositories>
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
deleted file mode 100644
index 5195204..0000000
--- a/gerrit-plugin-gwtui/pom.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-plugin-gwtui</artifactId>
-  <name>Gerrit Code Review - Plugin GWT UI</name>
-
-  <description>
-    API for UI plugins to build with GWT and integrate with Gerrit
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-user</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-dev</artifactId>
-    </dependency>
-  </dependencies>
-
-   <build>
-     <pluginManagement>
-       <plugins>
-         <plugin>
-           <groupId>org.eclipse.m2e</groupId>
-           <artifactId>lifecycle-mapping</artifactId>
-           <version>1.0.0</version>
-           <configuration>
-             <lifecycleMappingMetadata>
-               <pluginExecutions>
-                 <pluginExecution>
-                   <pluginExecutionFilter>
-                     <groupId>org.codehaus.mojo</groupId>
-                     <artifactId>gwt-maven-plugin</artifactId>
-                     <versionRange>[2.5.0,)</versionRange>
-                     <goals>
-                       <goal>resources</goal>
-                       <goal>compile</goal>
-                       <goal>i18n</goal>
-                       <goal>generateAsync</goal>
-                     </goals>
-                   </pluginExecutionFilter>
-                   <action>
-                     <execute />
-                   </action>
-                 </pluginExecution>
-                 <pluginExecution>
-                   <pluginExecutionFilter>
-                     <groupId>org.apache.maven.plugins</groupId>
-                     <artifactId>maven-war-plugin</artifactId>
-                     <versionRange>[2.1.1,)</versionRange>
-                     <goals>
-                       <goal>exploded</goal>
-                     </goals>
-                   </pluginExecutionFilter>
-                   <action>
-                     <execute />
-                   </action>
-                 </pluginExecution>
-               </pluginExecutions>
-             </lifecycleMappingMetadata>
-          </configuration>
-        </plugin>
-      </plugins>
-    </pluginManagement>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>gwt-maven-plugin</artifactId>
-        <configuration>
-          <module>com.google.gerrit.Plugin</module>
-          <disableClassMetadata>true</disableClassMetadata>
-          <disableCastChecking>true</disableCastChecking>
-        </configuration>
-        <executions>
-          <execution>
-            <goals>
-              <goal>resources</goal>
-              <goal>compile</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
-
diff --git a/gerrit-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml
index 5b12f7d..5e31d90 100644
--- a/gerrit-plugin-js-archetype/pom.xml
+++ b/gerrit-plugin-js-archetype/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
+    <version>2.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-plugin-js-archetype</artifactId>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
index 2a8b469..814fc5e 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
@@ -112,9 +112,9 @@
     <repository>
       <id>gerrit-api-repository</id>
 #if ($gerritApiVersion.endsWith("SNAPSHOT"))
-      <url>https://gerrit-api.commondatastorage.googleapis.com/snapshot/</url>
+      <url>https://gerrit-api.storage.googleapis.com/snapshot/</url>
 #else
-      <url>https://gerrit-api.commondatastorage.googleapis.com/release/</url>
+      <url>https://gerrit-api.storage.googleapis.com/release/</url>
 #end
     </repository>
   </repositories>
diff --git a/gerrit-prettify/BUCK b/gerrit-prettify/BUCK
new file mode 100644
index 0000000..79dc760
--- /dev/null
+++ b/gerrit-prettify/BUCK
@@ -0,0 +1,47 @@
+SRC = 'src/main/java/com/google/gerrit/prettify/'
+
+gwt_module(
+  name = 'client',
+  srcs = glob([
+    SRC + 'client/**/*.java',
+    SRC + 'common/**/*.java',
+  ]),
+  gwtxml = SRC + 'PrettyFormatter.gwt.xml',
+  resources = glob([
+    'src/main/java/com/google/gerrit/prettify/client/*.properties',
+  ]),
+  deps = [
+    ':google-code-prettify',
+    '//gerrit-patch-jgit:client',
+    '//gerrit-reviewdb:client',
+    '//gerrit-gwtexpui:SafeHtml',
+    '//lib:guava',
+    '//lib:gwtjsonrpc',
+    '//lib/gwt:user',
+    '//lib/jgit:jgit',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'google-code-prettify',
+  resources = glob([
+    'src/main/resources/com/google/gerrit/prettify/client/**/*',
+  ]),
+  deps = [
+    '//lib:LICENSE-Apache2.0',
+  ],
+)
+
+java_library(
+  name = 'server',
+  srcs = glob([SRC + 'common/**/*.java']),
+  deps = [
+    '//gerrit-patch-jgit:server',
+    '//gerrit-reviewdb:server',
+    '//lib:guava',
+    '//lib:gwtjsonrpc',
+    '//lib/jgit:jgit',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
deleted file mode 100644
index fa4ae55..0000000
--- a/gerrit-prettify/pom.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-prettify</artifactId>
-  <name>Gerrit Code Review - Prettify</name>
-
-  <description>
-    Prettify based syntax highlighting
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-gwtexpui</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-patch-jgit</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-reviewdb</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-user</artifactId>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-reviewdb/BUCK b/gerrit-reviewdb/BUCK
new file mode 100644
index 0000000..05674cf
--- /dev/null
+++ b/gerrit-reviewdb/BUCK
@@ -0,0 +1,20 @@
+SRC = 'src/main/java/com/google/gerrit/reviewdb/'
+
+gwt_module(
+  name = 'client',
+  srcs = glob([SRC + 'client/**/*.java']),
+  gwtxml = SRC + 'ReviewDB.gwt.xml',
+  deps = [
+    '//lib:gwtorm',
+    '//lib:gwtorm_src'
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'server',
+  srcs = glob([SRC + '**/*.java']),
+  resources = glob(['src/main/resources/**/*']),
+  deps = ['//lib:gwtorm'],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-reviewdb/pom.xml b/gerrit-reviewdb/pom.xml
deleted file mode 100644
index d69c5c5..0000000
--- a/gerrit-reviewdb/pom.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-reviewdb</artifactId>
-  <name>Gerrit Code Review - ReviewDB</name>
-
-  <description>
-    Database schema definition and interface.
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>gwtorm</groupId>
-      <artifactId>gwtorm</artifactId>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
index ad0f130..abdf879 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
@@ -249,7 +249,7 @@
 
   public CommentVisibilityStrategy getCommentVisibilityStrategy() {
     if (commentVisibilityStrategy == null) {
-      return CommentVisibilityStrategy.EXPAND_MOST_RECENT;
+      return CommentVisibilityStrategy.EXPAND_RECENT;
     }
     return CommentVisibilityStrategy.valueOf(commentVisibilityStrategy);
   }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
index d243496..f3cf471 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
@@ -107,6 +107,8 @@
 
   protected InheritableBoolean requireChangeID;
 
+  protected String maxObjectSizeLimit;
+
   protected InheritableBoolean useContentMerge;
 
   protected String defaultDashboardId;
@@ -160,6 +162,10 @@
     return requireChangeID;
   }
 
+  public String getMaxObjectSizeLimit() {
+    return maxObjectSizeLimit;
+  }
+
   public void setUseContributorAgreements(final InheritableBoolean u) {
     useContributorAgreements = u;
   }
@@ -176,6 +182,10 @@
     requireChangeID = cid;
   }
 
+  public void setMaxObjectSizeLimit(final String limit) {
+    maxObjectSizeLimit = limit;
+  }
+
   public SubmitType getSubmitType() {
     return submitType;
   }
@@ -224,6 +234,7 @@
     requireChangeID = update.requireChangeID;
     submitType = update.submitType;
     state = update.state;
+    maxObjectSizeLimit = update.maxObjectSizeLimit;
   }
 
   /**
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java
index 7e0b90c..703edbb 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetAccess.java
@@ -30,10 +30,10 @@
   @Query("WHERE id.changeId = ? ORDER BY id.patchSetId")
   ResultSet<PatchSet> byChange(Change.Id id) throws OrmException;
 
-  @Query("WHERE revision = ? LIMIT 2")
+  @Query("WHERE revision = ?")
   ResultSet<PatchSet> byRevision(RevId rev) throws OrmException;
 
-  @Query("WHERE revision >= ? AND revision <= ? LIMIT 2")
+  @Query("WHERE revision >= ? AND revision <= ?")
   ResultSet<PatchSet> byRevisionRange(RevId reva, RevId revb)
       throws OrmException;
 }
diff --git a/gerrit-server/.settings/org.eclipse.core.resources.prefs b/gerrit-server/.settings/org.eclipse.core.resources.prefs
index 29abf99..1daeba9 100644
--- a/gerrit-server/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-server/.settings/org.eclipse.core.resources.prefs
@@ -3,4 +3,5 @@
 encoding//src/main/resources=UTF-8
 encoding//src/test/java=UTF-8
 encoding//src/test/resources=UTF-8
+encoding//target/generated-sources/prolog-java=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
new file mode 100644
index 0000000..95f5b11
--- /dev/null
+++ b/gerrit-server/BUCK
@@ -0,0 +1,94 @@
+include_defs('//lib/prolog/DEFS')
+
+SRCS = glob(['src/main/java/**/*.java'])
+RESOURCES =  glob(['src/main/resources/**/*'])
+
+# TODO(sop) break up gerrit-server java_library(), its too big
+java_library2(
+  name = 'server',
+  srcs = SRCS,
+  resources = RESOURCES,
+  deps = [
+    '//gerrit-antlr:query_exception',
+    '//gerrit-antlr:query_parser',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-patch-commonsnet:commons-net',
+    '//gerrit-patch-jgit:server',
+    '//gerrit-prettify:server',
+    '//gerrit-reviewdb:server',
+    '//gerrit-util-cli:cli',
+    '//gerrit-util-ssl:ssl',
+    '//lib:args4j',
+    '//lib:automaton',
+    '//lib:gson',
+    '//lib:guava',
+    '//lib:gwtjsonrpc',
+    '//lib:gwtorm',
+    '//lib:jsch',
+    '//lib:jsr305',
+    '//lib:juniversalchardet',
+    '//lib:mime-util',
+    '//lib:ow2-asm',
+    '//lib:ow2-asm-tree',
+    '//lib:ow2-asm-util',
+    '//lib:parboiled-core',
+    '//lib:pegdown',
+    '//lib:velocity',
+    '//lib/antlr:java_runtime',
+    '//lib/commons:codec',
+    '//lib/commons:dbcp',
+    '//lib/commons:lang',
+    '//lib/commons:net',
+    '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
+    '//lib/guice:guice-servlet',
+    '//lib/jgit:jgit',
+    '//lib/log:api',
+    '//lib/prolog:prolog-cafe',
+  ],
+  compile_deps = [
+    '//lib/bouncycastle:bcprov',
+    '//lib/bouncycastle:bcpg',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_sources(
+  name = 'server-src',
+  srcs = SRCS + RESOURCES,
+  visibility = ['PUBLIC'],
+)
+
+prolog_cafe_library(
+  name = 'common_rules',
+  srcs = ['src/main/prolog/gerrit_common.pl'],
+  deps = [':server'],
+  visibility = ['PUBLIC'],
+)
+
+java_test(
+  name = 'server_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  resources = glob(['src/test/resources/**/*']),
+  deps = [
+    ':server',
+    ':common_rules',
+    '//gerrit-antlr:query_exception',
+    '//gerrit-antlr:query_parser',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-reviewdb:server',
+    '//lib:easymock',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:h2',
+    '//lib:junit',
+    '//lib/antlr:java_runtime',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
+    '//lib/jgit:junit',
+    '//lib/prolog:prolog-cafe',
+  ],
+  source_under_test = [':server'],
+)
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
deleted file mode 100644
index 68b1625..0000000
--- a/gerrit-server/pom.xml
+++ /dev/null
@@ -1,248 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-server</artifactId>
-  <name>Gerrit Code Review - Server</name>
-
-  <description>
-    Commons server routines
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>log4j</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.velocity</groupId>
-      <artifactId>velocity</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.junit</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>commons-dbcp</groupId>
-      <artifactId>commons-dbcp</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>commons-lang</groupId>
-      <artifactId>commons-lang</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>commons-net</groupId>
-      <artifactId>commons-net</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-log4j12</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>bouncycastle</groupId>
-      <artifactId>bcpg-jdk15</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>eu.medsea.mimeutil</groupId>
-      <artifactId>mime-util</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.inject</groupId>
-      <artifactId>guice</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.inject.extensions</groupId>
-      <artifactId>guice-servlet</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.inject.extensions</groupId>
-      <artifactId>guice-assistedinject</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>aopalliance</groupId>
-      <artifactId>aopalliance</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-antlr</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-common</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-extension-api</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-util-cli</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-util-ssl</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-patch-commonsnet</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.h2database</groupId>
-      <artifactId>h2</artifactId>
-      <scope>test</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.code.findbugs</groupId>
-      <artifactId>jsr305</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.googlecode.juniversalchardet</groupId>
-      <artifactId>juniversalchardet</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>dk.brics.automaton</groupId>
-      <artifactId>automaton</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.googlecode.prolog-cafe</groupId>
-      <artifactId>PrologCafe</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.pegdown</groupId>
-      <artifactId>pegdown</artifactId>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-antrun-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>prolog-to-java</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>run</goal>
-            </goals>
-            <configuration>
-              <target>
-                <property name="gensrc" location="${project.build.directory}/generated-sources"/>
-
-                <java classname="com.googlecode.prolog_cafe.compiler.Compiler"
-                    fork="true"
-                    failonerror="true"
-                    classpathref="maven.compile.classpath">
-                  <arg value="--show-stack-trace"/>
-                  <arg value="-O"/>
-                  <arg value="-am"/><arg value="${gensrc}/prolog-am"/>
-                  <arg value="-s" /><arg value="${gensrc}/prolog-java"/>
-                  <arg value="src/main/prolog/gerrit_common.pl"/>
-                </java>
-              </target>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>build-helper-maven-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>add-source</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>add-source</goal>
-            </goals>
-            <configuration>
-              <sources>
-                <source>${project.build.directory}/generated-sources/prolog-java</source>
-              </sources>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 2d54601..66a6ae8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.events.ReviewerAddedEvent;
+import com.google.gerrit.server.events.TopicChangedEvent;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.project.ProjectCache;
@@ -186,6 +187,9 @@
     /** Filename of the reviewer added hook. */
     private final File reviewerAddedHook;
 
+    /** Filename of the topic changed hook. */
+    private final File topicChangedHook;
+
     /** Filename of the cla signed hook. */
     private final File claSignedHook;
 
@@ -254,6 +258,7 @@
         changeRestoredHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeRestoredHook", "change-restored")).getPath());
         refUpdatedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdatedHook", "ref-updated")).getPath());
         reviewerAddedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "reviewerAddedHook", "reviewer-added")).getPath());
+        topicChangedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "topicChangedHook", "topic-changed")).getPath());
         claSignedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "claSignedHook", "cla-signed")).getPath());
         refUpdateHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdateHook", "ref-update")).getPath());
         syncHookTimeout = config.getInt("hooks", "syncHookTimeout", 30);
@@ -469,11 +474,13 @@
     }
 
     public void doChangeAbandonedHook(final Change change, final Account account,
-          final String reason, final ReviewDb db) throws OrmException {
+          final PatchSet patchSet, final String reason, final ReviewDb db)
+          throws OrmException {
         final ChangeAbandonedEvent event = new ChangeAbandonedEvent();
 
         event.change = eventFactory.asChangeAttribute(change);
         event.abandoner = eventFactory.asAccountAttribute(account);
+        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
         event.reason = reason;
         fireEvent(change, event, db);
 
@@ -484,17 +491,20 @@
         addArg(args, "--branch", event.change.branch);
         addArg(args, "--topic", event.change.topic);
         addArg(args, "--abandoner", getDisplayName(account));
+        addArg(args, "--commit", event.patchSet.revision);
         addArg(args, "--reason", reason == null ? "" : reason);
 
         runHook(change.getProject(), changeAbandonedHook, args);
     }
 
     public void doChangeRestoredHook(final Change change, final Account account,
-          final String reason, final ReviewDb db) throws OrmException {
+          final PatchSet patchSet, final String reason, final ReviewDb db)
+          throws OrmException {
         final ChangeRestoredEvent event = new ChangeRestoredEvent();
 
         event.change = eventFactory.asChangeAttribute(change);
         event.restorer = eventFactory.asAccountAttribute(account);
+        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
         event.reason = reason;
         fireEvent(change, event, db);
 
@@ -505,6 +515,7 @@
         addArg(args, "--branch", event.change.branch);
         addArg(args, "--topic", event.change.topic);
         addArg(args, "--restorer", getDisplayName(account));
+        addArg(args, "--commit", event.patchSet.revision);
         addArg(args, "--reason", reason == null ? "" : reason);
 
         runHook(change.getProject(), changeRestoredHook, args);
@@ -554,6 +565,25 @@
       runHook(change.getProject(), reviewerAddedHook, args);
     }
 
+    public void doTopicChangedHook(final Change change, final Account account,
+        final String oldTopic, final ReviewDb db)
+            throws OrmException {
+      final TopicChangedEvent event = new TopicChangedEvent();
+
+      event.change = eventFactory.asChangeAttribute(change);
+      event.changer = eventFactory.asAccountAttribute(account);
+      event.oldTopic = oldTopic;
+      fireEvent(change, event, db);
+
+      final List<String> args = new ArrayList<String>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--changer", getDisplayName(account));
+      addArg(args, "--old-topic", oldTopic);
+      addArg(args, "--new-topic", event.change.topic);
+
+      runHook(change.getProject(), topicChangedHook, args);
+    }
+
     public void doClaSignupHook(Account account, ContributorAgreement cla) {
       if (account != null) {
         final List<String> args = new ArrayList<String>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
index 48a52a0..28c64a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
@@ -103,7 +103,7 @@
    * @throws OrmException
    */
   public void doChangeAbandonedHook(Change change, Account account,
-      String reason, ReviewDb db) throws OrmException;
+      PatchSet patchSet, String reason, ReviewDb db) throws OrmException;
 
   /**
    * Fire the Change Restored Hook.
@@ -114,7 +114,7 @@
    * @throws OrmException
    */
   public void doChangeRestoredHook(Change change, Account account,
-      String reason, ReviewDb db) throws OrmException;
+      PatchSet patchSet, String reason, ReviewDb db) throws OrmException;
 
   /**
    * Fire the Ref Updated Hook
@@ -147,6 +147,16 @@
   public void doReviewerAddedHook(Change change, Account account,
       PatchSet patchSet, ReviewDb db) throws OrmException;
 
+  /**
+   * Fire the Topic Changed Hook
+   *
+   * @param change The change itself.
+   * @param account The gerrit user who changed the topic.
+   * @param oldTopic The old topic name.
+   */
+  public void doTopicChangedHook(Change change, Account account,
+      String oldTopic, ReviewDb db) throws OrmException;
+
   public void doClaSignupHook(Account account, ContributorAgreement cla);
 
   /**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
index 6011ab0..021d1d2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -37,7 +37,7 @@
 
   @Override
   public void doChangeAbandonedHook(Change change, Account account,
-      String reason, ReviewDb db) {
+      PatchSet patchSet, String reason, ReviewDb db) {
   }
 
   @Override
@@ -52,7 +52,7 @@
 
   @Override
   public void doChangeRestoredHook(Change change, Account account,
-      String reason, ReviewDb db) {
+      PatchSet patchSet, String reason, ReviewDb db) {
   }
 
   @Override
@@ -91,6 +91,11 @@
   }
 
   @Override
+  public void doTopicChangedHook(Change change, Account account, String oldTopic,
+      ReviewDb db) {
+  }
+
+  @Override
   public void removeChangeListener(ChangeListener listener) {
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index 8f73014..e988683 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -15,19 +15,17 @@
 package com.google.gerrit.server;
 
 import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.errors.EmailException;
-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.PatchSetAncestor;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
 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.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeMessages;
+import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.config.TrackingFooter;
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.events.CommitReceivedEvent;
@@ -46,7 +44,6 @@
 import com.google.gerrit.server.project.RefControl;
 import com.google.gerrit.server.util.IdGenerator;
 import com.google.gerrit.server.util.MagicBranch;
-import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmConcurrencyException;
 import com.google.gwtorm.server.OrmException;
 
@@ -64,6 +61,8 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.util.ChangeIdUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.sql.Timestamp;
@@ -78,11 +77,15 @@
 import java.util.regex.Matcher;
 
 public class ChangeUtil {
+  private static final long SORT_KEY_EPOCH = 1222819200L; // Oct 1 2008 00:00
   private static final Object uuidLock = new Object();
   private static final int SEED = 0x2418e6f9;
   private static int uuidPrefix;
   private static int uuidSeq;
 
+  private static final Logger log =
+      LoggerFactory.getLogger(ChangeUtil.class);
+
   /**
    * Generate a new unique identifier for change message entities.
    *
@@ -197,7 +200,7 @@
       ReviewDb db, RevertedSender.Factory revertedSenderFactory,
       ChangeHooks hooks, Repository git,
       PatchSetInfoFactory patchSetInfoFactory, PersonIdent myIdent,
-      ChangeInserter changeInserter)
+      ChangeInserter.Factory changeInserterFactory)
           throws NoSuchChangeException, EmailException,
       OrmException, MissingObjectException, IncorrectObjectTypeException,
       IOException, InvalidChangeOperationException {
@@ -223,7 +226,7 @@
       revertCommitBuilder.addParentId(commitToRevert);
       revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
       revertCommitBuilder.setAuthor(authorIdent);
-      revertCommitBuilder.setCommitter(myIdent);
+      revertCommitBuilder.setCommitter(authorIdent);
 
       if (message == null) {
         message = MessageFormat.format(
@@ -252,18 +255,14 @@
           user.getAccountId(),
           changeToRevert.getDest());
       change.setTopic(changeToRevert.getTopic());
-
-      PatchSet.Id id =
-          new PatchSet.Id(change.getId(), Change.INITIAL_PATCH_SET_ID);
-      final PatchSet ps = new PatchSet(id);
-      ps.setCreatedOn(change.getCreatedOn());
-      ps.setUploader(change.getOwner());
-      ps.setRevision(new RevId(revertCommit.name()));
+      ChangeInserter ins =
+          changeInserterFactory.create(refControl, change, revertCommit);
+      PatchSet ps = ins.getPatchSet();
 
       String ref = refControl.getRefName();
       final String cmdRef =
           MagicBranch.NEW_PUBLISH_CHANGE
-              + ref.substring(ref.lastIndexOf("/") + 1);
+              + ref.substring(ref.lastIndexOf('/') + 1);
       CommitReceivedEvent commitReceivedEvent =
           new CommitReceivedEvent(new ReceiveCommand(ObjectId.zeroId(),
               revertCommit.getId(), cmdRef), refControl.getProjectControl()
@@ -275,11 +274,6 @@
         throw new InvalidChangeOperationException(e.getMessage());
       }
 
-      PatchSetInfo info = patchSetInfoFactory.get(revertCommit, ps.getId());
-      change.setCurrentPatchSet(info);
-      ChangeUtil.updated(change);
-
-
       final RefUpdate ru = git.updateRef(ps.getRefName());
       ru.setExpectedOldObjectId(ObjectId.zeroId());
       ru.setNewObjectId(revertCommit);
@@ -299,14 +293,17 @@
       msgBuf.append("This patchset was reverted in change: " + change.getKey().get());
       cmsg.setMessage(msgBuf.toString());
 
-      LabelTypes labelTypes = refControl.getProjectControl().getLabelTypes();
-      changeInserter.insertChange(db, change, cmsg, ps, revertCommit,
-          labelTypes, info, Collections.<Account.Id> emptySet());
+      ins.setMessage(cmsg).insert();
 
-      final RevertedSender cm = revertedSenderFactory.create(change);
-      cm.setFrom(user.getAccountId());
-      cm.setChangeMessage(cmsg);
-      cm.send();
+      try {
+        final RevertedSender cm = revertedSenderFactory.create(change);
+        cm.setFrom(user.getAccountId());
+        cm.setChangeMessage(cmsg);
+        cm.send();
+      } catch (Exception err) {
+        log.error("Cannot send email for revert change " + change.getId(),
+            err);
+      }
 
       return change.getId();
     } finally {
@@ -315,13 +312,11 @@
   }
 
   public static Change.Id editCommitMessage(final PatchSet.Id patchSetId,
-      final RefControl refControl, CommitValidators commitValidators,
-      final IdentifiedUser user, final String message, final ReviewDb db,
+      final RefControl refControl, final IdentifiedUser user,
+      final String message, final ReviewDb db,
       final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory,
-      final ChangeHooks hooks, Repository git,
-      final PatchSetInfoFactory patchSetInfoFactory,
-      final GitReferenceUpdated gitRefUpdated, PersonIdent myIdent,
-      final TrackingFooters trackingFooters)
+      Repository git, PersonIdent myIdent,
+      PatchSetInserter.Factory patchSetInserterFactory)
       throws NoSuchChangeException, EmailException, OrmException,
       MissingObjectException, IncorrectObjectTypeException, IOException,
       InvalidChangeOperationException, PatchSetInfoNotAvailableException {
@@ -332,15 +327,18 @@
     }
 
     if (message == null || message.length() == 0) {
-      throw new InvalidChangeOperationException("The commit message cannot be empty");
+      throw new InvalidChangeOperationException(
+          "The commit message cannot be empty");
     }
 
     final RevWalk revWalk = new RevWalk(git);
     try {
       RevCommit commit =
-          revWalk.parseCommit(ObjectId.fromString(originalPS.getRevision().get()));
+          revWalk.parseCommit(ObjectId.fromString(originalPS.getRevision()
+              .get()));
       if (commit.getFullMessage().equals(message)) {
-        throw new InvalidChangeOperationException("New commit message cannot be same as existing commit message");
+        throw new InvalidChangeOperationException(
+            "New commit message cannot be same as existing commit message");
       }
 
       Date now = myIdent.getWhen();
@@ -372,97 +370,17 @@
       newPatchSet.setRevision(new RevId(newCommit.name()));
       newPatchSet.setDraft(originalPS.isDraft());
 
-      final PatchSetInfo info =
-          patchSetInfoFactory.get(newCommit, newPatchSet.getId());
+      final String msg =
+          "Patch Set " + newPatchSet.getPatchSetId()
+              + ": Commit message was updated";
 
-      final String refName = newPatchSet.getRefName();
-      CommitReceivedEvent commitReceivedEvent =
-          new CommitReceivedEvent(new ReceiveCommand(ObjectId.zeroId(),
-              newCommit.getId(), refName.substring(0,
-                  refName.lastIndexOf("/") + 1) + "new"), refControl
-              .getProjectControl().getProject(), refControl.getRefName(),
-              newCommit, user);
-
-      try {
-        commitValidators.validateForReceiveCommits(commitReceivedEvent);
-      } catch (CommitValidationException e) {
-        throw new InvalidChangeOperationException(e.getMessage());
-      }
-
-      final RefUpdate ru = git.updateRef(newPatchSet.getRefName());
-      ru.setExpectedOldObjectId(ObjectId.zeroId());
-      ru.setNewObjectId(newCommit);
-      ru.disableRefLog();
-      if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-        throw new IOException(String.format(
-            "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
-            change.getDest().getParentKey().get(), ru.getResult()));
-      }
-      gitRefUpdated.fire(change.getProject(), ru);
-
-      db.changes().beginTransaction(change.getId());
-      try {
-        Change updatedChange = db.changes().get(change.getId());
-        if (updatedChange != null && updatedChange.getStatus().isOpen()) {
-          change = updatedChange;
-        } else {
-          throw new InvalidChangeOperationException(String.format(
-              "Change %s is closed", change.getId()));
-        }
-
-        ChangeUtil.insertAncestors(db, newPatchSet.getId(), commit);
-        db.patchSets().insert(Collections.singleton(newPatchSet));
-        updatedChange =
-            db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
-              @Override
-              public Change update(Change change) {
-                if (change.getStatus().isClosed()) {
-                  return null;
-                }
-                if (!change.currentPatchSetId().equals(patchSetId)) {
-                  return null;
-                }
-                if (change.getStatus() != Change.Status.DRAFT) {
-                  change.setStatus(Change.Status.NEW);
-                }
-                change.setLastSha1MergeTested(null);
-                change.setCurrentPatchSet(info);
-                ChangeUtil.updated(change);
-                return change;
-              }
-            });
-        if (updatedChange != null) {
-          change = updatedChange;
-        } else {
-          throw new InvalidChangeOperationException(String.format(
-              "Change %s was modified", change.getId()));
-        }
-
-        ApprovalsUtil.copyLabels(db,
-            refControl.getProjectControl().getLabelTypes(),
-            originalPS.getId(),
-            change.currentPatchSetId());
-
-        final List<FooterLine> footerLines = newCommit.getFooterLines();
-        updateTrackingIds(db, change, trackingFooters, footerLines);
-
-        final ChangeMessage cmsg =
-            new ChangeMessage(new ChangeMessage.Key(changeId,
-                ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
-        final String msg = "Patch Set " + newPatchSet.getPatchSetId() + ": Commit message was updated";
-        cmsg.setMessage(msg);
-        db.changeMessages().insert(Collections.singleton(cmsg));
-        db.commit();
-
-        final CommitMessageEditedSender cm = commitMessageEditedSenderFactory.create(change);
-        cm.setFrom(user.getAccountId());
-        cm.setChangeMessage(cmsg);
-        cm.send();
-      } finally {
-        db.rollback();
-      }
-
-      hooks.doPatchsetCreatedHook(change, newPatchSet, db);
+      change = patchSetInserterFactory
+          .create(git, revWalk, refControl, change, newCommit)
+          .setPatchSet(newPatchSet)
+          .setMessage(msg)
+          .setCopyLabels(true)
+          .setValidateForReceiveCommits(true)
+          .insert();
 
       return change.getId();
     } finally {
@@ -534,7 +452,7 @@
     // The encoding uses minutes since Wed Oct 1 00:00:00 2008 UTC.
     // We overrun approximately 4,085 years later, so ~6093.
     //
-    final long lastUpdatedOn = (lastUpdated / 1000L) - 1222819200L;
+    final long lastUpdatedOn = (lastUpdated / 1000L) - SORT_KEY_EPOCH;
     final StringBuilder r = new StringBuilder(16);
     r.setLength(16);
     formatHexInt(r, 0, (int) (lastUpdatedOn / 60));
@@ -542,6 +460,17 @@
     return r.toString();
   }
 
+  public static long parseSortKey(String sortKey) {
+    if ("z".equals(sortKey)) {
+      return Long.MAX_VALUE;
+    }
+    String ts = sortKey.substring(0, 8);
+    int i = 0;
+    while (i < 8 && ts.charAt(i) == '0')
+      i++;
+    return Long.parseLong(ts.substring(i), 16);
+  }
+
   public static void computeSortKey(final Change c) {
     long lastUpdated = c.getLastUpdatedOn().getTime();
     int id = c.getId().get();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 86a6ef8..ae8c6bf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -51,6 +51,20 @@
   }
 
   /**
+   * Identity of the authenticated user.
+   * <p>
+   * In the normal case where a user authenticates as themselves
+   * {@code getRealUser() == this}.
+   * <p>
+   * If {@code X-Gerrit-RunAs} or {@code suexec} was used this method returns
+   * the identity of the account that has permission to act on behalf of this
+   * user.
+   */
+  public CurrentUser getRealUser() {
+    return this;
+  }
+
+  /**
    * Get the set of groups the user is currently a member of.
    * <p>
    * The returned set may be a subset of the user's actual groups; if the user's
@@ -76,11 +90,9 @@
 
   /** Capabilities available to this user account. */
   public CapabilityControl getCapabilities() {
-    CapabilityControl ctl = capabilities;
-    if (ctl == null) {
-      ctl = capabilityControlFactory.create(this);
-      capabilities = ctl;
+    if (capabilities == null) {
+      capabilities = capabilityControlFactory.create(this);
     }
-    return ctl;
+    return capabilities;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 3826293..8e61c9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -97,13 +97,20 @@
     public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupBackend, null, db, id);
+          groupBackend, null, db, id, null);
     }
 
     public IdentifiedUser create(SocketAddress remotePeer, Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupBackend, Providers.of(remotePeer), null, id);
+          groupBackend, Providers.of(remotePeer), null, id,  null);
+    }
+
+    public CurrentUser runAs(SocketAddress remotePeer, Account.Id id,
+        @Nullable CurrentUser caller) {
+      return new IdentifiedUser(capabilityControlFactory,
+          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          groupBackend, Providers.of(remotePeer), null, id, caller);
     }
   }
 
@@ -152,7 +159,13 @@
     public IdentifiedUser create(Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
           authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
-          groupBackend, remotePeerProvider, dbProvider, id);
+          groupBackend, remotePeerProvider, dbProvider, id, null);
+    }
+
+    public IdentifiedUser runAs(Account.Id id, CurrentUser caller) {
+      return new IdentifiedUser(capabilityControlFactory,
+          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          groupBackend, remotePeerProvider, dbProvider, id, caller);
     }
   }
 
@@ -183,6 +196,7 @@
   private GroupMembership effectiveGroups;
   private Set<Change.Id> starredChanges;
   private Collection<AccountProjectWatch> notificationFilters;
+  private CurrentUser realUser;
 
   private IdentifiedUser(
       CapabilityControl.Factory capabilityControlFactory,
@@ -192,7 +206,9 @@
       final Realm realm, final AccountCache accountCache,
       final GroupBackend groupBackend,
       @Nullable final Provider<SocketAddress> remotePeerProvider,
-      @Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
+      @Nullable final Provider<ReviewDb> dbProvider,
+      final Account.Id id,
+      @Nullable CurrentUser realUser) {
     super(capabilityControlFactory);
     this.canonicalUrl = canonicalUrl;
     this.accountCache = accountCache;
@@ -202,6 +218,12 @@
     this.remotePeerProvider = remotePeerProvider;
     this.dbProvider = dbProvider;
     this.accountId = id;
+    this.realUser = realUser != null ? realUser : this;
+  }
+
+  @Override
+  public CurrentUser getRealUser() {
+    return realUser;
   }
 
   // TODO(cranger): maybe get the state through the accountCache instead.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessCollection.java
new file mode 100644
index 0000000..58f93d8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessCollection.java
@@ -0,0 +1,53 @@
+// 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.access;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestCollection;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class AccessCollection implements
+    RestCollection<TopLevelResource, AccessResource> {
+  private final Provider<ListAccess> list;
+  private final DynamicMap<RestView<AccessResource>> views;
+
+  @Inject
+  AccessCollection(Provider<ListAccess> list,
+      DynamicMap<RestView<AccessResource>> views) {
+    this.list = list;
+    this.views = views;
+  }
+
+  @Override
+  public RestView<TopLevelResource> list() {
+    return list.get();
+  }
+
+  @Override
+  public AccessResource parse(TopLevelResource parent, IdString id)
+      throws ResourceNotFoundException {
+    throw new ResourceNotFoundException(id);
+  }
+
+  @Override
+  public DynamicMap<RestView<AccessResource>> views() {
+    return views;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessResource.java
similarity index 61%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/access/AccessResource.java
index b2fb901..22888b8 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/AccessResource.java
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.server.access;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class AccessResource implements RestResource {
+  public static final TypeLiteral<RestView<AccessResource>> ACCESS_KIND =
+      new TypeLiteral<RestView<AccessResource>>() {};
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
new file mode 100644
index 0000000..4002c1a7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
@@ -0,0 +1,308 @@
+// 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.access;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.RefConfigSection;
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.GroupJson;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectJson;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.RefControl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ListAccess implements RestReadView<TopLevelResource> {
+
+  @Option(name = "--project", aliases = {"-p"}, metaVar = "PROJECT",
+      usage = "projects for which the access rights should be returned")
+  private List<String> projects = Lists.newArrayList();
+
+  private final Provider<CurrentUser> self;
+  private final ProjectControl.GenericFactory projectControlFactory;
+  private final ProjectCache projectCache;
+  private final ProjectJson projectJson;
+  private final MetaDataUpdate.Server metaDataUpdateFactory;
+  private final GroupControl.Factory groupControlFactory;
+  private final GroupBackend groupBackend;
+  private final AllProjectsName allProjectsName;
+
+  @Inject
+  public ListAccess(Provider<CurrentUser> self,
+      ProjectControl.GenericFactory projectControlFactory,
+      ProjectCache projectCache, ProjectJson projectJson,
+      MetaDataUpdate.Server metaDataUpdateFactory,
+      GroupControl.Factory groupControlFactory, GroupBackend groupBackend,
+      GroupJson groupJson, AllProjectsName allProjectsName) {
+    this.self = self;
+    this.projectControlFactory = projectControlFactory;
+    this.projectCache = projectCache;
+    this.projectJson = projectJson;
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.groupControlFactory = groupControlFactory;
+    this.groupBackend = groupBackend;
+    this.allProjectsName = allProjectsName;
+  }
+
+  @Override
+  public Map<String, ProjectAccessInfo> apply(TopLevelResource resource)
+      throws ResourceNotFoundException, ResourceConflictException, IOException {
+    Map<String, ProjectAccessInfo> access = Maps.newTreeMap();
+    for (String p: projects) {
+      Project.NameKey projectName = new Project.NameKey(p);
+      ProjectControl pc = open(projectName);
+      ProjectConfig config;
+
+      try {
+        // Load the current configuration from the repository, ensuring it's the most
+        // recent version available. If it differs from what was in the project
+        // state, force a cache flush now.
+        //
+        MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
+        try {
+          config = ProjectConfig.read(md);
+
+          if (config.updateGroupNames(groupBackend)) {
+            md.setMessage("Update group names\n");
+            config.commit(md);
+            projectCache.evict(config.getProject());
+            pc = open(projectName);
+          } else if (config.getRevision() != null
+              && !config.getRevision().equals(
+                  pc.getProjectState().getConfig().getRevision())) {
+            projectCache.evict(config.getProject());
+            pc = open(projectName);
+          }
+        } catch (ConfigInvalidException e) {
+          throw new ResourceConflictException(e.getMessage());
+        } finally {
+          md.close();
+        }
+      } catch (RepositoryNotFoundException e) {
+        throw new ResourceNotFoundException(p);
+      }
+
+      access.put(p, new ProjectAccessInfo(pc, config));
+    }
+    return access;
+  }
+
+  private ProjectControl open(Project.NameKey projectName)
+      throws ResourceNotFoundException, IOException {
+    try {
+      return projectControlFactory.validateFor(projectName,
+          ProjectControl.OWNER | ProjectControl.VISIBLE, self.get());
+    } catch (NoSuchProjectException e) {
+      throw new ResourceNotFoundException(projectName.get());
+    }
+  }
+
+  public class ProjectAccessInfo {
+    public String revision;
+    public ProjectInfo inheritsFrom;
+    public Map<String, AccessSectionInfo> local;
+    public Boolean isOwner;
+    public Set<String> ownerOf;
+    public Boolean canUpload;
+    public Boolean canAdd;
+    public Boolean configVisible;
+
+    public ProjectAccessInfo(ProjectControl pc, ProjectConfig config) {
+      final RefControl metaConfigControl =
+          pc.controlForRef(GitRepositoryManager.REF_CONFIG);
+      local = Maps.newHashMap();
+      ownerOf = Sets.newHashSet();
+      Map<AccountGroup.UUID, Boolean> visibleGroups =
+          new HashMap<AccountGroup.UUID, Boolean>();
+
+      for (AccessSection section : config.getAccessSections()) {
+        String name = section.getName();
+        if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
+          if (pc.isOwner()) {
+            local.put(name, new AccessSectionInfo(section));
+            ownerOf.add(name);
+
+          } else if (metaConfigControl.isVisible()) {
+            local.put(section.getName(), new AccessSectionInfo(section));
+          }
+
+        } else if (RefConfigSection.isValid(name)) {
+          RefControl rc = pc.controlForRef(name);
+          if (rc.isOwner()) {
+            local.put(name, new AccessSectionInfo(section));
+            ownerOf.add(name);
+
+          } else if (metaConfigControl.isVisible()) {
+            local.put(name, new AccessSectionInfo(section));
+
+          } else if (rc.isVisible()) {
+            // Filter the section to only add rules describing groups that
+            // are visible to the current-user. This includes any group the
+            // user is a member of, as well as groups they own or that
+            // are visible to all users.
+
+            AccessSection dst = null;
+            for (Permission srcPerm : section.getPermissions()) {
+              Permission dstPerm = null;
+
+              for (PermissionRule srcRule : srcPerm.getRules()) {
+                AccountGroup.UUID group = srcRule.getGroup().getUUID();
+                if (group == null) {
+                  continue;
+                }
+
+                Boolean canSeeGroup = visibleGroups.get(group);
+                if (canSeeGroup == null) {
+                  try {
+                    canSeeGroup = groupControlFactory.controlFor(group).isVisible();
+                  } catch (NoSuchGroupException e) {
+                    canSeeGroup = Boolean.FALSE;
+                  }
+                  visibleGroups.put(group, canSeeGroup);
+                }
+
+                if (canSeeGroup) {
+                  if (dstPerm == null) {
+                    if (dst == null) {
+                      dst = new AccessSection(name);
+                      local.put(name, new AccessSectionInfo(dst));
+                    }
+                    dstPerm = dst.getPermission(srcPerm.getName(), true);
+                  }
+                  dstPerm.add(srcRule);
+                }
+              }
+            }
+          }
+        }
+      }
+
+      if (ownerOf.isEmpty() && pc.isOwnerAnyRef()) {
+        // Special case: If the section list is empty, this project has no current
+        // access control information. Rely on what ProjectControl determines
+        // is ownership, which probably means falling back to site administrators.
+        ownerOf.add(AccessSection.ALL);
+      }
+
+
+      if (config.getRevision() != null) {
+        revision = config.getRevision().name();
+      }
+
+      ProjectState parent =
+          Iterables.getFirst(pc.getProjectState().parents(), null);
+      if (parent != null) {
+        inheritsFrom = projectJson.format(parent.getProject());
+      }
+
+      if (pc.getProject().getNameKey().equals(allProjectsName)) {
+        if (pc.isOwner()) {
+          ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
+        }
+      }
+
+      isOwner = toBoolean(pc.isOwner());
+      canUpload = toBoolean(pc.isOwner()
+          || (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
+      canAdd = toBoolean(pc.canAddRefs());
+      configVisible = pc.isOwner() || metaConfigControl.isVisible();
+    }
+  }
+
+  public class AccessSectionInfo {
+    public Map<String, PermissionInfo> permissions;
+
+    public AccessSectionInfo(AccessSection section) {
+      permissions = Maps.newHashMap();
+      for (Permission p : section.getPermissions()) {
+        permissions.put(p.getName(), new PermissionInfo(p));
+      }
+    }
+  }
+
+  public class PermissionInfo {
+    public String label;
+    public Boolean exclusive;
+    public Map<String, PermissionRuleInfo> rules;
+
+    public PermissionInfo(Permission permission) {
+      label = permission.getLabel();
+      exclusive = toBoolean(permission.getExclusiveGroup());
+      rules = Maps.newHashMap();
+      for (PermissionRule r : permission.getRules()) {
+        rules.put(r.getGroup().getUUID().get(), new PermissionRuleInfo(r));
+      }
+    }
+  }
+
+  public class PermissionRuleInfo {
+    public PermissionRule.Action action;
+    public Boolean force;
+    public Integer min;
+    public Integer max;
+
+
+    public PermissionRuleInfo(PermissionRule rule) {
+      action = rule.getAction();
+      force = toBoolean(rule.getForce());
+      if (hasRange(rule)) {
+        min = rule.getMin();
+        max = rule.getMax();
+      }
+    }
+
+    private boolean hasRange(PermissionRule rule) {
+      return (!(rule.getMin() == null || rule.getMin() == 0))
+          || (!(rule.getMax() == null || rule.getMax() == 0));
+    }
+  }
+
+  private static Boolean toBoolean(boolean value) {
+    return value ? true : null;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/Module.java
new file mode 100644
index 0000000..cd0d334
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/Module.java
@@ -0,0 +1,29 @@
+// 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.access;
+
+import static com.google.gerrit.server.access.AccessResource.ACCESS_KIND;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+
+public class Module extends RestApiModule {
+  @Override
+  protected void configure() {
+    bind(AccessCollection.class);
+
+    DynamicMap.mapOf(binder(), ACCESS_KIND);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
index a296716..2f95368 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
@@ -112,12 +112,14 @@
   public Integer _account_id;
   public String name;
   public String email;
+  public String username;
 
   private void fill(Account account, boolean detailed) {
     name = account.getFullName();
     if (detailed) {
       _account_id = account.getId().get();
       email = account.getPreferredEmail();
+      username = account.getUserName();
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 1827446..10b34ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -386,46 +386,42 @@
    *         cannot be linked at this time.
    */
   public AuthResult link(final Account.Id to, AuthRequest who)
-      throws AccountException {
+      throws AccountException, OrmException {
+    final ReviewDb db = schema.open();
     try {
-      final ReviewDb db = schema.open();
-      try {
-        who = realm.link(db, to, who);
+      who = realm.link(db, to, who);
 
-        final AccountExternalId.Key key = id(who);
-        AccountExternalId extId = db.accountExternalIds().get(key);
-        if (extId != null) {
-          if (!extId.getAccountId().equals(to)) {
-            throw new AccountException("Identity in use by another account");
-          }
-          update(db, who, extId);
+      final AccountExternalId.Key key = id(who);
+      AccountExternalId extId = db.accountExternalIds().get(key);
+      if (extId != null) {
+        if (!extId.getAccountId().equals(to)) {
+          throw new AccountException("Identity in use by another account");
+        }
+        update(db, who, extId);
 
-        } else {
-          extId = createId(to, who);
-          extId.setEmailAddress(who.getEmailAddress());
-          db.accountExternalIds().insert(Collections.singleton(extId));
+      } else {
+        extId = createId(to, who);
+        extId.setEmailAddress(who.getEmailAddress());
+        db.accountExternalIds().insert(Collections.singleton(extId));
 
-          if (who.getEmailAddress() != null) {
-            final Account a = db.accounts().get(to);
-            if (a.getPreferredEmail() == null) {
-              a.setPreferredEmail(who.getEmailAddress());
-              db.accounts().update(Collections.singleton(a));
-            }
-          }
-
-          if (who.getEmailAddress() != null) {
-            byEmailCache.evict(who.getEmailAddress());
-            byIdCache.evict(to);
+        if (who.getEmailAddress() != null) {
+          final Account a = db.accounts().get(to);
+          if (a.getPreferredEmail() == null) {
+            a.setPreferredEmail(who.getEmailAddress());
+            db.accounts().update(Collections.singleton(a));
           }
         }
 
-        return new AuthResult(to, key, false);
-
-      } finally {
-        db.close();
+        if (who.getEmailAddress() != null) {
+          byEmailCache.evict(who.getEmailAddress());
+          byIdCache.evict(to);
+        }
       }
-    } catch (OrmException e) {
-      throw new AccountException("Cannot link identity", e);
+
+      return new AuthResult(to, key, false);
+
+    } finally {
+      db.close();
     }
   }
 
@@ -439,42 +435,38 @@
    *         cannot be unlinked at this time.
    */
   public AuthResult unlink(final Account.Id from, AuthRequest who)
-      throws AccountException {
+      throws AccountException, OrmException {
+    final ReviewDb db = schema.open();
     try {
-      final ReviewDb db = schema.open();
-      try {
-        who = realm.unlink(db, from, who);
+      who = realm.unlink(db, from, who);
 
-        final AccountExternalId.Key key = id(who);
-        AccountExternalId extId = db.accountExternalIds().get(key);
-        if (extId != null) {
-          if (!extId.getAccountId().equals(from)) {
-            throw new AccountException("Identity in use by another account");
+      final AccountExternalId.Key key = id(who);
+      AccountExternalId extId = db.accountExternalIds().get(key);
+      if (extId != null) {
+        if (!extId.getAccountId().equals(from)) {
+          throw new AccountException("Identity in use by another account");
+        }
+        db.accountExternalIds().delete(Collections.singleton(extId));
+
+        if (who.getEmailAddress() != null) {
+          final Account a = db.accounts().get(from);
+          if (a.getPreferredEmail() != null
+              && a.getPreferredEmail().equals(who.getEmailAddress())) {
+            a.setPreferredEmail(null);
+            db.accounts().update(Collections.singleton(a));
           }
-          db.accountExternalIds().delete(Collections.singleton(extId));
-
-          if (who.getEmailAddress() != null) {
-            final Account a = db.accounts().get(from);
-            if (a.getPreferredEmail() != null
-                && a.getPreferredEmail().equals(who.getEmailAddress())) {
-              a.setPreferredEmail(null);
-              db.accounts().update(Collections.singleton(a));
-            }
-            byEmailCache.evict(who.getEmailAddress());
-            byIdCache.evict(from);
-          }
-
-        } else {
-          throw new AccountException("Identity not found");
+          byEmailCache.evict(who.getEmailAddress());
+          byIdCache.evict(from);
         }
 
-        return new AuthResult(from, key, false);
-
-      } finally {
-        db.close();
+      } else {
+        throw new AccountException("Identity not found");
       }
-    } catch (OrmException e) {
-      throw new AccountException("Cannot unlink identity", e);
+
+      return new AuthResult(from, key, false);
+
+    } finally {
+      db.close();
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
index 9dc423a..629bd15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.inject.TypeLiteral;
 
@@ -26,6 +27,12 @@
   public static final TypeLiteral<RestView<Capability>> CAPABILITY_KIND =
       new TypeLiteral<RestView<Capability>>() {};
 
+  public static final TypeLiteral<RestView<Email>> EMAIL_KIND =
+      new TypeLiteral<RestView<Email>>() {};
+
+  public static final TypeLiteral<RestView<SshKey>> SSH_KEY_KIND =
+      new TypeLiteral<RestView<SshKey>>() {};
+
   private final IdentifiedUser user;
 
   public AccountResource(IdentifiedUser user) {
@@ -57,4 +64,30 @@
       return user.getCapabilities().canPerform(getCapability());
     }
   }
+
+  public static class Email extends AccountResource {
+    private final String email;
+
+    public Email(IdentifiedUser user, String email) {
+      super(user);
+      this.email = email;
+    }
+
+    public String getEmail() {
+      return email;
+    }
+  }
+
+  public static class SshKey extends AccountResource {
+    private final AccountSshKey sshKey;
+
+    public SshKey(IdentifiedUser user, AccountSshKey sshKey) {
+      super(user);
+      this.sshKey = sshKey;
+    }
+
+    public AccountSshKey getSshKey() {
+      return sshKey;
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
index 674046c..e7a6004 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsCreate;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -34,24 +35,28 @@
 import java.util.Set;
 
 public class AccountsCollection implements
-    RestCollection<TopLevelResource, AccountResource> {
+    RestCollection<TopLevelResource, AccountResource>,
+    AcceptsCreate<TopLevelResource>{
   private final Provider<CurrentUser> self;
   private final AccountResolver resolver;
   private final AccountControl.Factory accountControlFactory;
   private final IdentifiedUser.GenericFactory userFactory;
   private final DynamicMap<RestView<AccountResource>> views;
+  private final CreateAccount.Factory createAccountFactory;
 
   @Inject
   AccountsCollection(Provider<CurrentUser> self,
       AccountResolver resolver,
       AccountControl.Factory accountControlFactory,
       IdentifiedUser.GenericFactory userFactory,
-      DynamicMap<RestView<AccountResource>> views) {
+      DynamicMap<RestView<AccountResource>> views,
+      CreateAccount.Factory createAccountFactory) {
     this.self = self;
     this.resolver = resolver;
     this.accountControlFactory = accountControlFactory;
     this.userFactory = userFactory;
     this.views = views;
+    this.createAccountFactory = createAccountFactory;
   }
 
   @Override
@@ -73,7 +78,7 @@
    *        "Full Name <email@example.com>", just the email address, a full name
    *        if it is unique, an account ID, a user name or 'self' for the
    *        calling user
-   * @return the project
+   * @return the user, never null.
    * @throws UnprocessableEntityException thrown if the account ID cannot be
    *         resolved or if the account is not visible to the calling user
    */
@@ -116,4 +121,10 @@
   public DynamicMap<RestView<AccountResource>> views() {
     return views;
   }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public CreateAccount create(TopLevelResource parent, IdString username) {
+    return createAccountFactory.create(username.get());
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
new file mode 100644
index 0000000..2cff009
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -0,0 +1,98 @@
+// 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.account;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteSource;
+import com.google.gerrit.common.errors.InvalidSshKeyException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.RawInput;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AddSshKey.Input;
+import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
+import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+
+public class AddSshKey implements RestModifyView<AccountResource, Input> {
+  public static class Input {
+    public RawInput raw;
+  }
+
+  private final Provider<CurrentUser> self;
+  private final Provider<ReviewDb> dbProvider;
+  private final SshKeyCache sshKeyCache;
+
+  @Inject
+  AddSshKey(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
+      SshKeyCache sshKeyCache) {
+    this.self = self;
+    this.dbProvider = dbProvider;
+    this.sshKeyCache = sshKeyCache;
+  }
+
+  @Override
+  public Response<SshKeyInfo> apply(AccountResource rsrc, Input input)
+      throws AuthException, MethodNotAllowedException, BadRequestException,
+      ResourceConflictException, OrmException, IOException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to add SSH keys");
+    }
+    if (input == null) {
+      input = new Input();
+    }
+    if (input.raw == null) {
+      throw new BadRequestException("SSH public key missing");
+    }
+
+    int max = 0;
+    for (AccountSshKey k : dbProvider.get().accountSshKeys()
+        .byAccount(rsrc.getUser().getAccountId())) {
+      max = Math.max(max, k.getKey().get());
+    }
+
+    final RawInput rawKey = input.raw;
+    String sshPublicKey = new ByteSource() {
+      @Override
+      public InputStream openStream() throws IOException {
+        return rawKey.getInputStream();
+      }
+    }.asCharSource(Charsets.UTF_8).read();
+
+    try {
+      AccountSshKey sshKey =
+          sshKeyCache.create(new AccountSshKey.Id(
+              rsrc.getUser().getAccountId(), max + 1), sshPublicKey);
+      dbProvider.get().accountSshKeys().insert(Collections.singleton(sshKey));
+      sshKeyCache.evict(rsrc.getUser().getUserName());
+      return Response.<SshKeyInfo>created(new SshKeyInfo(sshKey));
+    } catch (InvalidSshKeyException e) {
+      throw new BadRequestException(e.getMessage());
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index d2014ec..01e8002 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -65,8 +65,12 @@
   /** @return true if the user can administer this server. */
   public boolean canAdministrateServer() {
     if (canAdministrateServer == null) {
-      canAdministrateServer = user instanceof PeerDaemonUser
-          || matchAny(capabilities.administrateServer, ALLOWED_RULE);
+      if (user.getRealUser() != user) {
+        canAdministrateServer = false;
+      } else {
+        canAdministrateServer = user instanceof PeerDaemonUser
+            || matchAny(capabilities.administrateServer, ALLOWED_RULE);
+      }
     }
     return canAdministrateServer;
   }
@@ -130,7 +134,6 @@
       || canAdministrateServer();
   }
 
-
   /** @return true if the user can access the database (with gsql). */
   public boolean canAccessDatabase() {
     return canPerform(GlobalCapability.ACCESS_DATABASE);
@@ -154,6 +157,17 @@
         || canAdministrateServer();
   }
 
+  /** @return true if the user can generate HTTP passwords for users other than self. */
+  public boolean canGenerateHttpPassword() {
+    return canPerform(GlobalCapability.GENERATE_HTTP_PASSWORD)
+        || canAdministrateServer();
+  }
+
+  /** @return true if the user can impersonate another user. */
+  public boolean canRunAs() {
+    return canPerform(GlobalCapability.RUN_AS);
+  }
+
   /** @return which priority queue the user's tasks should be submitted to. */
   public QueueProvider.QueueType getQueueType() {
     // If a non-generic group (that is not Anonymous Users or Registered Users)
@@ -216,9 +230,18 @@
       List<PermissionRule> ruleList) {
     int min = 0;
     int max = 0;
-    for (PermissionRule rule : ruleList) {
-      min = Math.min(min, rule.getMin());
-      max = Math.max(max, rule.getMax());
+    if (ruleList.isEmpty()) {
+      PermissionRange.WithDefaults defaultRange =
+          GlobalCapability.getRange(permissionName);
+      if (defaultRange != null) {
+        min = defaultRange.getDefaultMin();
+        max = defaultRange.getDefaultMax();
+      }
+    } else {
+      for (PermissionRule rule : ruleList) {
+        min = Math.min(min, rule.getMin());
+        max = Math.max(max, rule.getMax());
+      }
     }
     return new PermissionRange(permissionName, min, max);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
deleted file mode 100644
index 255c248..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2010 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.account;
-
-import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.util.Collections;
-import java.util.concurrent.Callable;
-
-/** Operation to clear a password for an account. */
-public class ClearPassword implements Callable<AccountExternalId> {
-  public interface Factory {
-    ClearPassword create(AccountExternalId.Key forUser);
-  }
-
-  private final AccountCache accountCache;
-  private final ReviewDb db;
-  private final IdentifiedUser user;
-
-  private final AccountExternalId.Key forUser;
-
-  @Inject
-  ClearPassword(final AccountCache accountCache, final ReviewDb db,
-      final IdentifiedUser user,
-
-      @Assisted AccountExternalId.Key forUser) {
-    this.accountCache = accountCache;
-    this.db = db;
-    this.user = user;
-
-    this.forUser = forUser;
-  }
-
-  public AccountExternalId call() throws OrmException, NoSuchEntityException {
-    AccountExternalId id = db.accountExternalIds().get(forUser);
-    if (id == null || !user.getAccountId().equals(id.getAccountId())) {
-      throw new NoSuchEntityException();
-    }
-
-    id.setPassword(null);
-    db.accountExternalIds().update(Collections.singleton(id));
-    accountCache.evict(user.getAccountId());
-    return id;
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
new file mode 100644
index 0000000..4a1f4db
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -0,0 +1,200 @@
+// 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.account;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupDescriptions;
+import com.google.gerrit.common.errors.InvalidSshKeyException;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.CreateAccount.Input;
+import com.google.gerrit.server.group.GroupsCollection;
+import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+@RequiresCapability(GlobalCapability.CREATE_ACCOUNT)
+public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
+  public static class Input {
+    @DefaultInput
+    public String username;
+    public String name;
+    public String email;
+    public String sshKey;
+    public String httpPassword;
+    public List<String> groups;
+  }
+
+  public static interface Factory {
+    CreateAccount create(String username);
+  }
+
+  private final ReviewDb db;
+  private final IdentifiedUser currentUser;
+  private final GroupsCollection groupsCollection;
+  private final SshKeyCache sshKeyCache;
+  private final AccountCache accountCache;
+  private final AccountByEmailCache byEmailCache;
+  private final String username;
+
+  @Inject
+  CreateAccount(ReviewDb db, IdentifiedUser currentUser,
+      GroupsCollection groupsCollection, SshKeyCache sshKeyCache,
+      AccountCache accountCache, AccountByEmailCache byEmailCache,
+      @Assisted String username) {
+    this.db = db;
+    this.currentUser = currentUser;
+    this.groupsCollection = groupsCollection;
+    this.sshKeyCache = sshKeyCache;
+    this.accountCache = accountCache;
+    this.byEmailCache = byEmailCache;
+    this.username = username;
+  }
+
+  @Override
+  public Object apply(TopLevelResource rsrc, Input input)
+      throws BadRequestException, ResourceConflictException,
+      UnprocessableEntityException, OrmException {
+    if (input == null) {
+      input = new Input();
+    }
+    if (input.username != null && !username.equals(input.username)) {
+      throw new BadRequestException("username must match URL");
+    }
+
+    if (!username.matches(Account.USER_NAME_PATTERN)) {
+      throw new BadRequestException("Username '" + username + "'"
+          + " must contain only letters, numbers, _, - or .");
+    }
+
+    Set<AccountGroup.Id> groups = parseGroups(input.groups);
+
+    Account.Id id = new Account.Id(db.nextAccountId());
+    AccountSshKey key = createSshKey(id, input.sshKey);
+
+    AccountExternalId extUser =
+        new AccountExternalId(id, new AccountExternalId.Key(
+            AccountExternalId.SCHEME_USERNAME, username));
+
+    if (input.httpPassword != null) {
+      extUser.setPassword(input.httpPassword);
+    }
+
+    if (db.accountExternalIds().get(extUser.getKey()) != null) {
+      throw new ResourceConflictException(
+          "username '" + username + "' already exists");
+    }
+    if (input.email != null
+        && db.accountExternalIds().get(getEmailKey(input.email)) != null) {
+      throw new UnprocessableEntityException(
+          "email '" + input.email + "' already exists");
+    }
+
+    try {
+      db.accountExternalIds().insert(Collections.singleton(extUser));
+    } catch (OrmDuplicateKeyException duplicateKey) {
+      throw new ResourceConflictException(
+          "username '" + username + "' already exists");
+    }
+
+    if (input.email != null) {
+      AccountExternalId extMailto =
+          new AccountExternalId(id, getEmailKey(input.email));
+      extMailto.setEmailAddress(input.email);
+      try {
+        db.accountExternalIds().insert(Collections.singleton(extMailto));
+      } catch (OrmDuplicateKeyException duplicateKey) {
+        try {
+          db.accountExternalIds().delete(Collections.singleton(extUser));
+        } catch (OrmException cleanupError) {
+        }
+        throw new UnprocessableEntityException(
+            "email '" + input.email + "' already exists");
+      }
+    }
+
+    Account a = new Account(id);
+    a.setFullName(input.name);
+    a.setPreferredEmail(input.email);
+    db.accounts().insert(Collections.singleton(a));
+
+    if (key != null) {
+      db.accountSshKeys().insert(Collections.singleton(key));
+    }
+
+    for (AccountGroup.Id groupId : groups) {
+      AccountGroupMember m =
+          new AccountGroupMember(new AccountGroupMember.Key(id, groupId));
+      db.accountGroupMembersAudit().insert(Collections.singleton(
+          new AccountGroupMemberAudit(m, currentUser.getAccountId())));
+      db.accountGroupMembers().insert(Collections.singleton(m));
+    }
+
+    sshKeyCache.evict(username);
+    accountCache.evictByUsername(username);
+    byEmailCache.evict(input.email);
+
+    return Response.created(AccountInfo.parse(a, true));
+  }
+
+  private Set<AccountGroup.Id> parseGroups(List<String> groups)
+      throws UnprocessableEntityException {
+    Set<AccountGroup.Id> groupIds = Sets.newHashSet();
+    if (groups != null) {
+      for (String g : groups) {
+        groupIds.add(GroupDescriptions.toAccountGroup(
+            groupsCollection.parseInternal(g)).getId());
+      }
+    }
+    return groupIds;
+  }
+
+  private AccountSshKey createSshKey(Account.Id id, String sshKey)
+      throws BadRequestException {
+    if (sshKey == null) {
+      return null;
+    }
+    try {
+      return sshKeyCache.create(new AccountSshKey.Id(id, 1), sshKey.trim());
+    } catch (InvalidSshKeyException e) {
+      throw new BadRequestException(e.getMessage());
+    }
+  }
+
+  private AccountExternalId.Key getEmailKey(String email) {
+    return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
new file mode 100644
index 0000000..4fda74c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
@@ -0,0 +1,137 @@
+// 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.account;
+
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.reviewdb.client.Account.FieldName;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.CreateEmail.Input;
+import com.google.gerrit.server.account.GetEmails.EmailInfo;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.mail.RegisterNewEmailSender;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CreateEmail implements RestModifyView<AccountResource, Input> {
+  private final Logger log = LoggerFactory.getLogger(getClass());
+
+  public static class Input {
+    @DefaultInput
+    public String email;
+    public boolean preferred;
+    public boolean noConfirmation;
+  }
+
+  public static interface Factory {
+    CreateEmail create(String email);
+  }
+
+  private final Provider<CurrentUser> self;
+  private final Realm realm;
+  private final AuthConfig authConfig;
+  private final AccountManager accountManager;
+  private final RegisterNewEmailSender.Factory registerNewEmailFactory;
+  private final Provider<PutPreferred> putPreferredProvider;
+  private final String email;
+
+  @Inject
+  CreateEmail(Provider<CurrentUser> self,
+      Realm realm,
+      AuthConfig authConfig,
+      AccountManager accountManager,
+      RegisterNewEmailSender.Factory registerNewEmailFactory,
+      Provider<PutPreferred> putPreferredProvider,
+      @Assisted String email) {
+    this.self = self;
+    this.realm = realm;
+    this.authConfig = authConfig;
+    this.accountManager = accountManager;
+    this.registerNewEmailFactory = registerNewEmailFactory;
+    this.putPreferredProvider = putPreferredProvider;
+    this.email = email;
+  }
+
+  @Override
+  public Object apply(AccountResource rsrc, Input input) throws AuthException,
+      BadRequestException, ResourceConflictException,
+      ResourceNotFoundException, OrmException, EmailException,
+      MethodNotAllowedException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to add email address");
+    }
+
+    if (!realm.allowsEdit(FieldName.REGISTER_NEW_EMAIL)) {
+      throw new MethodNotAllowedException("realm does not allow adding emails");
+    }
+
+    if (input == null) {
+      input = new Input();
+    }
+
+    if (input.email != null && !email.equals(input.email)) {
+      throw new BadRequestException("email address must match URL");
+    }
+
+    if (input.noConfirmation
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("must be administrator to use no_confirmation");
+    }
+
+    EmailInfo info = new EmailInfo();
+    info.email = email;
+    if (input.noConfirmation
+        || authConfig.getAuthType() == AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT) {
+      try {
+        accountManager.link(rsrc.getUser().getAccountId(),
+            AuthRequest.forEmail(email));
+      } catch (AccountException e) {
+        throw new ResourceConflictException(e.getMessage());
+      }
+      if (input.preferred) {
+        putPreferredProvider.get().apply(
+            new AccountResource.Email(rsrc.getUser(), email),
+            null);
+        info.preferred = true;
+      }
+    } else {
+      try {
+        registerNewEmailFactory.create(email).send();
+        info.pendingConfirmation = true;
+      } catch (EmailException e) {
+        log.error("Cannot send email verification message to " + email, e);
+        throw e;
+      } catch (RuntimeException e) {
+        log.error("Cannot send email verification message to " + email, e);
+        throw e;
+      }
+    }
+    return Response.created(info);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
new file mode 100644
index 0000000..d44bc2c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
@@ -0,0 +1,60 @@
+// 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.account;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.DeleteActive.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+public class DeleteActive implements RestModifyView<AccountResource, Input> {
+  public static class Input {
+  }
+
+  private final Provider<ReviewDb> dbProvider;
+  private final AccountCache byIdCache;
+
+  @Inject
+  DeleteActive(Provider<ReviewDb> dbProvider, AccountCache byIdCache) {
+    this.dbProvider = dbProvider;
+    this.byIdCache = byIdCache;
+  }
+
+  @Override
+  public Object apply(AccountResource rsrc, Input input)
+      throws ResourceNotFoundException, OrmException {
+    Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId());
+    if (a == null) {
+      throw new ResourceNotFoundException("account not found");
+    }
+    if (!a.isActive()) {
+      throw new ResourceNotFoundException();
+    }
+    a.setActive(false);
+    dbProvider.get().accounts().update(Collections.singleton(a));
+    byIdCache.evict(a.getId());
+    return Response.none();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
new file mode 100644
index 0000000..4b38b9f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -0,0 +1,75 @@
+// 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.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account.FieldName;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.DeleteEmail.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class DeleteEmail implements RestModifyView<AccountResource.Email, Input> {
+  public static class Input {
+  }
+
+  private final Provider<CurrentUser> self;
+  private final Realm realm;
+  private final Provider<ReviewDb> dbProvider;
+  private final AccountManager accountManager;
+
+  @Inject
+  DeleteEmail(Provider<CurrentUser> self, Realm realm,
+      Provider<ReviewDb> dbProvider, AccountManager accountManager) {
+    this.self = self;
+    this.realm = realm;
+    this.dbProvider = dbProvider;
+    this.accountManager = accountManager;
+  }
+
+  @Override
+  public Object apply(AccountResource.Email rsrc, Input input)
+      throws AuthException, ResourceNotFoundException,
+      ResourceConflictException, MethodNotAllowedException, OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to delete email address");
+    }
+    if (!realm.allowsEdit(FieldName.REGISTER_NEW_EMAIL)) {
+      throw new MethodNotAllowedException("realm does not allow deleting emails");
+    }
+    AccountExternalId.Key key = new AccountExternalId.Key(
+        AccountExternalId.SCHEME_MAILTO, rsrc.getEmail());
+    AccountExternalId extId = dbProvider.get().accountExternalIds().get(key);
+    if (extId == null) {
+      throw new ResourceNotFoundException(rsrc.getEmail());
+    }
+    try {
+      accountManager.unlink(rsrc.getUser().getAccountId(),
+          AuthRequest.forEmail(rsrc.getEmail()));
+    } catch (AccountException e) {
+      throw new ResourceConflictException(e.getMessage());
+    }
+    return Response.none();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
new file mode 100644
index 0000000..cf60df1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
@@ -0,0 +1,50 @@
+// 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.account;
+
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.DeleteSshKey.Input;
+import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+public class DeleteSshKey implements
+    RestModifyView<AccountResource.SshKey, Input> {
+  public static class Input {
+  }
+
+  private final Provider<ReviewDb> dbProvider;
+  private final SshKeyCache sshKeyCache;
+
+  @Inject
+  DeleteSshKey(Provider<ReviewDb> dbProvider, SshKeyCache sshKeyCache) {
+    this.dbProvider = dbProvider;
+    this.sshKeyCache = sshKeyCache;
+  }
+
+  @Override
+  public Object apply(AccountResource.SshKey rsrc, Input input)
+      throws OrmException {
+    dbProvider.get().accountSshKeys()
+        .deleteKeys(Collections.singleton(rsrc.getSshKey().getKey()));
+    sshKeyCache.evict(rsrc.getUser().getUserName());
+    return Response.none();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
new file mode 100644
index 0000000..f523e15
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
@@ -0,0 +1,84 @@
+// 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.account;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AccountResource.Email;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Emails implements
+    ChildCollection<AccountResource, AccountResource.Email>,
+    AcceptsCreate<AccountResource> {
+  private final DynamicMap<RestView<AccountResource.Email>> views;
+  private final Provider<GetEmails> list;
+  private final Provider<CurrentUser> self;
+  private final CreateEmail.Factory createEmailFactory;
+
+  @Inject
+  Emails(DynamicMap<RestView<AccountResource.Email>> views,
+      Provider<GetEmails> list,
+      Provider<CurrentUser> self,
+      CreateEmail.Factory createEmailFactory) {
+    this.views = views;
+    this.list = list;
+    this.self = self;
+    this.createEmailFactory = createEmailFactory;
+  }
+
+  @Override
+  public RestView<AccountResource> list() {
+    return list.get();
+  }
+
+  @Override
+  public AccountResource.Email parse(AccountResource rsrc, IdString id)
+      throws ResourceNotFoundException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new ResourceNotFoundException();
+    }
+
+    if ("preferred".equals(id.get())) {
+      String email = rsrc.getUser().getAccount().getPreferredEmail();
+      if (Strings.isNullOrEmpty(email)) {
+        throw new ResourceNotFoundException();
+      }
+      return new AccountResource.Email(rsrc.getUser(), email);
+    } else if (rsrc.getUser().getEmailAddresses().contains(id.get())) {
+      return new AccountResource.Email(rsrc.getUser(), id.get());
+    } else {
+      throw new ResourceNotFoundException();
+    }
+  }
+
+  @Override
+  public DynamicMap<RestView<Email>> views() {
+    return views;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public CreateEmail create(AccountResource parent, IdString email) {
+    return createEmailFactory.create(email.get());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneratePassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneratePassword.java
deleted file mode 100644
index bbab126..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneratePassword.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (C) 2010 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.account;
-
-import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.apache.commons.codec.binary.Base64;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Collections;
-import java.util.concurrent.Callable;
-
-/** Operation to generate a password for an account. */
-public class GeneratePassword implements Callable<AccountExternalId> {
-  private static final int LEN = 12;
-  private static final SecureRandom rng;
-
-  static {
-    try {
-      rng = SecureRandom.getInstance("SHA1PRNG");
-    } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException("Cannot create RNG for password generator", e);
-    }
-  }
-
-  public interface Factory {
-    GeneratePassword create(AccountExternalId.Key forUser);
-  }
-
-  private final AccountCache accountCache;
-  private final ReviewDb db;
-  private final IdentifiedUser user;
-
-  private final AccountExternalId.Key forUser;
-
-  @Inject
-  GeneratePassword(final AccountCache accountCache, final ReviewDb db,
-      final IdentifiedUser user,
-
-      @Assisted AccountExternalId.Key forUser) {
-    this.accountCache = accountCache;
-    this.db = db;
-    this.user = user;
-
-    this.forUser = forUser;
-  }
-
-  public AccountExternalId call() throws OrmException, NoSuchEntityException {
-    AccountExternalId id = db.accountExternalIds().get(forUser);
-    if (id == null || !user.getAccountId().equals(id.getAccountId())) {
-      throw new NoSuchEntityException();
-    }
-
-    id.setPassword(generate());
-    db.accountExternalIds().update(Collections.singleton(id));
-    accountCache.evict(user.getAccountId());
-    return id;
-  }
-
-  private String generate() {
-    byte[] rand = new byte[LEN];
-    rng.nextBytes(rand);
-
-    byte[] enc = Base64.encodeBase64(rand, false);
-    StringBuilder r = new StringBuilder(LEN);
-    for (int i = 0; i < LEN; i++) {
-      if (enc[i] == '=') {
-        break;
-      }
-      r.append((char) enc[i]);
-    }
-    return r.toString();
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java
new file mode 100644
index 0000000..76c7ddb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java
@@ -0,0 +1,29 @@
+// 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.account;
+
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+
+public class GetActive implements RestReadView<AccountResource> {
+  @Override
+  public Object apply(AccountResource rsrc) throws ResourceNotFoundException {
+    if (rsrc.getUser().getAccount().isActive()) {
+      return Response.ok("");
+    }
+    throw new ResourceNotFoundException();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAvatar.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAvatar.java
index 1c66555..a96e713 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAvatar.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetAvatar.java
@@ -16,6 +16,7 @@
 
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.CacheControl;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -24,6 +25,8 @@
 
 import org.kohsuke.args4j.Option;
 
+import java.util.concurrent.TimeUnit;
+
 class GetAvatar implements RestReadView<AccountResource> {
   private final DynamicItem<AvatarProvider> avatarProvider;
 
@@ -41,12 +44,14 @@
       throws ResourceNotFoundException {
     AvatarProvider impl = avatarProvider.get();
     if (impl == null) {
-      throw new ResourceNotFoundException();
+      throw (new ResourceNotFoundException())
+          .caching(CacheControl.PUBLIC(1, TimeUnit.DAYS));
     }
 
     String url = impl.getUrl(rsrc.getUser(), size);
     if (Strings.isNullOrEmpty(url)) {
-      throw new ResourceNotFoundException();
+      throw (new ResourceNotFoundException())
+          .caching(CacheControl.PUBLIC(1, TimeUnit.HOURS));
     } else {
       return Response.redirect(url);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmail.java
new file mode 100644
index 0000000..c56a0a0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmail.java
@@ -0,0 +1,28 @@
+// 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.account;
+
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.account.GetEmails.EmailInfo;
+
+public class GetEmail implements RestReadView<AccountResource.Email> {
+  @Override
+  public EmailInfo apply(AccountResource.Email rsrc) {
+    EmailInfo e = new EmailInfo();
+    e.email = rsrc.getEmail();
+    e.preferred(rsrc.getUser().getAccount().getPreferredEmail());
+    return e;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
new file mode 100644
index 0000000..0e0e77a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
@@ -0,0 +1,72 @@
+// 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.account;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class GetEmails implements RestReadView<AccountResource> {
+  private final Provider<CurrentUser> self;
+
+  @Inject
+  public GetEmails(Provider<CurrentUser> self) {
+    this.self = self;
+  }
+
+  @Override
+  public List<EmailInfo> apply(AccountResource rsrc) throws AuthException,
+      OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to list email addresses");
+    }
+
+    List<EmailInfo> emails = Lists.newArrayList();
+    for (String email : rsrc.getUser().getEmailAddresses()) {
+      if (email != null) {
+        EmailInfo e = new EmailInfo();
+        e.email = email;
+        e.preferred(rsrc.getUser().getAccount().getPreferredEmail());
+        emails.add(e);
+      }
+    }
+    Collections.sort(emails, new Comparator<EmailInfo>() {
+      @Override
+      public int compare(EmailInfo a, EmailInfo b) {
+        return a.email.compareTo(b.email);
+      }
+    });
+    return emails;
+  }
+
+  public static class EmailInfo {
+    public String email;
+    public Boolean preferred;
+    public Boolean pendingConfirmation;
+
+    void preferred(String e) {
+      this.preferred = e != null && e.equals(email) ? true : null;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
new file mode 100644
index 0000000..8eaf4b3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
@@ -0,0 +1,51 @@
+// 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.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class GetHttpPassword implements RestReadView<AccountResource> {
+
+  private final Provider<CurrentUser> self;
+
+  @Inject
+  GetHttpPassword(Provider<CurrentUser> self) {
+    this.self = self;
+  }
+
+  @Override
+  public String apply(AccountResource rsrc) throws AuthException,
+      ResourceNotFoundException, OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to get http password");
+    }
+    AccountState s = rsrc.getUser().state();
+    if (s.getUserName() == null) {
+      throw new ResourceNotFoundException();
+    }
+    String p = s.getPassword(s.getUserName());
+    if (p == null) {
+      throw new ResourceNotFoundException();
+    }
+    return p;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetName.java
similarity index 63%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/account/GetName.java
index b2fb901..646a3b2 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetName.java
@@ -12,12 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.server.account;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.RestReadView;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class GetName implements RestReadView<AccountResource> {
+  @Override
+  public String apply(AccountResource rsrc) {
+    return Strings.nullToEmpty(rsrc.getUser().getAccount().getFullName());
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKey.java
new file mode 100644
index 0000000..37445e9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKey.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.server.account;
+
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.account.AccountResource.SshKey;
+import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
+
+public class GetSshKey implements RestReadView<AccountResource.SshKey> {
+
+  @Override
+  public SshKeyInfo apply(SshKey rsrc) {
+    return new SshKeyInfo(rsrc.getSshKey());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
new file mode 100644
index 0000000..f198b77
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
@@ -0,0 +1,74 @@
+// 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.account;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.List;
+
+public class GetSshKeys implements RestReadView<AccountResource> {
+
+  private final Provider<CurrentUser> self;
+  private final Provider<ReviewDb> dbProvider;
+
+  @Inject
+  GetSshKeys(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider) {
+    this.self = self;
+    this.dbProvider = dbProvider;
+  }
+
+  @Override
+  public List<SshKeyInfo> apply(AccountResource rsrc) throws AuthException,
+      OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to get SSH keys");
+    }
+
+    List<SshKeyInfo> sshKeys = Lists.newArrayList();
+    for (AccountSshKey sshKey : dbProvider.get().accountSshKeys()
+        .byAccount(rsrc.getUser().getAccountId()).toList()) {
+      sshKeys.add(new SshKeyInfo(sshKey));
+    }
+    return sshKeys;
+  }
+
+  public static class SshKeyInfo {
+    public SshKeyInfo(AccountSshKey sshKey) {
+      seq = sshKey.getKey().get();
+      sshPublicKey = sshKey.getSshPublicKey();
+      encodedKey = sshKey.getEncodedKey();
+      algorithm = sshKey.getAlgorithm();
+      comment = Strings.emptyToNull(sshKey.getComment());
+      valid = sshKey.isValid();
+    }
+
+    public int seq;
+    public String sshPublicKey;
+    public String encodedKey;
+    public String algorithm;
+    public String comment;
+    public boolean valid;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetUsername.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetUsername.java
new file mode 100644
index 0000000..8dcb236
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetUsername.java
@@ -0,0 +1,46 @@
+// 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.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class GetUsername implements RestReadView<AccountResource> {
+
+  private final Provider<CurrentUser> self;
+
+  @Inject
+  GetUsername(Provider<CurrentUser> self) {
+    this.self = self;
+  }
+
+  @Override
+  public String apply(AccountResource rsrc) throws AuthException,
+      ResourceNotFoundException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to get username");
+    }
+    String username = rsrc.getUser().getAccount().getUserName();
+    if (username == null) {
+      throw new ResourceNotFoundException();
+    }
+    return username;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
index 08cf1a7..2efc611 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
@@ -28,6 +28,7 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
@@ -58,13 +59,13 @@
 
   public Set<Account> listAccounts(final AccountGroup.UUID groupUUID,
       final Project.NameKey project) throws NoSuchGroupException,
-      NoSuchProjectException, OrmException {
+      NoSuchProjectException, OrmException, IOException {
     return listAccounts(groupUUID, project, new HashSet<AccountGroup.UUID>());
   }
 
   private Set<Account> listAccounts(final AccountGroup.UUID groupUUID,
       final Project.NameKey project, final Set<AccountGroup.UUID> seen)
-      throws NoSuchGroupException, OrmException, NoSuchProjectException {
+      throws NoSuchGroupException, OrmException, NoSuchProjectException, IOException {
     if (AccountGroup.PROJECT_OWNERS.equals(groupUUID)) {
       return getProjectOwners(project, seen);
     } else {
@@ -79,7 +80,7 @@
 
   private Set<Account> getProjectOwners(final Project.NameKey project,
       final Set<AccountGroup.UUID> seen) throws NoSuchProjectException,
-      NoSuchGroupException, OrmException {
+      NoSuchGroupException, OrmException, IOException {
     seen.add(AccountGroup.PROJECT_OWNERS);
     if (project == null) {
       return Collections.emptySet();
@@ -100,7 +101,7 @@
 
   private Set<Account> getGroupMembers(final AccountGroup group,
       final Project.NameKey project, final Set<AccountGroup.UUID> seen)
-      throws NoSuchGroupException, OrmException, NoSuchProjectException {
+      throws NoSuchGroupException, OrmException, NoSuchProjectException, IOException {
     seen.add(group.getGroupUUID());
     final GroupDetail groupDetail =
         groupDetailFactory.create(group.getId()).call();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 57a4a22..fe3086a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -16,9 +16,12 @@
 
 import static com.google.gerrit.server.account.AccountResource.ACCOUNT_KIND;
 import static com.google.gerrit.server.account.AccountResource.CAPABILITY_KIND;
+import static com.google.gerrit.server.account.AccountResource.EMAIL_KIND;
+import static com.google.gerrit.server.account.AccountResource.SSH_KEY_KIND;
 
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 public class Module extends RestApiModule {
   @Override
@@ -27,9 +30,31 @@
     bind(Capabilities.class);
 
     DynamicMap.mapOf(binder(), ACCOUNT_KIND);
+    DynamicMap.mapOf(binder(), EMAIL_KIND);
+    DynamicMap.mapOf(binder(), SSH_KEY_KIND);
     DynamicMap.mapOf(binder(), CAPABILITY_KIND);
 
+    put(ACCOUNT_KIND).to(PutAccount.class);
     get(ACCOUNT_KIND).to(GetAccount.class);
+    get(ACCOUNT_KIND, "name").to(GetName.class);
+    put(ACCOUNT_KIND, "name").to(PutName.class);
+    delete(ACCOUNT_KIND, "name").to(PutName.class);
+    get(ACCOUNT_KIND, "username").to(GetUsername.class);
+    get(ACCOUNT_KIND, "active").to(GetActive.class);
+    put(ACCOUNT_KIND, "active").to(PutActive.class);
+    delete(ACCOUNT_KIND, "active").to(DeleteActive.class);
+    child(ACCOUNT_KIND, "emails").to(Emails.class);
+    get(EMAIL_KIND).to(GetEmail.class);
+    put(EMAIL_KIND).to(PutEmail.class);
+    delete(EMAIL_KIND).to(DeleteEmail.class);
+    put(EMAIL_KIND, "preferred").to(PutPreferred.class);
+    get(ACCOUNT_KIND, "password.http").to(GetHttpPassword.class);
+    put(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
+    delete(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
+    child(ACCOUNT_KIND, "sshkeys").to(SshKeys.class);
+    post(ACCOUNT_KIND, "sshkeys").to(AddSshKey.class);
+    get(SSH_KEY_KIND).to(GetSshKey.class);
+    delete(SSH_KEY_KIND).to(DeleteSshKey.class);
     get(ACCOUNT_KIND, "avatar").to(GetAvatar.class);
     get(ACCOUNT_KIND, "avatar.change.url").to(GetAvatarChangeUrl.class);
     child(ACCOUNT_KIND, "capabilities").to(Capabilities.class);
@@ -37,5 +62,8 @@
     get(ACCOUNT_KIND, "preferences.diff").to(GetDiffPreferences.class);
     put(ACCOUNT_KIND, "preferences.diff").to(SetDiffPreferences.class);
     get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
+
+    install(new FactoryModuleBuilder().build(CreateAccount.Factory.class));
+    install(new FactoryModuleBuilder().build(CreateEmail.Factory.class));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAccount.java
new file mode 100644
index 0000000..f7584ed
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutAccount.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.server.account;
+
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.account.CreateAccount.Input;
+
+public class PutAccount implements RestModifyView<AccountResource, Input> {
+  @Override
+  public Object apply(AccountResource resource, Input input)
+      throws ResourceConflictException {
+    throw new ResourceConflictException("account exists");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
new file mode 100644
index 0000000..a860fda
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
@@ -0,0 +1,60 @@
+// 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.account;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.PutActive.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+public class PutActive implements RestModifyView<AccountResource, Input> {
+  public static class Input {
+  }
+
+  private final Provider<ReviewDb> dbProvider;
+  private final AccountCache byIdCache;
+
+  @Inject
+  PutActive(Provider<ReviewDb> dbProvider, AccountCache byIdCache) {
+    this.dbProvider = dbProvider;
+    this.byIdCache = byIdCache;
+  }
+
+  @Override
+  public Object apply(AccountResource rsrc, Input input)
+      throws ResourceNotFoundException, OrmException {
+    Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId());
+    if (a == null) {
+      throw new ResourceNotFoundException("account not found");
+    }
+    if (a.isActive()) {
+      return Response.ok("");
+    }
+    a.setActive(true);
+    dbProvider.get().accounts().update(Collections.singleton(a));
+    byIdCache.evict(a.getId());
+    return Response.created("");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutEmail.java
new file mode 100644
index 0000000..b79d8dc4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutEmail.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.server.account;
+
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.account.CreateEmail.Input;
+
+public class PutEmail implements RestModifyView<AccountResource.Email, Input> {
+  @Override
+  public Object apply(AccountResource.Email rsrc, Input input)
+      throws ResourceConflictException {
+    throw new ResourceConflictException("email exists");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
new file mode 100644
index 0000000..0601b8d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -0,0 +1,132 @@
+// 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.account;
+
+import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.PutHttpPassword.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Collections;
+
+public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
+  public static class Input {
+    public String httpPassword;
+    public boolean generate;
+  }
+
+  private static final int LEN = 12;
+  private static final SecureRandom rng;
+
+  static {
+    try {
+      rng = SecureRandom.getInstance("SHA1PRNG");
+    } catch (NoSuchAlgorithmException e) {
+      throw new RuntimeException("Cannot create RNG for password generator", e);
+    }
+  }
+
+  private final Provider<CurrentUser> self;
+  private final Provider<ReviewDb> dbProvider;
+  private final AccountCache accountCache;
+
+  @Inject
+  PutHttpPassword(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
+      AccountCache accountCache) {
+    this.self = self;
+    this.dbProvider = dbProvider;
+    this.accountCache = accountCache;
+  }
+
+  @Override
+  public Response<String> apply(AccountResource rsrc, Input input) throws AuthException,
+      ResourceNotFoundException, ResourceConflictException, OrmException {
+    if (input == null) {
+      input = new Input();
+    }
+    input.httpPassword = Strings.emptyToNull(input.httpPassword);
+
+    String newPassword;
+    if (input.generate) {
+      if (self.get() != rsrc.getUser()
+          && !self.get().getCapabilities().canGenerateHttpPassword()) {
+        throw new AuthException("not allowed to generate HTTP password");
+      }
+      newPassword = generate();
+
+    } else if (input.httpPassword == null) {
+      if (self.get() != rsrc.getUser()
+          && !self.get().getCapabilities().canAdministrateServer()) {
+        throw new AuthException("not allowed to clear HTTP password");
+      }
+      newPassword = null;
+    } else {
+      if (!self.get().getCapabilities().canAdministrateServer()) {
+        throw new AuthException("not allowed to set HTTP password directly, "
+            + "need to be Gerrit administrator");
+      }
+      newPassword = input.httpPassword;
+    }
+
+    if (rsrc.getUser().getUserName() == null) {
+      throw new ResourceConflictException("username must be set");
+    }
+
+    AccountExternalId id = dbProvider.get().accountExternalIds()
+        .get(new AccountExternalId.Key(
+            SCHEME_USERNAME,
+            rsrc.getUser().getUserName()));
+    if (id == null) {
+      throw new ResourceNotFoundException();
+    }
+    id.setPassword(newPassword);
+    dbProvider.get().accountExternalIds().update(Collections.singleton(id));
+    accountCache.evict(rsrc.getUser().getAccountId());
+
+    return Strings.isNullOrEmpty(newPassword)
+        ? Response.<String>none()
+        : Response.ok(newPassword);
+  }
+
+  private static String generate() {
+    byte[] rand = new byte[LEN];
+    rng.nextBytes(rand);
+
+    byte[] enc = Base64.encodeBase64(rand, false);
+    StringBuilder r = new StringBuilder(LEN);
+    for (int i = 0; i < LEN; i++) {
+      if (enc[i] == '=') {
+        break;
+      }
+      r.append((char) enc[i]);
+    }
+    return r.toString();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
new file mode 100644
index 0000000..50497c7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
@@ -0,0 +1,83 @@
+// 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.account;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Account.FieldName;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.PutName.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+public class PutName implements RestModifyView<AccountResource, Input> {
+  public static class Input {
+    @DefaultInput
+    public String name;
+  }
+
+  private final Provider<CurrentUser> self;
+  private final Realm realm;
+  private final Provider<ReviewDb> dbProvider;
+  private final AccountCache byIdCache;
+
+  @Inject
+  PutName(Provider<CurrentUser> self, Realm realm,
+      Provider<ReviewDb> dbProvider, AccountCache byIdCache) {
+    this.self = self;
+    this.realm = realm;
+    this.dbProvider = dbProvider;
+    this.byIdCache = byIdCache;
+  }
+
+  @Override
+  public Response<String> apply(AccountResource rsrc, Input input)
+      throws AuthException, MethodNotAllowedException,
+      ResourceNotFoundException, OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to change name");
+    }
+
+    if (!realm.allowsEdit(FieldName.FULL_NAME)) {
+      throw new MethodNotAllowedException("realm does not allow editing name");
+    }
+
+    if (input == null) {
+      input = new Input();
+    }
+
+    Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId());
+    if (a == null) {
+      throw new ResourceNotFoundException("account not found");
+    }
+    a.setFullName(input.name);
+    dbProvider.get().accounts().update(Collections.singleton(a));
+    byIdCache.evict(a.getId());
+    return Strings.isNullOrEmpty(a.getFullName())
+        ? Response.<String> none()
+        : Response.ok(a.getFullName());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
new file mode 100644
index 0000000..d81f361
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
@@ -0,0 +1,68 @@
+// 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.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.PutPreferred.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+public class PutPreferred implements
+    RestModifyView<AccountResource.Email, Input> {
+  static class Input {
+  }
+
+  private final Provider<CurrentUser> self;
+  private final Provider<ReviewDb> dbProvider;
+  private final AccountCache byIdCache;
+
+  @Inject
+  PutPreferred(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
+      AccountCache byIdCache) {
+    this.self = self;
+    this.dbProvider = dbProvider;
+    this.byIdCache = byIdCache;
+  }
+
+  @Override
+  public Response<String> apply(AccountResource.Email rsrc, Input input)
+      throws AuthException, ResourceNotFoundException, OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to set preferred email address");
+    }
+
+    Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId());
+    if (a == null) {
+      throw new ResourceNotFoundException("account not found");
+    }
+    if (rsrc.getEmail().equals(a.getPreferredEmail())) {
+      return Response.ok("");
+    }
+    a.setPreferredEmail(rsrc.getEmail());
+    dbProvider.get().accounts().update(Collections.singleton(a));
+    byIdCache.evict(a.getId());
+    return Response.created("");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
new file mode 100644
index 0000000..5578c3f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
@@ -0,0 +1,77 @@
+// 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.account;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class SshKeys implements
+    ChildCollection<AccountResource, AccountResource.SshKey> {
+  private final DynamicMap<RestView<AccountResource.SshKey>> views;
+  private final Provider<GetSshKeys> list;
+  private final Provider<CurrentUser> self;
+  private final Provider<ReviewDb> dbProvider;
+
+  @Inject
+  SshKeys(DynamicMap<RestView<AccountResource.SshKey>> views,
+      Provider<GetSshKeys> list, Provider<CurrentUser> self,
+      Provider<ReviewDb> dbProvider) {
+    this.views = views;
+    this.list = list;
+    this.self = self;
+    this.dbProvider = dbProvider;
+  }
+
+  @Override
+  public RestView<AccountResource> list() {
+    return list.get();
+  }
+
+  @Override
+  public AccountResource.SshKey parse(AccountResource rsrc, IdString id)
+      throws ResourceNotFoundException, OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new ResourceNotFoundException();
+    }
+
+    try {
+      int seq = Integer.parseInt(id.get(), 10);
+      AccountSshKey sshKey =
+          dbProvider.get().accountSshKeys()
+              .get(new AccountSshKey.Id(rsrc.getUser().getAccountId(), seq));
+      if (sshKey == null) {
+        throw new ResourceNotFoundException(id);
+      }
+      return new AccountResource.SshKey(rsrc.getUser(), sshKey);
+    } catch (NumberFormatException e) {
+      throw new ResourceNotFoundException(id);
+    }
+  }
+
+  @Override
+  public DynamicMap<RestView<AccountResource.SshKey>> views() {
+    return views;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index f766297..6b4c0e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -28,6 +29,7 @@
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.Abandon.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.AbandonedSender;
 import com.google.gerrit.server.mail.ReplyToChangeSender;
 import com.google.gerrit.server.project.ChangeControl;
@@ -41,13 +43,15 @@
 
 import java.util.Collections;
 
-public class Abandon implements RestModifyView<ChangeResource, Input> {
+public class Abandon implements RestModifyView<ChangeResource, Input>,
+    UiAction<ChangeResource> {
   private static final Logger log = LoggerFactory.getLogger(Abandon.class);
 
   private final ChangeHooks hooks;
   private final AbandonedSender.Factory abandonedSenderFactory;
   private final Provider<ReviewDb> dbProvider;
   private final ChangeJson json;
+  private final ChangeIndexer indexer;
 
   public static class Input {
     @DefaultInput
@@ -58,11 +62,13 @@
   Abandon(ChangeHooks hooks,
       AbandonedSender.Factory abandonedSenderFactory,
       Provider<ReviewDb> dbProvider,
-      ChangeJson json) {
+      ChangeJson json,
+      ChangeIndexer indexer) {
     this.hooks = hooks;
     this.abandonedSenderFactory = abandonedSenderFactory;
     this.dbProvider = dbProvider;
     this.json = json;
+    this.indexer = indexer;
   }
 
   @Override
@@ -107,6 +113,7 @@
       db.rollback();
     }
 
+    indexer.index(change);
     try {
       ReplyToChangeSender cm = abandonedSenderFactory.create(change);
       cm.setFrom(caller.getAccountId());
@@ -117,11 +124,20 @@
     }
     hooks.doChangeAbandonedHook(change,
         caller.getAccount(),
+        db.patchSets().get(change.currentPatchSetId()),
         Strings.emptyToNull(input.message),
         db);
     return json.format(change);
   }
 
+  @Override
+  public UiAction.Description getDescription(ChangeResource resource) {
+    return new UiAction.Description()
+      .setLabel("Abandon")
+      .setVisible(resource.getChange().getStatus().isOpen()
+          && resource.getControl().canAbandon());
+  }
+
   private ChangeMessage newMessage(Input input, IdentifiedUser caller,
       Change change) throws OrmException {
     StringBuilder msg = new StringBuilder();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index 5c965e2..0d051a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
+
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.reviewdb.client.Account;
@@ -21,65 +23,172 @@
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.RefControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
 import java.util.Set;
 
 public class ChangeInserter {
+  public static interface Factory {
+    ChangeInserter create(RefControl ctl, Change c, RevCommit rc);
+  }
+
+  private static final Logger log =
+      LoggerFactory.getLogger(ChangeInserter.class);
+
+  private final Provider<ReviewDb> dbProvider;
   private final GitReferenceUpdated gitRefUpdated;
   private final ChangeHooks hooks;
   private final ApprovalsUtil approvalsUtil;
   private final TrackingFooters trackingFooters;
+  private final ChangeIndexer indexer;
+  private final CreateChangeSender.Factory createChangeSenderFactory;
+
+  private final RefControl refControl;
+  private final Change change;
+  private final PatchSet patchSet;
+  private final RevCommit commit;
+  private final PatchSetInfo patchSetInfo;
+
+  private ChangeMessage changeMessage;
+  private Set<Account.Id> reviewers;
+  private Set<Account.Id> extraCC;
+  private boolean runHooks;
+  private boolean sendMail;
 
   @Inject
-  public ChangeInserter(final GitReferenceUpdated gitRefUpdated,
-      ChangeHooks hooks, ApprovalsUtil approvalsUtil,
-      TrackingFooters trackingFooters) {
+  ChangeInserter(Provider<ReviewDb> dbProvider,
+      PatchSetInfoFactory patchSetInfoFactory,
+      GitReferenceUpdated gitRefUpdated,
+      ChangeHooks hooks,
+      ApprovalsUtil approvalsUtil,
+      TrackingFooters trackingFooters,
+      ChangeIndexer indexer,
+      CreateChangeSender.Factory createChangeSenderFactory,
+      @Assisted RefControl refControl,
+      @Assisted Change change,
+      @Assisted RevCommit commit) {
+    this.dbProvider = dbProvider;
     this.gitRefUpdated = gitRefUpdated;
     this.hooks = hooks;
     this.approvalsUtil = approvalsUtil;
     this.trackingFooters = trackingFooters;
+    this.indexer = indexer;
+    this.createChangeSenderFactory = createChangeSenderFactory;
+    this.refControl = refControl;
+    this.change = change;
+    this.commit = commit;
+    this.reviewers = Collections.emptySet();
+    this.extraCC = Collections.emptySet();
+    this.runHooks = true;
+    this.sendMail = true;
+
+    patchSet =
+        new PatchSet(new PatchSet.Id(change.getId(), INITIAL_PATCH_SET_ID));
+    patchSet.setCreatedOn(change.getCreatedOn());
+    patchSet.setUploader(change.getOwner());
+    patchSet.setRevision(new RevId(commit.name()));
+    patchSetInfo = patchSetInfoFactory.get(commit, patchSet.getId());
+    change.setCurrentPatchSet(patchSetInfo);
+    ChangeUtil.computeSortKey(change);
   }
 
-  public void insertChange(ReviewDb db, Change change, PatchSet ps,
-      RevCommit commit, LabelTypes labelTypes, PatchSetInfo info,
-      Set<Account.Id> reviewers) throws OrmException {
-    insertChange(db, change, null, ps, commit, labelTypes, info, reviewers);
+  public ChangeInserter setMessage(ChangeMessage changeMessage) {
+    this.changeMessage = changeMessage;
+    return this;
   }
 
-  public void insertChange(ReviewDb db, Change change,
-      ChangeMessage changeMessage, PatchSet ps, RevCommit commit,
-      LabelTypes labelTypes, PatchSetInfo info, Set<Account.Id> reviewers)
-      throws OrmException {
+  public ChangeInserter setReviewers(Set<Account.Id> reviewers) {
+    this.reviewers = reviewers;
+    return this;
+  }
 
+  public ChangeInserter setExtraCC(Set<Account.Id> extraCC) {
+    this.extraCC = extraCC;
+    return this;
+  }
+
+  public ChangeInserter setDraft(boolean draft) {
+    change.setStatus(draft ? Change.Status.DRAFT : Change.Status.NEW);
+    patchSet.setDraft(draft);
+    return this;
+  }
+
+  public ChangeInserter setRunHooks(boolean runHooks) {
+    this.runHooks = runHooks;
+    return this;
+  }
+
+  public ChangeInserter setSendMail(boolean sendMail) {
+    this.sendMail = sendMail;
+    return this;
+  }
+
+  public PatchSet getPatchSet() {
+    return patchSet;
+  }
+
+  public PatchSetInfo getPatchSetInfo() {
+    return patchSetInfo;
+  }
+
+  public void insert() throws OrmException {
+    ReviewDb db = dbProvider.get();
     db.changes().beginTransaction(change.getId());
     try {
-      ChangeUtil.insertAncestors(db, ps.getId(), commit);
-      db.patchSets().insert(Collections.singleton(ps));
+      ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
+      db.patchSets().insert(Collections.singleton(patchSet));
       db.changes().insert(Collections.singleton(change));
       ChangeUtil.updateTrackingIds(db, change, trackingFooters, commit.getFooterLines());
-      approvalsUtil.addReviewers(db, labelTypes, change, ps, info, reviewers,
-          Collections.<Account.Id> emptySet());
-      if (changeMessage != null) {
-        db.changeMessages().insert(Collections.singleton(changeMessage));
-      }
+      LabelTypes labelTypes = refControl.getProjectControl().getLabelTypes();
+      approvalsUtil.addReviewers(db, labelTypes, change, patchSet, patchSetInfo,
+          reviewers, Collections.<Account.Id> emptySet());
       db.commit();
     } finally {
       db.rollback();
     }
+    if (changeMessage != null) {
+      db.changeMessages().insert(Collections.singleton(changeMessage));
+    }
 
-    gitRefUpdated.fire(change.getProject(), ps.getRefName(), ObjectId.zeroId(),
-        commit);
-    hooks.doPatchsetCreatedHook(change, ps, db);
+    indexer.index(change);
+    gitRefUpdated.fire(change.getProject(), patchSet.getRefName(),
+        ObjectId.zeroId(), commit);
+
+    if (runHooks) {
+      hooks.doPatchsetCreatedHook(change, patchSet, db);
+    }
+
+    if (sendMail) {
+      try {
+        CreateChangeSender cm =
+            createChangeSenderFactory.create(change);
+        cm.setFrom(change.getOwner());
+        cm.setPatchSet(patchSet, patchSetInfo);
+        cm.addReviewers(reviewers);
+        cm.addExtraCC(extraCC);
+        cm.send();
+      } catch (Exception err) {
+        log.error("Cannot send email for new change " + change.getId(), err);
+      }
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 80cdca6..6258d5a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.common.changes.ListChangesOption.ALL_COMMITS;
 import static com.google.gerrit.common.changes.ListChangesOption.ALL_FILES;
 import static com.google.gerrit.common.changes.ListChangesOption.ALL_REVISIONS;
+import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_ACTIONS;
 import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_COMMIT;
 import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_FILES;
 import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_REVISION;
@@ -45,7 +46,10 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
@@ -63,10 +67,8 @@
 import com.google.gerrit.server.account.AccountInfo;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.git.LabelNormalizer;
-import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListEntry;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
@@ -124,10 +126,13 @@
   private final IdentifiedUser.GenericFactory userFactory;
   private final ChangeControl.GenericFactory changeControlGenericFactory;
   private final PatchSetInfoFactory patchSetInfoFactory;
-  private final PatchListCache patchListCache;
+  private final FileInfoJson fileInfoJson;
   private final AccountInfo.Loader.Factory accountLoaderFactory;
   private final Provider<String> urlProvider;
   private final Urls urls;
+  private final DynamicMap<RestView<ChangeResource>> changes;
+  private final Revisions revisions;
+
   private ChangeControl.Factory changeControlUserFactory;
   private SshInfo sshInfo;
   private EnumSet<ListChangesOption> options;
@@ -143,10 +148,12 @@
       IdentifiedUser.GenericFactory uf,
       ChangeControl.GenericFactory ccf,
       PatchSetInfoFactory psi,
-      PatchListCache plc,
+      FileInfoJson fileInfoJson,
       AccountInfo.Loader.Factory ailf,
       @CanonicalWebUrl Provider<String> curl,
-      Urls urls) {
+      Urls urls,
+      DynamicMap<RestView<ChangeResource>> changes,
+      Revisions revisions) {
     this.db = db;
     this.labelNormalizer = ln;
     this.user = u;
@@ -154,10 +161,12 @@
     this.userFactory = uf;
     this.changeControlGenericFactory = ccf;
     this.patchSetInfoFactory = psi;
-    this.patchListCache = plc;
+    this.fileInfoJson = fileInfoJson;
     this.accountLoaderFactory = ailf;
     this.urlProvider = curl;
     this.urls = urls;
+    this.changes = changes;
+    this.revisions = revisions;
 
     options = EnumSet.noneOf(ListChangesOption.class);
   }
@@ -211,7 +220,11 @@
     List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
     for (List<ChangeData> changes : in) {
       ChangeData.ensureChangeLoaded(db, changes);
-      ChangeData.ensureCurrentPatchSetLoaded(db, changes);
+      if (has(ALL_REVISIONS)) {
+        ChangeData.ensureAllPatchSetsLoaded(db, changes);
+      } else {
+        ChangeData.ensureCurrentPatchSetLoaded(db, changes);
+      }
       ChangeData.ensureCurrentApprovalsLoaded(db, changes);
       res.add(toChangeInfo(changes));
     }
@@ -277,6 +290,14 @@
       }
     }
 
+    if (has(CURRENT_ACTIONS) && user instanceof IdentifiedUser) {
+      out.actions = Maps.newTreeMap();
+      for (UiAction.Description d : UiActions.from(
+          changes,
+          new ChangeResource(control(cd)))) {
+        out.actions.put(d.getId(), new ActionInfo(d));
+      }
+    }
     lastControl = null;
     return out;
   }
@@ -469,16 +490,18 @@
           continue;
         }
         Integer value;
+        Timestamp date = null;
         PatchSetApproval psa = current.get(accountId, lt.getName());
         if (psa != null) {
           value = Integer.valueOf(psa.getValue());
+          date = psa.getGranted();
         } else {
           // Either the user cannot vote on this label, or there just wasn't a
           // dummy approval for this label. Explicitly check whether the user
           // can vote on this label.
           value = labelNormalizer.canVote(ctl, lt, accountId) ? 0 : null;
         }
-        e.getValue().addApproval(approvalInfo(accountId, value));
+        e.getValue().addApproval(approvalInfo(accountId, value, date));
       }
     }
   }
@@ -522,7 +545,7 @@
 
       if (detailed) {
         for (String name : labels.keySet()) {
-          ApprovalInfo ai = approvalInfo(accountId, 0);
+          ApprovalInfo ai = approvalInfo(accountId, 0, null);
           byLabel.put(name, ai);
           labels.get(name).addApproval(ai);
         }
@@ -537,6 +560,7 @@
         ApprovalInfo info = byLabel.get(type.getName());
         if (info != null) {
           info.value = Integer.valueOf(val);
+          info.date = psa.getGranted();
         }
 
         LabelInfo li = labels.get(type.getName());
@@ -561,9 +585,10 @@
     return labels;
   }
 
-  private ApprovalInfo approvalInfo(Account.Id id, Integer value) {
+  private ApprovalInfo approvalInfo(Account.Id id, Integer value, Timestamp date) {
     ApprovalInfo ai = new ApprovalInfo(id);
     ai.value = value;
+    ai.date = date;
     accountLoader.put(ai);
     return ai;
   }
@@ -750,74 +775,51 @@
 
     if (has(ALL_COMMITS) || (out.isCurrent && has(CURRENT_COMMIT))) {
       try {
-        PatchSetInfo info = patchSetInfoFactory.get(db.get(), in.getId());
-        out.commit = new CommitInfo();
-        out.commit.parents = Lists.newArrayListWithCapacity(info.getParents().size());
-        out.commit.author = toGitPerson(info.getAuthor());
-        out.commit.committer = toGitPerson(info.getCommitter());
-        out.commit.subject = info.getSubject();
-        out.commit.message = info.getMessage();
-
-        for (ParentInfo parent : info.getParents()) {
-          CommitInfo i = new CommitInfo();
-          i.commit = parent.id.get();
-          i.subject = parent.shortMessage;
-          out.commit.parents.add(i);
-        }
+        out.commit = toCommit(in);
       } catch (PatchSetInfoNotAvailableException e) {
         log.warn("Cannot load PatchSetInfo " + in.getId(), e);
       }
     }
 
     if (has(ALL_FILES) || (out.isCurrent && has(CURRENT_FILES))) {
-      PatchList list;
       try {
-        list = patchListCache.get(cd.change(db), in);
+        out.files = fileInfoJson.toFileInfoMap(cd.change(db), in);
+        out.files.remove(Patch.COMMIT_MSG);
       } catch (PatchListNotAvailableException e) {
         log.warn("Cannot load PatchList " + in.getId(), e);
-        list = null;
       }
-      if (list != null) {
-        out.files = Maps.newTreeMap();
-        for (PatchListEntry e : list.getPatches()) {
-          if (Patch.COMMIT_MSG.equals(e.getNewName())) {
-            continue;
-          }
+    }
 
-          FileInfo d = new FileInfo();
-          d.status = e.getChangeType() != Patch.ChangeType.MODIFIED
-              ? e.getChangeType().getCode()
-              : null;
-          d.oldPath = e.getOldName();
-          if (e.getPatchType() == Patch.PatchType.BINARY) {
-            d.binary = true;
-          } else {
-            d.linesInserted = e.getInsertions() > 0 ? e.getInsertions() : null;
-            d.linesDeleted = e.getDeletions() > 0 ? e.getDeletions() : null;
-          }
-
-          FileInfo o = out.files.put(e.getNewName(), d);
-          if (o != null) {
-            // This should only happen on a delete-add break created by JGit
-            // when the file was rewritten and too little content survived. Write
-            // a single record with data from both sides.
-            d.status = Patch.ChangeType.REWRITE.getCode();
-            if (o.binary != null && o.binary) {
-              d.binary = true;
-            }
-            if (o.linesInserted != null) {
-              d.linesInserted = o.linesInserted;
-            }
-            if (o.linesDeleted != null) {
-              d.linesDeleted = o.linesDeleted;
-            }
-          }
-        }
+    if (out.isCurrent && has(CURRENT_ACTIONS) && user instanceof IdentifiedUser) {
+      out.actions = Maps.newTreeMap();
+      for (UiAction.Description d : UiActions.from(
+          revisions,
+          new RevisionResource(new ChangeResource(control(cd)), in))) {
+        out.actions.put(d.getId(), new ActionInfo(d));
       }
     }
     return out;
   }
 
+  CommitInfo toCommit(PatchSet in)
+      throws PatchSetInfoNotAvailableException {
+    PatchSetInfo info = patchSetInfoFactory.get(db.get(), in.getId());
+    CommitInfo commit = new CommitInfo();
+    commit.parents = Lists.newArrayListWithCapacity(info.getParents().size());
+    commit.author = toGitPerson(info.getAuthor());
+    commit.committer = toGitPerson(info.getCommitter());
+    commit.subject = info.getSubject();
+    commit.message = info.getMessage();
+
+    for (ParentInfo parent : info.getParents()) {
+      CommitInfo i = new CommitInfo();
+      i.commit = parent.id.get();
+      i.subject = parent.shortMessage;
+      commit.parents.add(i);
+    }
+    return commit;
+  }
+
   private Map<String, FetchInfo> makeFetchMap(ChangeData cd, PatchSet in)
       throws OrmException {
     Map<String, FetchInfo> r = Maps.newLinkedHashMap();
@@ -879,6 +881,7 @@
 
     AccountInfo owner;
 
+    Map<String, ActionInfo> actions;
     Map<String, LabelInfo> labels;
     Map<String, Collection<String>> permitted_labels;
     Collection<AccountInfo> removable_reviewers;
@@ -902,7 +905,8 @@
     int _number;
     Map<String, FetchInfo> fetch;
     CommitInfo commit;
-    Map<String, FileInfo> files;
+    Map<String, FileInfoJson.FileInfo> files;
+    Map<String, ActionInfo> actions;
   }
 
   static class FetchInfo {
@@ -923,6 +927,7 @@
   }
 
   static class CommitInfo {
+    final String kind = "gerritcodereview#commit";
     String commit;
     List<CommitInfo> parents;
     GitPerson author;
@@ -931,14 +936,6 @@
     String message;
   }
 
-  static class FileInfo {
-    Character status;
-    Boolean binary;
-    String oldPath;
-    Integer linesInserted;
-    Integer linesDeleted;
-  }
-
   static class LabelInfo {
     transient SubmitRecord.Label.Status _status;
 
@@ -963,6 +960,7 @@
 
   static class ApprovalInfo extends AccountInfo {
     Integer value;
+    Timestamp date;
 
     ApprovalInfo(Account.Id id) {
       super(id);
@@ -976,4 +974,18 @@
     String message;
     Integer _revisionNumber;
   }
+
+  static class ActionInfo {
+    String method;
+    String label;
+    String title;
+    Boolean enabled;
+
+    ActionInfo(UiAction.Description d) {
+      method = d.getMethod();
+      label = d.getLabel();
+      title = d.getTitle();
+      enabled = d.isEnabled() ? true : null;
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
new file mode 100644
index 0000000..18f45bb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -0,0 +1,102 @@
+// 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.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+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.CherryPick.Input;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.RefControl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+class CherryPick implements RestModifyView<RevisionResource, Input>,
+    UiAction<RevisionResource> {
+  private final Provider<ReviewDb> dbProvider;
+  private final Provider<CherryPickChange> cherryPickChange;
+  private final ChangeJson json;
+
+  static class Input {
+    String message;
+    String destination;
+  }
+
+  @Inject
+  CherryPick(Provider<ReviewDb> dbProvider,
+      Provider<CherryPickChange> cherryPickChange,
+      ChangeJson json) {
+    this.dbProvider = dbProvider;
+    this.cherryPickChange = cherryPickChange;
+    this.json = json;
+  }
+
+  @Override
+  public Object apply(RevisionResource revision, Input input)
+      throws AuthException, BadRequestException, ResourceConflictException,
+      Exception {
+    final ChangeControl control = revision.getControl();
+
+    if (input.message == null || input.message.trim().isEmpty()) {
+      throw new BadRequestException("message must be non-empty");
+    } else if (input.destination == null || input.destination.trim().isEmpty()) {
+      throw new BadRequestException("destination must be non-empty");
+    }
+
+    ReviewDb db = dbProvider.get();
+    if (!control.isVisible(db)) {
+      throw new AuthException("Cherry pick not permitted");
+    }
+
+    String refName = input.destination;
+    if (!refName.startsWith("refs/")) {
+      refName = "refs/heads/" + input.destination;
+    }
+
+    RefControl refControl = control.getProjectControl().controlForRef(refName);
+    if (!refControl.canUpload()) {
+      throw new AuthException("Not allowed to cherry pick "
+          + revision.getChange().getId().toString() + " to "
+          + input.destination);
+    }
+
+    final PatchSet.Id patchSetId = revision.getPatchSet().getId();
+    try {
+      Change.Id cherryPickedChangeId = cherryPickChange.get().cherryPick(
+          patchSetId, input.message,
+          input.destination, refControl);
+      return json.format(cherryPickedChangeId);
+    } catch (InvalidChangeOperationException e) {
+      throw new BadRequestException(e.getMessage());
+    } catch (MergeException  e) {
+      throw new ResourceConflictException(e.getMessage());
+    }
+  }
+
+  @Override
+  public UiAction.Description getDescription(RevisionResource resource) {
+    return new UiAction.Description()
+      .setLabel("Cherry Pick")
+      .setTitle("Cherry pick change to a different branch")
+      .setVisible(resource.getControl().getProjectControl().canUpload());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
new file mode 100644
index 0000000..9f5ecef
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -0,0 +1,258 @@
+// 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.change;
+
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.reviewdb.client.Branch;
+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.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.ssh.NoSshInfo;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.util.ChangeIdUtil;
+
+import java.io.IOException;
+import java.util.List;
+
+public class CherryPickChange {
+
+  private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
+
+  private final ReviewDb db;
+  private final GitRepositoryManager gitManager;
+  private final PersonIdent myIdent;
+  private final IdentifiedUser currentUser;
+  private final CommitValidators.Factory commitValidatorsFactory;
+  private final ChangeInserter.Factory changeInserterFactory;
+  private final PatchSetInserter.Factory patchSetInserterFactory;
+  final MergeUtil.Factory mergeUtilFactory;
+
+  @Inject
+  CherryPickChange(final ReviewDb db, @GerritPersonIdent final PersonIdent myIdent,
+      final GitRepositoryManager gitManager, final IdentifiedUser currentUser,
+      final CommitValidators.Factory commitValidatorsFactory,
+      final ChangeInserter.Factory changeInserterFactory,
+      final PatchSetInserter.Factory patchSetInserterFactory,
+      final MergeUtil.Factory mergeUtilFactory) {
+    this.db = db;
+    this.gitManager = gitManager;
+    this.myIdent = myIdent;
+    this.currentUser = currentUser;
+    this.commitValidatorsFactory = commitValidatorsFactory;
+    this.changeInserterFactory = changeInserterFactory;
+    this.patchSetInserterFactory = patchSetInserterFactory;
+    this.mergeUtilFactory = mergeUtilFactory;
+  }
+
+  public Change.Id cherryPick(final PatchSet.Id patchSetId,
+      final String message, final String destinationBranch,
+      final RefControl refControl) throws NoSuchChangeException,
+      EmailException, OrmException, MissingObjectException,
+      IncorrectObjectTypeException, IOException,
+      InvalidChangeOperationException, MergeException {
+
+    final Change.Id changeId = patchSetId.getParentKey();
+    final PatchSet patch = db.patchSets().get(patchSetId);
+    if (patch == null) {
+      throw new NoSuchChangeException(changeId);
+    }
+    if (destinationBranch == null || destinationBranch.length() == 0) {
+      throw new InvalidChangeOperationException(
+          "Cherry Pick: Destination branch cannot be null or empty");
+    }
+
+    Project.NameKey project = db.changes().get(changeId).getProject();
+    final Repository git;
+    try {
+      git = gitManager.openRepository(project);
+    } catch (RepositoryNotFoundException e) {
+      throw new NoSuchChangeException(changeId, e);
+    }
+
+    try {
+      RevWalk revWalk = new RevWalk(git);
+      try {
+        Ref destRef = git.getRef(destinationBranch);
+        if (destRef == null) {
+          throw new InvalidChangeOperationException("Branch "
+              + destinationBranch + " does not exist.");
+        }
+
+        final RevCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
+
+        RevCommit commitToCherryPick =
+            revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+
+        PersonIdent committerIdent =
+            currentUser.newCommitterIdent(myIdent.getWhen(),
+                myIdent.getTimeZone());
+
+        RevCommit cherryPickCommit;
+        ObjectInserter oi = git.newObjectInserter();
+        try {
+          ProjectState projectState = refControl.getProjectControl().getProjectState();
+          cherryPickCommit =
+              mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
+                  commitToCherryPick, committerIdent, message, revWalk);
+        } finally {
+          oi.release();
+        }
+
+        if (cherryPickCommit == null) {
+          throw new MergeException(
+              "Could not create a merge commit during the cherry pick");
+        }
+
+        Change.Key changeKey;
+        final List<String> idList = cherryPickCommit.getFooterLines(CHANGE_ID);
+        if (!idList.isEmpty()) {
+          final String idStr = idList.get(idList.size() - 1).trim();
+          changeKey = new Change.Key(idStr);
+        } else {
+          final ObjectId computedChangeId =
+              ChangeIdUtil
+                  .computeChangeId(cherryPickCommit.getTree(), mergeTip,
+                      cherryPickCommit.getAuthorIdent(), myIdent, message);
+
+          changeKey = new Change.Key("I" + computedChangeId.name());
+        }
+
+        List<Change> destChanges =
+            db.changes()
+                .byBranchKey(
+                    new Branch.NameKey(db.changes().get(changeId).getProject(),
+                        destRef.getName()), changeKey).toList();
+
+        if (destChanges.size() > 1) {
+          throw new InvalidChangeOperationException("Several changes with key "
+              + changeKey + " resides on the same branch. "
+              + "Cannot create a new patch set.");
+        } else if (destChanges.size() == 1) {
+          // The change key exists on the destination branch. The cherry pick
+          // will be added as a new patch set.
+          return insertPatchSet(git, revWalk, destChanges.get(0), patchSetId,
+              cherryPickCommit, refControl);
+        } else {
+          // Change key not found on destination branch. We can create a new
+          // change.
+          return createNewChange(git, revWalk, changeKey, project, patchSetId, destRef,
+              cherryPickCommit, refControl);
+        }
+      } finally {
+        revWalk.release();
+      }
+    } finally {
+      git.close();
+    }
+  }
+
+  private Change.Id insertPatchSet(Repository git, RevWalk revWalk, Change change,
+      PatchSet.Id patchSetId, RevCommit cherryPickCommit,
+      RefControl refControl) throws InvalidChangeOperationException,
+      IOException, OrmException, NoSuchChangeException {
+    patchSetInserterFactory
+        .create(git, revWalk, refControl, change, cherryPickCommit)
+        .setMessage(buildChangeMessage(patchSetId, change))
+        .insert();
+    return change.getId();
+  }
+
+  private Change.Id createNewChange(Repository git, RevWalk revWalk,
+      Change.Key changeKey, Project.NameKey project, PatchSet.Id patchSetId,
+      Ref destRef, RevCommit cherryPickCommit, RefControl refControl)
+      throws OrmException, InvalidChangeOperationException, IOException {
+    Change change =
+        new Change(changeKey, new Change.Id(db.nextChangeId()),
+            currentUser.getAccountId(), new Branch.NameKey(project,
+                destRef.getName()));
+    ChangeInserter ins =
+        changeInserterFactory.create(refControl, change, cherryPickCommit);
+    PatchSet newPatchSet = ins.getPatchSet();
+
+    CommitValidators commitValidators =
+        commitValidatorsFactory.create(refControl, new NoSshInfo(), git);
+    CommitReceivedEvent commitReceivedEvent =
+        new CommitReceivedEvent(new ReceiveCommand(ObjectId.zeroId(),
+            cherryPickCommit.getId(), newPatchSet.getRefName()), refControl
+            .getProjectControl().getProject(), refControl.getRefName(),
+            cherryPickCommit, currentUser);
+
+    try {
+      commitValidators.validateForGerritCommits(commitReceivedEvent);
+    } catch (CommitValidationException e) {
+      throw new InvalidChangeOperationException(e.getMessage());
+    }
+
+    final RefUpdate ru = git.updateRef(newPatchSet.getRefName());
+    ru.setExpectedOldObjectId(ObjectId.zeroId());
+    ru.setNewObjectId(cherryPickCommit);
+    ru.disableRefLog();
+    if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+      throw new IOException(String.format(
+          "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
+          change.getDest().getParentKey().get(), ru.getResult()));
+    }
+
+    ins.setMessage(buildChangeMessage(patchSetId, change)).insert();
+
+    return change.getId();
+  }
+
+  private ChangeMessage buildChangeMessage(PatchSet.Id patchSetId, Change dest)
+      throws OrmException {
+    ChangeMessage cmsg =
+        new ChangeMessage(new ChangeMessage.Key(patchSetId.getParentKey(),
+            ChangeUtil.messageUUID(db)), currentUser.getAccountId(),
+            patchSetId);
+    StringBuilder msgBuf =
+        new StringBuilder("Patch Set " + patchSetId.get()
+            + ": Cherry Picked");
+    msgBuf.append("\n\n");
+    msgBuf.append("This patchset was cherry picked to change: "
+        + dest.getKey().get());
+    cmsg.setMessage(msgBuf.toString());
+    return cmsg;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
index 798b9c6..c0942b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CommentInfo.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.server.account.AccountInfo;
@@ -22,9 +23,6 @@
 import java.sql.Timestamp;
 
 public class CommentInfo {
-  static enum Side {
-    PARENT, REVISION;
-  }
 
   final String kind = "gerritcodereview#comment";
   String id;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
index 5157af4..9ef1cfe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -60,7 +61,7 @@
         rsrc.getAccountId(),
         Url.decode(in.inReplyTo));
     c.setStatus(Status.DRAFT);
-    c.setSide(in.side == CommentInfo.Side.PARENT ? (short) 0 : (short) 1);
+    c.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
     c.setMessage(in.message.trim());
     db.get().patchComments().insert(Collections.singleton(c));
     return Response.created(new CommentInfo(c, null));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
new file mode 100644
index 0000000..fec72d6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -0,0 +1,97 @@
+// 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.common.collect.Maps;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.PatchListKey;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+public class FileInfoJson {
+  private final PatchListCache patchListCache;
+
+  @Inject
+  FileInfoJson(PatchListCache patchListCache) {
+    this.patchListCache = patchListCache;
+  }
+
+  Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet)
+      throws PatchListNotAvailableException {
+    return toFileInfoMap(change, patchSet, null);
+  }
+
+  Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet, @Nullable PatchSet base)
+      throws PatchListNotAvailableException {
+    ObjectId a = (base == null)
+        ? null
+        : ObjectId.fromString(base.getRevision().get());
+    ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
+    PatchList list = patchListCache.get(
+        new PatchListKey(change.getProject(), a, b, Whitespace.IGNORE_NONE));
+
+    Map<String, FileInfo> files = Maps.newTreeMap();
+    for (PatchListEntry e : list.getPatches()) {
+      FileInfoJson.FileInfo d = new FileInfoJson.FileInfo();
+      d.status = e.getChangeType() != Patch.ChangeType.MODIFIED
+          ? e.getChangeType().getCode() : null;
+      d.oldPath = e.getOldName();
+      if (e.getPatchType() == Patch.PatchType.BINARY) {
+        d.binary = true;
+      } else {
+        d.linesInserted = e.getInsertions() > 0 ? e.getInsertions() : null;
+        d.linesDeleted = e.getDeletions() > 0 ? e.getDeletions() : null;
+      }
+
+      FileInfoJson.FileInfo o = files.put(e.getNewName(), d);
+      if (o != null) {
+        // This should only happen on a delete-add break created by JGit
+        // when the file was rewritten and too little content survived. Write
+        // a single record with data from both sides.
+        d.status = Patch.ChangeType.REWRITE.getCode();
+        if (o.binary != null && o.binary) {
+          d.binary = true;
+        }
+        if (o.linesInserted != null) {
+          d.linesInserted = o.linesInserted;
+        }
+        if (o.linesDeleted != null) {
+          d.linesDeleted = o.linesDeleted;
+        }
+      }
+    }
+    return files;
+  }
+
+  static class FileInfo {
+    Character status;
+    Boolean binary;
+    String oldPath;
+    Integer linesInserted;
+    Integer linesDeleted;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileResource.java
similarity index 77%
rename from gerrit-server/src/main/java/com/google/gerrit/server/change/PatchResource.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/change/FileResource.java
index d3cf5c6..521e8c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileResource.java
@@ -20,14 +20,14 @@
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.inject.TypeLiteral;
 
-public class PatchResource implements RestResource {
-  public static final TypeLiteral<RestView<PatchResource>> PATCH_KIND =
-      new TypeLiteral<RestView<PatchResource>>() {};
+public class FileResource implements RestResource {
+  public static final TypeLiteral<RestView<FileResource>> FILE_KIND =
+      new TypeLiteral<RestView<FileResource>>() {};
 
   private final RevisionResource rev;
   private final Patch.Key key;
 
-  PatchResource(RevisionResource rev, String name) {
+  FileResource(RevisionResource rev, String name) {
     this.rev = rev;
     this.key = new Patch.Key(rev.getPatchSet().getId(), name);
   }
@@ -36,7 +36,15 @@
     return key;
   }
 
+  public boolean isCacheable() {
+    return rev.isCacheable();
+  }
+
   Account.Id getAccountId() {
     return rev.getAccountId();
   }
+
+  RevisionResource getRevision() {
+    return rev;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
new file mode 100644
index 0000000..645aa38
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -0,0 +1,92 @@
+// 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.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.CacheControl;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.change.FileInfoJson.FileInfo;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.kohsuke.args4j.Option;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+class Files implements ChildCollection<RevisionResource, FileResource> {
+  private final DynamicMap<RestView<FileResource>> views;
+  private final FileInfoJson fileInfoJson;
+  private final Provider<Revisions> revisions;
+
+  @Inject
+  Files(DynamicMap<RestView<FileResource>> views,
+      FileInfoJson fileInfoJson,
+      Provider<Revisions> revisions) {
+    this.views = views;
+    this.fileInfoJson = fileInfoJson;
+    this.revisions = revisions;
+  }
+
+  @Override
+  public DynamicMap<RestView<FileResource>> views() {
+    return views;
+  }
+
+  @Override
+  public RestView<RevisionResource> list() throws AuthException {
+    return new List();
+  }
+
+  @Override
+  public FileResource parse(RevisionResource rev, IdString id)
+      throws ResourceNotFoundException, OrmException, AuthException {
+    return new FileResource(rev, id.get());
+  }
+
+  private final class List implements RestReadView<RevisionResource> {
+    @Option(name = "--base", metaVar = "revision-id")
+    String base;
+
+    @Override
+    public Object apply(RevisionResource resource)
+        throws ResourceNotFoundException, OrmException,
+        PatchListNotAvailableException {
+      PatchSet basePatchSet = null;
+      if (base != null) {
+        RevisionResource baseResource = revisions.get().parse(
+            resource.getChangeResource(), IdString.fromDecoded(base));
+        basePatchSet = baseResource.getPatchSet();
+      }
+      Response<Map<String, FileInfo>> r = Response.ok(fileInfoJson.toFileInfoMap(
+          resource.getChange(),
+          resource.getPatchSet(),
+          basePatchSet));
+      if (resource.isCacheable()) {
+        r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+      }
+      return r;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
new file mode 100644
index 0000000..7d29449
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
@@ -0,0 +1,44 @@
+// 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.extensions.restapi.CacheControl;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.ChangeJson.CommitInfo;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import java.util.concurrent.TimeUnit;
+
+public class GetCommit implements RestReadView<RevisionResource> {
+  private final ChangeJson json;
+
+  @Inject
+  GetCommit(ChangeJson json) {
+    this.json = json;
+  }
+
+  @Override
+  public Response<CommitInfo> apply(RevisionResource resource)
+      throws OrmException, PatchSetInfoNotAvailableException {
+    Response<CommitInfo> r = Response.ok(json.toCommit(resource.getPatchSet()));
+    if (resource.isCacheable()) {
+      r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+    }
+    return r;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
new file mode 100644
index 0000000..f0d2297
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -0,0 +1,79 @@
+// 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.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class GetContent implements RestReadView<FileResource> {
+  private final GitRepositoryManager repoManager;
+
+  @Inject
+  GetContent(GitRepositoryManager repoManager) {
+    this.repoManager = repoManager;
+  }
+
+  @Override
+  public BinaryResult apply(FileResource rsrc)
+      throws ResourceNotFoundException, IOException {
+    Project.NameKey project =
+        rsrc.getRevision().getControl().getProject().getNameKey();
+    Repository repo = repoManager.openRepository(project);
+    try {
+      RevWalk rw = new RevWalk(repo);
+      try {
+        RevCommit commit =
+            rw.parseCommit(ObjectId.fromString(rsrc.getRevision().getPatchSet()
+                .getRevision().get()));
+        TreeWalk tw =
+            TreeWalk.forPath(rw.getObjectReader(), rsrc.getPatchKey().get(),
+                commit.getTree().getId());
+        if (tw == null) {
+          throw new ResourceNotFoundException();
+        }
+        try {
+          final ObjectLoader object = repo.open(tw.getObjectId(0));
+          return new BinaryResult() {
+            @Override
+            public void writeTo(OutputStream os) throws IOException {
+              object.copyTo(os);
+            }
+          }.setContentLength(object.getSize())
+           .base64();
+        } finally {
+          tw.release();
+        }
+      } finally {
+        rw.release();
+      }
+    } finally {
+      repo.close();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
index b3cd813..3d9c0fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
@@ -19,9 +19,21 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
+import org.kohsuke.args4j.Option;
+
 public class GetDetail implements RestReadView<ChangeResource> {
   private final ChangeJson json;
 
+  @Option(name = "-o", multiValued = true, usage = "Output options")
+  void addOption(ListChangesOption o) {
+    json.addOption(o);
+  }
+
+  @Option(name = "-O", usage = "Output option flags, in hex")
+  void setOptionFlagsHex(String hex) {
+    json.addOptions(ListChangesOption.fromBits(Integer.parseInt(hex, 16)));
+  }
+
   @Inject
   GetDetail(ChangeJson json) {
     this.json = json
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
new file mode 100644
index 0000000..753a70b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
@@ -0,0 +1,346 @@
+// 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 static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.common.data.PatchScript.DisplayMethod;
+import com.google.gerrit.common.data.PatchScript.FileMode;
+import com.google.gerrit.extensions.restapi.CacheControl;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.prettify.common.SparseFileContent;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.Patch.ChangeType;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.patch.PatchScriptFactory;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.git.LargeObjectException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.ReplaceEdit;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.NamedOptionDef;
+import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class GetDiff implements RestReadView<FileResource> {
+  private final PatchScriptFactory.Factory patchScriptFactoryFactory;
+  private final Provider<Revisions> revisions;
+
+  @Option(name = "--base", metaVar = "REVISION")
+  String base;
+
+  @Option(name = "--ignore-whitespace")
+  IgnoreWhitespace ignoreWhitespace = IgnoreWhitespace.NONE;
+
+  @Option(name = "--context", handler = ContextOptionHandler.class)
+  short context = AccountDiffPreference.DEFAULT_CONTEXT;
+
+  @Option(name = "--intraline")
+  boolean intraline;
+
+  @Inject
+  GetDiff(PatchScriptFactory.Factory patchScriptFactoryFactory,
+      Provider<Revisions> revisions) {
+    this.patchScriptFactoryFactory = patchScriptFactoryFactory;
+    this.revisions = revisions;
+  }
+
+  @Override
+  public Object apply(FileResource resource)
+      throws OrmException, NoSuchChangeException, LargeObjectException, ResourceNotFoundException {
+    PatchSet.Id basePatchSet = null;
+    if (base != null) {
+      RevisionResource baseResource = revisions.get().parse(
+          resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
+      basePatchSet = baseResource.getPatchSet().getId();
+    }
+    AccountDiffPreference prefs = new AccountDiffPreference(new Account.Id(0));
+    prefs.setIgnoreWhitespace(ignoreWhitespace.whitespace);
+    prefs.setContext(context);
+    prefs.setIntralineDifference(intraline);
+
+    PatchScript ps = patchScriptFactoryFactory.create(
+        resource.getRevision().getControl(),
+        resource.getPatchKey().getFileName(),
+        basePatchSet,
+        resource.getPatchKey().getParentKey(),
+        prefs)
+          .call();
+
+    Content content = new Content(ps);
+    for (Edit edit : ps.getEdits()) {
+      if (edit.getType() == Edit.Type.EMPTY) {
+        continue;
+      }
+      content.addCommon(edit.getBeginA());
+
+      checkState(content.nextA == edit.getBeginA(),
+          "nextA = %d; want %d", content.nextA, edit.getBeginA());
+      checkState(content.nextB == edit.getBeginB(),
+          "nextB = %d; want %d", content.nextB, edit.getBeginB());
+      switch (edit.getType()) {
+        case DELETE:
+        case INSERT:
+        case REPLACE:
+          List<Edit> internalEdit = edit instanceof ReplaceEdit
+            ? ((ReplaceEdit) edit).getInternalEdits()
+            : null;
+          content.addDiff(edit.getEndA(), edit.getEndB(), internalEdit);
+          break;
+        case EMPTY:
+        default:
+          throw new IllegalStateException();
+      }
+    }
+    content.addCommon(ps.getA().size());
+
+    Result result = new Result();
+    if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
+      result.metaA = new FileMeta();
+      result.metaA.name = Objects.firstNonNull(ps.getOldName(), ps.getNewName());
+      result.metaA.setContentType(ps.getFileModeA(), ps.getMimeTypeA());
+    }
+
+    if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
+      result.metaB = new FileMeta();
+      result.metaB.name = ps.getNewName();
+      result.metaB.setContentType(ps.getFileModeB(), ps.getMimeTypeB());
+    }
+
+    if (intraline) {
+      if (ps.hasIntralineTimeout()) {
+        result.intralineStatus = IntraLineStatus.TIMEOUT;
+      } else if (ps.hasIntralineFailure()) {
+        result.intralineStatus = IntraLineStatus.FAILURE;
+      } else {
+        result.intralineStatus = IntraLineStatus.OK;
+      }
+    }
+
+    result.changeType = ps.getChangeType();
+    if (ps.getPatchHeader().size() > 0) {
+      result.diffHeader = ps.getPatchHeader();
+    }
+    result.content = content.lines;
+    Response<Result> r = Response.ok(result);
+    if (resource.isCacheable()) {
+      r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+    }
+    return r;
+  }
+
+  static class Result {
+    FileMeta metaA;
+    FileMeta metaB;
+    IntraLineStatus intralineStatus;
+    ChangeType changeType;
+    List<String> diffHeader;
+    List<ContentEntry> content;
+  }
+
+  static class FileMeta {
+    String name;
+    String contentType;
+    String url;
+
+    void setContentType(FileMode fileMode, String mimeType) {
+      switch (fileMode) {
+        case FILE:
+          contentType = mimeType;
+          break;
+        case GITLINK:
+          contentType = "x-git/gitlink";
+          break;
+        case SYMLINK:
+          contentType = "x-git/symlink";
+          break;
+        default:
+          throw new IllegalStateException("file mode: " + fileMode);
+      }
+    }
+  }
+
+  enum IntraLineStatus {
+    OK,
+    TIMEOUT,
+    FAILURE;
+  }
+
+  private static class Content {
+    final List<ContentEntry> lines;
+    final SparseFileContent fileA;
+    final SparseFileContent fileB;
+
+    int nextA;
+    int nextB;
+
+    Content(PatchScript ps) {
+      lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
+      fileA = ps.getA();
+      fileB = ps.getB();
+    }
+
+    void addCommon(int end) {
+      end = Math.min(end, fileA.size());
+      if (nextA >= end) {
+        return;
+      }
+      nextB += end - nextA;
+
+      while (nextA < end) {
+        if (fileA.contains(nextA)) {
+          ContentEntry e = entry();
+          e.ab = Lists.newArrayListWithCapacity(end - nextA);
+          for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++) {
+            e.ab.add(fileA.get(i));
+          }
+        } else {
+          int endRegion = Math.min(end,
+              (nextA == 0) ? fileA.first() : fileA.next(nextA - 1));
+          ContentEntry e = entry();
+          e.skip = endRegion - nextA;
+          nextA = endRegion;
+        }
+      }
+    }
+
+    void addDiff(int endA, int endB, List<Edit> internalEdit) {
+      int lenA = endA - nextA;
+      int lenB = endB - nextB;
+      checkState(lenA > 0 || lenB > 0);
+
+      ContentEntry e = entry();
+      if (lenA > 0) {
+        e.a = Lists.newArrayListWithCapacity(lenA);
+        for (; nextA < endA; nextA++) {
+          e.a.add(fileA.get(nextA));
+        }
+      }
+      if (lenB > 0) {
+        e.b = Lists.newArrayListWithCapacity(lenB);
+        for (; nextB < endB; nextB++) {
+          e.b.add(fileB.get(nextB));
+        }
+      }
+      if (internalEdit != null && !internalEdit.isEmpty()) {
+        e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
+        e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
+        int lastA = 0;
+        int lastB = 0;
+        for (Edit edit : internalEdit) {
+          if (edit.getBeginA() != edit.getEndA()) {
+            e.editA.add(ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
+            lastA = edit.getEndA();
+          }
+          if (edit.getBeginB() != edit.getEndB()) {
+            e.editB.add(ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
+            lastB = edit.getEndB();
+          }
+        }
+      }
+    }
+
+    private ContentEntry entry() {
+      ContentEntry e = new ContentEntry();
+      lines.add(e);
+      return e;
+    }
+  }
+
+  enum IgnoreWhitespace {
+    NONE(AccountDiffPreference.Whitespace.IGNORE_NONE),
+    TRAILING(AccountDiffPreference.Whitespace.IGNORE_SPACE_AT_EOL),
+    CHANGED(AccountDiffPreference.Whitespace.IGNORE_SPACE_CHANGE),
+    ALL(AccountDiffPreference.Whitespace.IGNORE_ALL_SPACE);
+
+    private final AccountDiffPreference.Whitespace whitespace;
+
+    private IgnoreWhitespace(AccountDiffPreference.Whitespace whitespace) {
+      this.whitespace = whitespace;
+    }
+  }
+
+  static final class ContentEntry {
+    // Common lines to both sides.
+    List<String> ab;
+    // Lines of a.
+    List<String> a;
+    // Lines of b.
+    List<String> b;
+
+    // A list of changed sections of the of the corresponding line list.
+    // Each entry is a character <offset, length> pair. The offset is from the
+    // beginning of the first line in the list. Also, the offset includes an
+    // implied trailing newline character for each line.
+    List<List<Integer>> editA;
+    List<List<Integer>> editB;
+
+    // Number of lines to skip on both sides.
+    Integer skip;
+  }
+
+  public static class ContextOptionHandler extends OptionHandler<Short> {
+    public ContextOptionHandler(
+        CmdLineParser parser, OptionDef option, Setter<Short> setter) {
+      super(parser, option, setter);
+    }
+
+    @Override
+    public final int parseArguments(final Parameters params)
+        throws CmdLineException {
+      final String value = params.getParameter(0);
+      short context;
+      if ("all".equalsIgnoreCase(value)) {
+        context = AccountDiffPreference.WHOLE_FILE_CONTEXT;
+      } else {
+        try {
+          context = Short.parseShort(value, 10);
+          if (context < 0) {
+            throw new NumberFormatException();
+          }
+        } catch (NumberFormatException e) {
+          throw new CmdLineException(owner,
+              String.format("\"%s\" is not a valid value for \"%s\"",
+                  value, ((NamedOptionDef) option).name()));
+        }
+      }
+      setter.addValue(context);
+      return 1;
+    }
+
+    @Override
+    public final String getDefaultMetaVariable() {
+      return "ALL|# LINES";
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
new file mode 100644
index 0000000..a06831a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
@@ -0,0 +1,135 @@
+// 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 static com.google.common.base.Charsets.UTF_8;
+
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+
+public class GetPatch implements RestReadView<RevisionResource> {
+  private final GitRepositoryManager repoManager;
+
+  @Inject
+  GetPatch(GitRepositoryManager repoManager) {
+    this.repoManager = repoManager;
+  }
+
+  @Override
+  public BinaryResult apply(RevisionResource rsrc)
+      throws ResourceNotFoundException, ResourceConflictException {
+    Project.NameKey project = rsrc.getControl().getProject().getNameKey();
+    boolean close = true;
+    try {
+      final Repository repo = repoManager.openRepository(project);
+      try {
+        final RevWalk rw = new RevWalk(repo);
+        try {
+          final RevCommit commit =
+              rw.parseCommit(ObjectId.fromString(rsrc.getPatchSet()
+                  .getRevision().get()));
+          RevCommit[] parents = commit.getParents();
+          if (parents.length > 1) {
+            throw new ResourceConflictException(
+                "Revision has more than 1 parent.");
+          } else if (parents.length == 0) {
+            throw new ResourceConflictException("Revision has no parent.");
+          }
+          final RevCommit base = parents[0];
+          rw.parseBody(base);
+
+          BinaryResult bin = new BinaryResult() {
+            @Override
+            public void writeTo(OutputStream out) throws IOException {
+              out.write(formatEmailHeader(commit).getBytes(UTF_8));
+              DiffFormatter fmt = new DiffFormatter(out);
+              fmt.setRepository(repo);
+              fmt.format(base.getTree(), commit.getTree());
+              fmt.flush();
+            }
+
+            @Override
+            public void close() throws IOException {
+              rw.release();
+              repo.close();
+            }
+          }.setContentType("application/mbox")
+           .base64();
+          close = false;
+          return bin;
+        } finally {
+          if (close) {
+            rw.release();
+          }
+        }
+      } finally {
+        if (close) {
+          repo.close();
+        }
+      }
+    } catch (IOException e) {
+      throw new ResourceNotFoundException();
+    }
+  }
+
+  private static String formatEmailHeader(RevCommit commit) {
+    StringBuilder b = new StringBuilder();
+    PersonIdent author = commit.getAuthorIdent();
+    String subject = commit.getShortMessage();
+    String msg = commit.getFullMessage().substring(subject.length());
+    if (msg.startsWith("\n\n")) {
+      msg = msg.substring(2);
+    }
+    b.append("From ").append(commit.getName())
+     .append(' ')
+     .append("Mon Sep 17 00:00:00 2001\n")
+     .append("From: ").append(author.getName())
+     .append(" <").append(author.getEmailAddress()).append(">\n")
+     .append("Date: ").append(formatDate(author)).append('\n')
+     .append("Subject: [PATCH] ").append(subject).append('\n')
+     .append('\n')
+     .append(msg);
+    if (!msg.endsWith("\n")) {
+     b.append('\n');
+    }
+    return b.append("---\n\n").toString();
+  }
+
+  private static String formatDate(PersonIdent author) {
+    SimpleDateFormat df = new SimpleDateFormat(
+        "EEE, dd MMM yyyy HH:mm:ss Z",
+        Locale.US);
+    df.setCalendar(Calendar.getInstance(author.getTimeZone(), Locale.US));
+    return df.format(author.getWhen());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
index 47863ac..97c7694 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -25,8 +26,6 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.change.CommentInfo;
-import com.google.gerrit.server.change.CommentInfo.Side;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 690faf3..ed296f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -17,7 +17,7 @@
 import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND;
 import static com.google.gerrit.server.change.CommentResource.COMMENT_KIND;
 import static com.google.gerrit.server.change.DraftResource.DRAFT_KIND;
-import static com.google.gerrit.server.change.PatchResource.PATCH_KIND;
+import static com.google.gerrit.server.change.FileResource.FILE_KIND;
 import static com.google.gerrit.server.change.ReviewerResource.REVIEWER_KIND;
 import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
 
@@ -35,12 +35,12 @@
     bind(Reviewers.class);
     bind(Drafts.class);
     bind(Comments.class);
-    bind(Patches.class);
+    bind(Files.class);
 
     DynamicMap.mapOf(binder(), CHANGE_KIND);
     DynamicMap.mapOf(binder(), COMMENT_KIND);
     DynamicMap.mapOf(binder(), DRAFT_KIND);
-    DynamicMap.mapOf(binder(), PATCH_KIND);
+    DynamicMap.mapOf(binder(), FILE_KIND);
     DynamicMap.mapOf(binder(), REVIEWER_KIND);
     DynamicMap.mapOf(binder(), REVISION_KIND);
 
@@ -53,6 +53,7 @@
     post(CHANGE_KIND, "restore").to(Restore.class);
     post(CHANGE_KIND, "revert").to(Revert.class);
     post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
+    post(CHANGE_KIND, "rebase").to(Rebase.CurrentRevision.class);
 
     post(CHANGE_KIND, "reviewers").to(PostReviewers.class);
     child(CHANGE_KIND, "reviewers").to(Reviewers.class);
@@ -60,9 +61,13 @@
     delete(REVIEWER_KIND).to(DeleteReviewer.class);
 
     child(CHANGE_KIND, "revisions").to(Revisions.class);
+    post(REVISION_KIND, "cherrypick").to(CherryPick.class);
+    get(REVISION_KIND, "commit").to(GetCommit.class);
     get(REVISION_KIND, "review").to(GetReview.class);
     post(REVISION_KIND, "review").to(PostReview.class);
     post(REVISION_KIND, "submit").to(Submit.class);
+    post(REVISION_KIND, "rebase").to(Rebase.class);
+    get(REVISION_KIND, "patch").to(GetPatch.class);
     get(REVISION_KIND, "submit_type").to(TestSubmitType.Get.class);
     post(REVISION_KIND, "test.submit_rule").to(TestSubmitRule.class);
     post(REVISION_KIND, "test.submit_type").to(TestSubmitType.class);
@@ -76,9 +81,11 @@
     child(REVISION_KIND, "comments").to(Comments.class);
     get(COMMENT_KIND).to(GetComment.class);
 
-    child(REVISION_KIND, "files").to(Patches.class);
-    put(PATCH_KIND, "reviewed").to(PutReviewed.class);
-    delete(PATCH_KIND, "reviewed").to(DeleteReviewed.class);
+    child(REVISION_KIND, "files").to(Files.class);
+    put(FILE_KIND, "reviewed").to(PutReviewed.class);
+    delete(FILE_KIND, "reviewed").to(DeleteReviewed.class);
+    get(FILE_KIND, "content").to(GetContent.class);
+    get(FILE_KIND, "diff").to(GetDiff.class);
 
     install(new FactoryModule() {
       @Override
@@ -86,6 +93,8 @@
         factory(ReviewerResource.Factory.class);
         factory(AccountInfo.Loader.Factory.class);
         factory(EmailReviewComments.Factory.class);
+        factory(ChangeInserter.Factory.class);
+        factory(PatchSetInserter.Factory.class);
       }
     });
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
new file mode 100644
index 0000000..f16047a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -0,0 +1,336 @@
+// 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 static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.ChangeHooks;
+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.client.RevId;
+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.config.TrackingFooters;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.ssh.NoSshInfo;
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+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.eclipse.jgit.transport.ReceiveCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class PatchSetInserter {
+  private static final Logger log =
+      LoggerFactory.getLogger(PatchSetInserter.class);
+
+  public static interface Factory {
+    PatchSetInserter create(Repository git, RevWalk revWalk, RefControl refControl,
+        Change change, RevCommit commit);
+  }
+
+  private final ChangeHooks hooks;
+  private final TrackingFooters trackingFooters;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final ReviewDb db;
+  private final IdentifiedUser user;
+  private final GitReferenceUpdated gitRefUpdated;
+  private final CommitValidators.Factory commitValidatorsFactory;
+  private final ChangeIndexer indexer;
+  private boolean validateForReceiveCommits;
+  private final ReplacePatchSetSender.Factory replacePatchSetFactory;
+
+  private final Repository git;
+  private final RevWalk revWalk;
+  private final RevCommit commit;
+  private final Change change;
+  private final RefControl refControl;
+
+  private PatchSet patchSet;
+  private ChangeMessage changeMessage;
+  private boolean copyLabels;
+  private SshInfo sshInfo;
+  private boolean draft;
+  private boolean runHooks;
+  private boolean sendMail;
+
+  @Inject
+  public PatchSetInserter(ChangeHooks hooks,
+      TrackingFooters trackingFooters,
+      ReviewDb db,
+      PatchSetInfoFactory patchSetInfoFactory,
+      IdentifiedUser user,
+      GitReferenceUpdated gitRefUpdated,
+      CommitValidators.Factory commitValidatorsFactory,
+      ChangeIndexer indexer,
+      ReplacePatchSetSender.Factory replacePatchSetFactory,
+      @Assisted Repository git,
+      @Assisted RevWalk revWalk,
+      @Assisted RefControl refControl,
+      @Assisted Change change,
+      @Assisted RevCommit commit) {
+    this.hooks = hooks;
+    this.trackingFooters = trackingFooters;
+    this.db = db;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+    this.user = user;
+    this.gitRefUpdated = gitRefUpdated;
+    this.commitValidatorsFactory = commitValidatorsFactory;
+    this.indexer = indexer;
+    this.replacePatchSetFactory = replacePatchSetFactory;
+
+    this.git = git;
+    this.revWalk = revWalk;
+    this.refControl = refControl;
+    this.change = change;
+    this.commit = commit;
+    this.runHooks = true;
+    this.sendMail = true;
+  }
+
+  public PatchSetInserter setPatchSet(PatchSet patchSet) {
+    PatchSet.Id psid = patchSet.getId();
+    checkArgument(psid.getParentKey().equals(change.getId()),
+        "patch set %s not for change %s", psid, change.getId());
+    checkArgument(psid.get() > change.currentPatchSetId().get(),
+        "new patch set ID %s is not greater than current patch set ID %s",
+        psid.get(), change.currentPatchSetId().get());
+    this.patchSet = patchSet;
+    return this;
+  }
+
+  public PatchSetInserter setMessage(String message) throws OrmException {
+    changeMessage = new ChangeMessage(new ChangeMessage.Key(change.getId(),
+        ChangeUtil.messageUUID(db)), user.getAccountId(), patchSet.getId());
+    changeMessage.setMessage(message);
+    return this;
+  }
+
+  public PatchSetInserter setMessage(ChangeMessage changeMessage) throws OrmException {
+    this.changeMessage = changeMessage;
+    return this;
+  }
+
+  public PatchSetInserter setCopyLabels(boolean copyLabels) {
+    this.copyLabels = copyLabels;
+    return this;
+  }
+
+  public PatchSetInserter setSshInfo(SshInfo sshInfo) {
+    this.sshInfo = sshInfo;
+    return this;
+  }
+
+  public PatchSetInserter setValidateForReceiveCommits(boolean validate) {
+    this.validateForReceiveCommits = validate;
+    return this;
+  }
+
+  public PatchSetInserter setDraft(boolean draft) {
+    this.draft = draft;
+    return this;
+  }
+
+  public PatchSetInserter setRunHooks(boolean runHooks) {
+    this.runHooks = runHooks;
+    return this;
+  }
+
+  public PatchSetInserter setSendMail(boolean sendMail) {
+    this.sendMail = sendMail;
+    return this;
+  }
+
+  public Change insert() throws InvalidChangeOperationException, OrmException,
+      IOException {
+    init();
+    validate();
+
+    Change updatedChange;
+    RefUpdate ru = git.updateRef(patchSet.getRefName());
+    ru.setExpectedOldObjectId(ObjectId.zeroId());
+    ru.setNewObjectId(commit);
+    ru.disableRefLog();
+    if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+      throw new IOException(String.format(
+          "Failed to create ref %s in %s: %s", patchSet.getRefName(),
+          change.getDest().getParentKey().get(), ru.getResult()));
+    }
+    gitRefUpdated.fire(change.getProject(), ru);
+
+    final PatchSet.Id currentPatchSetId = change.currentPatchSetId();
+
+    db.changes().beginTransaction(change.getId());
+    try {
+      if (!db.changes().get(change.getId()).getStatus().isOpen()) {
+        throw new InvalidChangeOperationException(String.format(
+            "Change %s is closed", change.getId()));
+      }
+
+      ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
+      db.patchSets().insert(Collections.singleton(patchSet));
+
+      final List<PatchSetApproval> oldPatchSetApprovals =
+          db.patchSetApprovals().byChange(change.getId()).toList();
+      final Set<Account.Id> oldReviewers = Sets.newHashSet();
+      final Set<Account.Id> oldCC = Sets.newHashSet();
+      for (PatchSetApproval a : oldPatchSetApprovals) {
+        if (a.getValue() != 0) {
+          oldReviewers.add(a.getAccountId());
+        } else {
+          oldCC.add(a.getAccountId());
+        }
+      }
+
+      updatedChange =
+          db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+            @Override
+            public Change update(Change change) {
+              if (change.getStatus().isClosed()) {
+                return null;
+              }
+              if (!change.currentPatchSetId().equals(currentPatchSetId)) {
+                return null;
+              }
+              if (change.getStatus() != Change.Status.DRAFT) {
+                change.setStatus(Change.Status.NEW);
+              }
+              change.setLastSha1MergeTested(null);
+              change.setCurrentPatchSet(patchSetInfoFactory.get(commit,
+                  patchSet.getId()));
+              ChangeUtil.updated(change);
+              return change;
+            }
+          });
+      if (updatedChange == null) {
+        throw new ChangeModifiedException(String.format(
+            "Change %s was modified", change.getId()));
+      }
+
+      if (copyLabels) {
+        ApprovalsUtil.copyLabels(db, refControl.getProjectControl()
+            .getLabelTypes(), currentPatchSetId, updatedChange.currentPatchSetId());
+      }
+
+      final List<FooterLine> footerLines = commit.getFooterLines();
+      ChangeUtil.updateTrackingIds(db, updatedChange, trackingFooters, footerLines);
+      db.commit();
+
+      if (changeMessage != null) {
+        db.changeMessages().insert(Collections.singleton(changeMessage));
+      }
+
+      if (sendMail) {
+        try {
+          PatchSetInfo info = patchSetInfoFactory.get(commit, patchSet.getId());
+          ReplacePatchSetSender cm =
+              replacePatchSetFactory.create(updatedChange);
+          cm.setFrom(user.getAccountId());
+          cm.setPatchSet(patchSet, info);
+          cm.setChangeMessage(changeMessage);
+          cm.addReviewers(oldReviewers);
+          cm.addExtraCC(oldCC);
+          cm.send();
+        } catch (Exception err) {
+          log.error("Cannot send email for new patch set on change " + updatedChange.getId(),
+              err);
+        }
+      }
+
+      indexer.index(updatedChange);
+      if (runHooks) {
+        hooks.doPatchsetCreatedHook(updatedChange, patchSet, db);
+      }
+    } finally {
+      db.rollback();
+    }
+    return updatedChange;
+  }
+
+  private void init() {
+    if (sshInfo == null) {
+      sshInfo = new NoSshInfo();
+    }
+    if (patchSet == null) {
+      patchSet = new PatchSet(
+          ChangeUtil.nextPatchSetId(git, change.currentPatchSetId()));
+      patchSet.setCreatedOn(new Timestamp(System.currentTimeMillis()));
+      patchSet.setUploader(change.getOwner());
+      patchSet.setRevision(new RevId(commit.name()));
+    }
+    patchSet.setDraft(draft);
+  }
+
+  private void validate() throws InvalidChangeOperationException {
+    CommitValidators cv = commitValidatorsFactory.create(refControl, sshInfo, git);
+
+    String refName = patchSet.getRefName();
+    CommitReceivedEvent event = new CommitReceivedEvent(
+        new ReceiveCommand(
+            ObjectId.zeroId(),
+            commit.getId(),
+            refName.substring(0, refName.lastIndexOf('/') + 1) + "new"),
+        refControl.getProjectControl().getProject(), refControl.getRefName(),
+        commit, user);
+
+    try {
+      if (validateForReceiveCommits) {
+        cv.validateForReceiveCommits(event);
+      } else {
+        cv.validateForGerritCommits(event);
+      }
+    } catch (CommitValidationException e) {
+      throw new InvalidChangeOperationException(e.getMessage());
+    }
+  }
+
+  public class ChangeModifiedException extends InvalidChangeOperationException {
+    private static final long serialVersionUID = 1L;
+
+    public ChangeModifiedException(String msg) {
+      super(msg);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Patches.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Patches.java
deleted file mode 100644
index cb0a9bf..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Patches.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.ChildCollection;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-
-class Patches implements ChildCollection<RevisionResource, PatchResource> {
-  private final DynamicMap<RestView<PatchResource>> views;
-
-  @Inject
-  Patches(DynamicMap<RestView<PatchResource>> views) {
-    this.views = views;
-  }
-
-  @Override
-  public DynamicMap<RestView<PatchResource>> views() {
-    return views;
-  }
-
-  @Override
-  public RestView<RevisionResource> list() throws AuthException {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public PatchResource parse(RevisionResource rev, IdString id)
-      throws ResourceNotFoundException, OrmException, AuthException {
-    return new PatchResource(rev, id.get());
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 9c14bcb..772ce12 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -20,7 +20,9 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.ListenableFuture;
 import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.Permission;
@@ -29,6 +31,7 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -38,7 +41,9 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountsCollection;
 import com.google.gerrit.server.change.PostReview.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -51,6 +56,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
 
 public class PostReview implements RestModifyView<RevisionResource, Input> {
   private static final Logger log = LoggerFactory.getLogger(PostReview.class);
@@ -80,6 +86,20 @@
 
     /** Who to send email notifications to after review is stored. */
     public NotifyHandling notify = NotifyHandling.ALL;
+
+    /**
+     * Account ID, name, email address or username of another user. The review
+     * will be posted/updated on behalf of this named user instead of the
+     * caller. Caller must have the labelAs-$NAME permission granted for each
+     * label that appears in {@link #labels}. This is in addition to the named
+     * user also needing to have permission to use the labels.
+     * <p>
+     * {@link #strictLabels} impacts how labels is processed for the named user,
+     * not the caller.
+     */
+    public String onBehalfOf;
+
+    public boolean waitForCommit;
   }
 
   public static enum DraftHandling {
@@ -92,7 +112,7 @@
 
   static class Comment {
     String id;
-    CommentInfo.Side side;
+    Side side;
     int line;
     String inReplyTo;
     String message;
@@ -103,6 +123,8 @@
   }
 
   private final ReviewDb db;
+  private final ChangeIndexer indexer;
+  private final AccountsCollection accounts;
   private final EmailReviewComments.Factory email;
   @Deprecated private final ChangeHooks hooks;
 
@@ -115,16 +137,24 @@
 
   @Inject
   PostReview(ReviewDb db,
+      ChangeIndexer indexer,
+      AccountsCollection accounts,
       EmailReviewComments.Factory email,
       ChangeHooks hooks) {
     this.db = db;
+    this.indexer = indexer;
+    this.accounts = accounts;
     this.email = email;
     this.hooks = hooks;
   }
 
   @Override
   public Object apply(RevisionResource revision, Input input)
-      throws AuthException, BadRequestException, OrmException {
+      throws AuthException, BadRequestException, OrmException,
+      UnprocessableEntityException, InterruptedException, ExecutionException {
+    if (input.onBehalfOf != null) {
+      revision = onBehalfOf(revision, input);
+    }
     if (input.labels != null) {
       checkLabels(revision, input.strictLabels, input.labels);
     }
@@ -149,6 +179,11 @@
       if (dirty) {
         db.changes().update(Collections.singleton(change));
         db.commit();
+
+        ListenableFuture<?> indexWrite = indexer.index(change);
+        if (input.waitForCommit) {
+          indexWrite.get();
+        }
       }
     } finally {
       db.rollback();
@@ -170,6 +205,45 @@
     return output;
   }
 
+  private RevisionResource onBehalfOf(RevisionResource rev, Input in)
+      throws BadRequestException, AuthException, UnprocessableEntityException,
+      OrmException {
+    if (in.labels == null || in.labels.isEmpty()) {
+      throw new AuthException(String.format(
+          "label required to post review on behalf of \"%s\"",
+          in.onBehalfOf));
+    }
+
+    ChangeControl caller = rev.getControl();
+    Iterator<Map.Entry<String, Short>> itr = in.labels.entrySet().iterator();
+    while (itr.hasNext()) {
+      Map.Entry<String, Short> ent = itr.next();
+      LabelType type = caller.getLabelTypes().byLabel(ent.getKey());
+      if (type == null && in.strictLabels) {
+        throw new BadRequestException(String.format(
+            "label \"%s\" is not a configured label", ent.getKey()));
+      } else if (type == null) {
+        itr.remove();
+        continue;
+      }
+
+      PermissionRange r = caller.getRange(Permission.forLabelAs(type.getName()));
+      if (r == null || r.isEmpty() || !r.contains(ent.getValue())) {
+        throw new AuthException(String.format(
+            "not permitted to modify label \"%s\" on behalf of \"%s\"",
+            ent.getKey(), in.onBehalfOf));
+      }
+    }
+    if (in.labels.isEmpty()) {
+      throw new AuthException(String.format(
+          "label required to post review on behalf of \"%s\"",
+          in.onBehalfOf));
+    }
+
+    ChangeControl target = caller.forUser(accounts.parse(in.onBehalfOf));
+    return new RevisionResource(new ChangeResource(target), rev.getPatchSet());
+  }
+
   private void checkLabels(RevisionResource revision, boolean strict,
       Map<String, Short> labels) throws BadRequestException, AuthException {
     ChangeControl ctl = revision.getControl();
@@ -289,7 +363,7 @@
         }
         e.setStatus(PatchLineComment.Status.PUBLISHED);
         e.setWrittenOn(timestamp);
-        e.setSide(c.side == CommentInfo.Side.PARENT ? (short) 0 : (short) 1);
+        e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
         e.setMessage(c.message);
         (create ? ins : upd).add(e);
       }
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 70cf259..59458e4 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
@@ -56,6 +56,7 @@
 
 import org.eclipse.jgit.lib.Config;
 
+import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.List;
 import java.util.Set;
@@ -120,7 +121,7 @@
   @Override
   public PostResult apply(ChangeResource rsrc, Input input)
       throws BadRequestException, ResourceNotFoundException, AuthException,
-      UnprocessableEntityException, OrmException, EmailException {
+      UnprocessableEntityException, OrmException, EmailException, IOException {
     if (input.reviewer == null) {
       throw new BadRequestException("missing reviewer field");
     }
@@ -147,8 +148,8 @@
   }
 
   private PostResult putGroup(ChangeResource rsrc, Input input)
-      throws ResourceNotFoundException, AuthException, BadRequestException,
-      UnprocessableEntityException, OrmException, EmailException {
+      throws BadRequestException,
+      UnprocessableEntityException, OrmException, EmailException, IOException {
     GroupDescription.Basic group = groupsCollection.get().parseInternal(input.reviewer);
     PostResult result = new PostResult();
     if (!isLegalReviewerGroup(group.getGroupUUID())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
index befb8d7..c6fc4fc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.change;
 
+import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
@@ -23,7 +24,6 @@
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.CommentInfo.Side;
 import com.google.gerrit.server.change.PutDraft.Input;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -90,7 +90,7 @@
 
   private PatchLineComment update(PatchLineComment e, Input in) {
     if (in.side != null) {
-      e.setSide(in.side == CommentInfo.Side.PARENT ? (short) 0 : (short) 1);
+      e.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
     }
     if (in.line != null) {
       e.setLine(in.line);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index b96b480..108756e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -15,18 +15,21 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
-import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.PutTopic.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.inject.Inject;
@@ -34,8 +37,11 @@
 
 import java.util.Collections;
 
-class PutTopic implements RestModifyView<ChangeResource, Input> {
+class PutTopic implements RestModifyView<ChangeResource, Input>,
+    UiAction<ChangeResource> {
   private final Provider<ReviewDb> dbProvider;
+  private final ChangeIndexer indexer;
+  private final ChangeHooks hooks;
 
   static class Input {
     @DefaultInput
@@ -44,8 +50,11 @@
   }
 
   @Inject
-  PutTopic(Provider<ReviewDb> dbProvider) {
+  PutTopic(Provider<ReviewDb> dbProvider, ChangeIndexer indexer,
+      ChangeHooks hooks) {
     this.dbProvider = dbProvider;
+    this.indexer = indexer;
+    this.hooks = hooks;
   }
 
   @Override
@@ -77,9 +86,10 @@
             oldTopicName, newTopicName);
       }
 
+      IdentifiedUser currentUser = ((IdentifiedUser) control.getCurrentUser());
       ChangeMessage cmsg = new ChangeMessage(
           new ChangeMessage.Key(change.getId(), ChangeUtil.messageUUID(db)),
-          ((IdentifiedUser) control.getCurrentUser()).getAccountId(),
+          currentUser.getAccountId(),
           change.currentPatchSetId());
       StringBuilder msgBuf = new StringBuilder(summary);
       if (!Strings.isNullOrEmpty(input.message)) {
@@ -88,7 +98,7 @@
       }
       cmsg.setMessage(msgBuf.toString());
 
-      db.changes().atomicUpdate(change.getId(),
+      change = db.changes().atomicUpdate(change.getId(),
         new AtomicUpdate<Change>() {
           @Override
           public Change update(Change change) {
@@ -97,9 +107,19 @@
           }
         });
       db.changeMessages().insert(Collections.singleton(cmsg));
+      indexer.index(change);
+      hooks.doTopicChangedHook(change, currentUser.getAccount(),
+          oldTopicName, db);
     }
     return Strings.isNullOrEmpty(newTopicName)
         ? Response.none()
         : newTopicName;
   }
+
+  @Override
+  public UiAction.Description getDescription(ChangeResource resource) {
+    return new UiAction.Description()
+      .setLabel("Edit Topic")
+      .setVisible(resource.getControl().canEditTopicName());
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
new file mode 100644
index 0000000..3831c20
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -0,0 +1,117 @@
+// 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.changes.ListChangesOption;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+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.ChangeJson.ChangeInfo;
+import com.google.gerrit.server.change.Rebase.Input;
+import com.google.gerrit.server.changedetail.RebaseChange;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class Rebase implements RestModifyView<RevisionResource, Input>,
+    UiAction<RevisionResource> {
+  public static class Input {
+  }
+
+  private final Provider<RebaseChange> rebaseChange;
+  private final ChangeJson json;
+
+  @Inject
+  public Rebase(Provider<RebaseChange> rebaseChange, ChangeJson json) {
+    this.rebaseChange = rebaseChange;
+    this.json = json;
+  }
+
+  @Override
+  public ChangeInfo apply(RevisionResource rsrc, Input input)
+      throws AuthException, ResourceNotFoundException,
+      ResourceConflictException, EmailException, OrmException {
+    ChangeControl control = rsrc.getControl();
+    Change change = rsrc.getChange();
+    if (!control.canRebase()) {
+      throw new AuthException("rebase not permitted");
+    } else if (!change.getStatus().isOpen()) {
+      throw new ResourceConflictException("change is "
+          + change.getStatus().name().toLowerCase());
+    }
+
+    try {
+      rebaseChange.get().rebase(rsrc.getPatchSet().getId(), rsrc.getUser());
+    } catch (InvalidChangeOperationException e) {
+      throw new ResourceConflictException(e.getMessage());
+    } catch (IOException e) {
+      throw new ResourceConflictException(e.getMessage());
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(change.getId().toString());
+    }
+
+    json.addOption(ListChangesOption.CURRENT_REVISION)
+        .addOption(ListChangesOption.CURRENT_COMMIT);
+    return json.format(change.getId());
+  }
+
+  @Override
+  public UiAction.Description getDescription(RevisionResource resource) {
+    return new UiAction.Description()
+      .setLabel("Rebase")
+      .setTitle("Rebase onto tip of branch or parent change")
+      .setVisible(resource.getChange().getStatus().isOpen()
+          && resource.getControl().canRebase()
+          && rebaseChange.get().canRebase(resource));
+  }
+
+  public static class CurrentRevision implements
+      RestModifyView<ChangeResource, Input> {
+    private final Provider<ReviewDb> dbProvider;
+    private final Rebase rebase;
+
+    @Inject
+    CurrentRevision(Provider<ReviewDb> dbProvider, Rebase rebase) {
+      this.dbProvider = dbProvider;
+      this.rebase = rebase;
+    }
+
+    @Override
+    public ChangeInfo apply(ChangeResource rsrc, Input input)
+        throws AuthException, ResourceNotFoundException,
+        ResourceConflictException, EmailException, OrmException {
+      PatchSet ps =
+          dbProvider.get().patchSets()
+              .get(rsrc.getChange().currentPatchSetId());
+      if (ps == null) {
+        throw new ResourceConflictException("current revision is missing");
+      } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
+        throw new AuthException("current revision not accessible");
+      }
+      return rebase.apply(new RevisionResource(rsrc, ps), input);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index afb58f9..581c4ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -28,6 +29,7 @@
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.Restore.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.ReplyToChangeSender;
 import com.google.gerrit.server.mail.RestoredSender;
 import com.google.gerrit.server.project.ChangeControl;
@@ -41,13 +43,15 @@
 
 import java.util.Collections;
 
-public class Restore implements RestModifyView<ChangeResource, Input> {
+public class Restore implements RestModifyView<ChangeResource, Input>,
+    UiAction<ChangeResource> {
   private static final Logger log = LoggerFactory.getLogger(Restore.class);
 
   private final ChangeHooks hooks;
   private final RestoredSender.Factory restoredSenderFactory;
   private final Provider<ReviewDb> dbProvider;
   private final ChangeJson json;
+  private final ChangeIndexer indexer;
 
   public static class Input {
     @DefaultInput
@@ -58,11 +62,13 @@
   Restore(ChangeHooks hooks,
       RestoredSender.Factory restoredSenderFactory,
       Provider<ReviewDb> dbProvider,
-      ChangeJson json) {
+      ChangeJson json,
+      ChangeIndexer indexer) {
     this.hooks = hooks;
     this.restoredSenderFactory = restoredSenderFactory;
     this.dbProvider = dbProvider;
     this.json = json;
+    this.indexer = indexer;
   }
 
   @Override
@@ -86,8 +92,8 @@
         new AtomicUpdate<Change>() {
           @Override
           public Change update(Change change) {
-            if (change.getStatus() == Change.Status.ABANDONED) {
-              change.setStatus(Change.Status.NEW);
+            if (change.getStatus() == Status.ABANDONED) {
+              change.setStatus(Status.NEW);
               ChangeUtil.updated(change);
               return change;
             }
@@ -98,6 +104,7 @@
         throw new ResourceConflictException("change is "
             + status(db.changes().get(req.getChange().getId())));
       }
+      indexer.index(change);
       message = newMessage(input, caller, change);
       db.changeMessages().insert(Collections.singleton(message));
       new ApprovalsUtil(db).syncChangeStatus(change);
@@ -116,11 +123,20 @@
     }
     hooks.doChangeRestoredHook(change,
         caller.getAccount(),
+        db.patchSets().get(change.currentPatchSetId()),
         Strings.emptyToNull(input.message),
         dbProvider.get());
     return json.format(change);
   }
 
+  @Override
+  public UiAction.Description getDescription(ChangeResource resource) {
+    return new UiAction.Description()
+      .setLabel("Restore")
+      .setVisible(resource.getChange().getStatus() == Status.ABANDONED
+          && resource.getControl().canRestore());
+  }
+
   private ChangeMessage newMessage(Input input, IdentifiedUser caller,
       Change change) throws OrmException {
     StringBuilder msg = new StringBuilder();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index 154bd64..1d4ad71 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -40,7 +41,8 @@
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 
-public class Revert implements RestModifyView<ChangeResource, Input> {
+public class Revert implements RestModifyView<ChangeResource, Input>,
+    UiAction<ChangeResource> {
   private final ChangeHooks hooks;
   private final RevertedSender.Factory revertedSenderFactory;
   private final CommitValidators.Factory commitValidatorsFactory;
@@ -49,7 +51,7 @@
   private final GitRepositoryManager gitManager;
   private final PersonIdent myIdent;
   private final PatchSetInfoFactory patchSetInfoFactory;
-  private final ChangeInserter changeInserter;
+  private final ChangeInserter.Factory changeInserterFactory;
 
   public static class Input {
     public String message;
@@ -64,7 +66,7 @@
       GitRepositoryManager gitManager,
       final PatchSetInfoFactory patchSetInfoFactory,
       @GerritPersonIdent final PersonIdent myIdent,
-      final ChangeInserter changeInserter) {
+      final ChangeInserter.Factory changeInserterFactory) {
     this.hooks = hooks;
     this.revertedSenderFactory = revertedSenderFactory;
     this.commitValidatorsFactory = commitValidatorsFactory;
@@ -72,7 +74,7 @@
     this.json = json;
     this.gitManager = gitManager;
     this.myIdent = myIdent;
-    this.changeInserter = changeInserter;
+    this.changeInserterFactory = changeInserterFactory;
     this.patchSetInfoFactory = patchSetInfoFactory;
   }
 
@@ -97,7 +99,7 @@
               commitValidators,
               Strings.emptyToNull(input.message), dbProvider.get(),
               revertedSenderFactory, hooks, git, patchSetInfoFactory,
-              myIdent, changeInserter);
+              myIdent, changeInserterFactory);
 
       return json.format(revertedChangeId);
     } catch (InvalidChangeOperationException e) {
@@ -107,6 +109,14 @@
     }
   }
 
+  @Override
+  public UiAction.Description getDescription(ChangeResource resource) {
+    return new UiAction.Description()
+      .setLabel("Revert")
+      .setVisible(resource.getChange().getStatus() == Status.MERGED
+          && resource.getControl().getRefControl().canUpload());
+  }
+
   private static String status(Change change) {
     return change != null ? change.getStatus().name().toLowerCase() : "deleted";
    }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
index 0898bce..42d81b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
@@ -28,7 +28,7 @@
   static class Input {
   }
 
-  static class PutReviewed implements RestModifyView<PatchResource, Input> {
+  static class PutReviewed implements RestModifyView<FileResource, Input> {
     private final Provider<ReviewDb> dbProvider;
 
     @Inject
@@ -37,7 +37,7 @@
     }
 
     @Override
-    public Object apply(PatchResource resource, Input input)
+    public Object apply(FileResource resource, Input input)
         throws OrmException {
       ReviewDb db = dbProvider.get();
       AccountPatchReview apr = getExisting(db, resource);
@@ -52,7 +52,7 @@
     }
   }
 
-  static class DeleteReviewed implements RestModifyView<PatchResource, Input> {
+  static class DeleteReviewed implements RestModifyView<FileResource, Input> {
     private final Provider<ReviewDb> dbProvider;
 
     @Inject
@@ -61,7 +61,7 @@
     }
 
     @Override
-    public Object apply(PatchResource resource, Input input)
+    public Object apply(FileResource resource, Input input)
         throws OrmException {
       ReviewDb db = dbProvider.get();
       AccountPatchReview apr = getExisting(db, resource);
@@ -73,7 +73,7 @@
   }
 
   private static AccountPatchReview getExisting(ReviewDb db,
-      PatchResource resource) throws OrmException {
+      FileResource resource) throws OrmException {
     AccountPatchReview.Key key = new AccountPatchReview.Key(
         resource.getPatchKey(), resource.getAccountId());
     return db.accountPatchReviews().get(key);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
index cdd9e0f..f891729 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
@@ -29,14 +29,23 @@
 
   private final ChangeResource change;
   private final PatchSet ps;
+  private boolean cacheable = true;
 
   public RevisionResource(ChangeResource change, PatchSet ps) {
     this.change = change;
     this.ps = ps;
   }
 
+  public boolean isCacheable() {
+    return cacheable;
+  }
+
+  public ChangeResource getChangeResource() {
+    return change;
+  }
+
   public ChangeControl getControl() {
-    return change.getControl();
+    return getChangeResource().getControl();
   }
 
   public Change getChange() {
@@ -48,6 +57,15 @@
   }
 
   Account.Id getAccountId() {
-    return ((IdentifiedUser) getControl().getCurrentUser()).getAccountId();
+    return getUser().getAccountId();
+  }
+
+  IdentifiedUser getUser() {
+    return (IdentifiedUser) getControl().getCurrentUser();
+  }
+
+  RevisionResource doNotCache() {
+    cacheable = false;
+    return this;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
index 68ef63c..19c6d3a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
@@ -59,7 +59,7 @@
       PatchSet.Id p = change.getChange().currentPatchSetId();
       PatchSet ps = p != null ? dbProvider.get().patchSets().get(p) : null;
       if (ps != null && visible(change, ps)) {
-        return new RevisionResource(change, ps);
+        return new RevisionResource(change, ps).doNotCache();
       }
       throw new ResourceNotFoundException(id);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 2b51b0a..0db5208 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -36,6 +37,7 @@
 import com.google.gerrit.server.change.Submit.Input;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeQueue;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
@@ -50,7 +52,8 @@
 import java.util.Collections;
 import java.util.List;
 
-public class Submit implements RestModifyView<RevisionResource, Input> {
+public class Submit implements RestModifyView<RevisionResource, Input>,
+    UiAction<RevisionResource> {
   public static class Input {
     public boolean waitForMerge;
   }
@@ -72,14 +75,17 @@
   private final Provider<ReviewDb> dbProvider;
   private final GitRepositoryManager repoManager;
   private final MergeQueue mergeQueue;
+  private final ChangeIndexer indexer;
 
   @Inject
   Submit(Provider<ReviewDb> dbProvider,
       GitRepositoryManager repoManager,
-      MergeQueue mergeQueue) {
+      MergeQueue mergeQueue,
+      ChangeIndexer indexer) {
     this.dbProvider = dbProvider;
     this.repoManager = repoManager;
     this.mergeQueue = mergeQueue;
+    this.indexer = indexer;
   }
 
   @Override
@@ -136,6 +142,18 @@
     }
   }
 
+  @Override
+  public UiAction.Description getDescription(RevisionResource resource) {
+    PatchSet.Id current = resource.getChange().currentPatchSetId();
+    return new UiAction.Description()
+      .setLabel(String.format(
+          "Submit Revision %d",
+          resource.getPatchSet().getPatchSetId()))
+      .setVisible(resource.getChange().getStatus().isOpen()
+          && resource.getPatchSet().getId().equals(current)
+          && resource.getControl().canSubmit());
+  }
+
   /**
    * If the merge was attempted and it failed the system usually writes a
    * comment as a ChangeMessage and sets status to NEW. Find the relevant
@@ -187,6 +205,7 @@
     } finally {
       db.rollback();
     }
+    indexer.index(change);
     return change;
   }
 
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 22eae2d..2fbc79f 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
@@ -33,6 +33,7 @@
 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.CreateChangeSender;
 import com.google.gerrit.server.mail.MailUtil.MailRecipients;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
@@ -75,6 +76,7 @@
   private final AccountResolver accountResolver;
   private final CreateChangeSender.Factory createChangeSenderFactory;
   private final ReplacePatchSetSender.Factory replacePatchSetFactory;
+  private final ChangeIndexer indexer;
 
   private final PatchSet.Id patchSetId;
 
@@ -87,6 +89,7 @@
       final AccountResolver accountResolver,
       final CreateChangeSender.Factory createChangeSenderFactory,
       final ReplacePatchSetSender.Factory replacePatchSetFactory,
+      final ChangeIndexer indexer,
       @Assisted final PatchSet.Id patchSetId) {
     this.changeControlFactory = changeControlFactory;
     this.db = db;
@@ -97,6 +100,7 @@
     this.accountResolver = accountResolver;
     this.createChangeSenderFactory = createChangeSenderFactory;
     this.replacePatchSetFactory = replacePatchSetFactory;
+    this.indexer = indexer;
 
     this.patchSetId = patchSetId;
   }
@@ -146,6 +150,7 @@
       });
 
       if (!updatedPatchSet.isDraft() || updatedChange.getStatus() == Change.Status.NEW) {
+        indexer.index(updatedChange);
         hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, db);
 
         sendNotifications(control.getChange().getStatus() == Change.Status.DRAFT,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index d7bf5a3..1cd8e9c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -14,34 +14,25 @@
 
 package com.google.gerrit.server.changedetail;
 
-import com.google.common.collect.Sets;
-import com.google.gerrit.common.ChangeHookRunner;
 import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 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.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.mail.RebasedPatchSetSender;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
@@ -51,50 +42,35 @@
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.merge.ThreeWayMerger;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 
 import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 
 public class RebaseChange {
-  private final ChangeControl.Factory changeControlFactory;
-  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final ChangeControl.GenericFactory changeControlFactory;
   private final ReviewDb db;
   private final GitRepositoryManager gitManager;
   private final PersonIdent myIdent;
-  private final GitReferenceUpdated gitRefUpdated;
-  private final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory;
-  private final ChangeHookRunner hooks;
   private final MergeUtil.Factory mergeUtilFactory;
-  private final ProjectCache projectCache;
+  private final PatchSetInserter.Factory patchSetInserterFactory;
 
   @Inject
-  RebaseChange(final ChangeControl.Factory changeControlFactory,
-      final PatchSetInfoFactory patchSetInfoFactory, final ReviewDb db,
+  RebaseChange(final ChangeControl.GenericFactory changeControlFactory,
+      final ReviewDb db,
       @GerritPersonIdent final PersonIdent myIdent,
       final GitRepositoryManager gitManager,
-      final GitReferenceUpdated gitRefUpdated,
-      final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
-      final ChangeHookRunner hooks,
       final MergeUtil.Factory mergeUtilFactory,
-      final ProjectCache projectCache) {
+      final PatchSetInserter.Factory patchSetInserterFactory) {
     this.changeControlFactory = changeControlFactory;
-    this.patchSetInfoFactory = patchSetInfoFactory;
     this.db = db;
     this.gitManager = gitManager;
     this.myIdent = myIdent;
-    this.gitRefUpdated = gitRefUpdated;
-    this.rebasedPatchSetSenderFactory = rebasedPatchSetSenderFactory;
-    this.hooks = hooks;
     this.mergeUtilFactory = mergeUtilFactory;
-    this.projectCache = projectCache;
+    this.patchSetInserterFactory = patchSetInserterFactory;
   }
 
   /**
@@ -123,12 +99,12 @@
    * @throws IOException thrown if rebase is not possible or not needed
    * @throws InvalidChangeOperationException thrown if rebase is not allowed
    */
-  public void rebase(final PatchSet.Id patchSetId, final Account.Id uploader)
+  public void rebase(final PatchSet.Id patchSetId, final IdentifiedUser uploader)
       throws NoSuchChangeException, EmailException, OrmException, IOException,
       InvalidChangeOperationException {
     final Change.Id changeId = patchSetId.getParentKey();
     final ChangeControl changeControl =
-        changeControlFactory.validateFor(changeId);
+        changeControlFactory.validateFor(changeId, uploader);
     if (!changeControl.canRebase()) {
       throw new InvalidChangeOperationException(
           "Cannot rebase: New patch sets are not allowed to be added to change: "
@@ -143,37 +119,19 @@
       rw = new RevWalk(git);
       inserter = git.newObjectInserter();
 
-      final List<PatchSetApproval> oldPatchSetApprovals =
-          db.patchSetApprovals().byChange(change.getId()).toList();
-
       final String baseRev = findBaseRevision(patchSetId, db,
           change.getDest(), git, null, null, null);
       final RevCommit baseCommit =
           rw.parseCommit(ObjectId.fromString(baseRev));
 
-      final PatchSet newPatchSet =
-          rebase(git, rw, inserter, patchSetId, change, uploader, baseCommit,
-              mergeUtilFactory.create(
-                  changeControl.getProjectControl().getProjectState(), true));
+      PersonIdent committerIdent =
+          uploader.newCommitterIdent(myIdent.getWhen(),
+              myIdent.getTimeZone());
 
-      final Set<Account.Id> oldReviewers = Sets.newHashSet();
-      final Set<Account.Id> oldCC = Sets.newHashSet();
-      for (PatchSetApproval a : oldPatchSetApprovals) {
-        if (a.getValue() != 0) {
-          oldReviewers.add(a.getAccountId());
-        } else {
-          oldCC.add(a.getAccountId());
-        }
-      }
-      final ReplacePatchSetSender cm =
-          rebasedPatchSetSenderFactory.create(change);
-      cm.setFrom(uploader);
-      cm.setPatchSet(newPatchSet);
-      cm.addReviewers(oldReviewers);
-      cm.addExtraCC(oldCC);
-      cm.send();
-
-      hooks.doPatchsetCreatedHook(change, newPatchSet, db);
+      rebase(git, rw, inserter, patchSetId, change,
+          uploader, baseCommit, mergeUtilFactory.create(
+              changeControl.getProjectControl().getProjectState(), true),
+          committerIdent, true, true);
     } catch (PathConflictException e) {
       throw new IOException(e.getMessage());
     } finally {
@@ -285,17 +243,20 @@
    *
    * The rebased commit is added as new patch set to the change.
    *
-   * E-mail notification and triggering of hooks is NOT done for the creation of
-   * the new patch set.
+   * E-mail notification and triggering of hooks is only done for the creation of
+   * the new patch set if `sendEmail` and `runHooks` are set to true.
    *
    * @param git the repository
    * @param revWalk the RevWalk
    * @param inserter the object inserter
    * @param patchSetId the id of the patch set
-   * @param chg the change that should be rebased
+   * @param change the change that should be rebased
    * @param uploader the user that creates the rebased patch set
    * @param baseCommit the commit that should be the new base
    * @param mergeUtil merge utilities for the destination project
+   * @param committerIdent the committer's identity
+   * @param sendMail if a mail notification should be sent for the new patch set
+   * @param runHooks if hooks should be run for the new patch set
    * @return the new patch set which is based on the given base commit
    * @throws NoSuchChangeException thrown if the change to which the patch set
    *         belongs does not exist or is not visible to the user
@@ -305,12 +266,13 @@
    */
   public PatchSet rebase(final Repository git, final RevWalk revWalk,
       final ObjectInserter inserter, final PatchSet.Id patchSetId,
-      final Change chg, final Account.Id uploader, final RevCommit baseCommit,
-      final MergeUtil mergeUtil) throws NoSuchChangeException,
+      final Change change, final IdentifiedUser uploader, final RevCommit baseCommit,
+      final MergeUtil mergeUtil, PersonIdent committerIdent,
+      boolean sendMail, boolean runHooks)
+          throws NoSuchChangeException,
       OrmException, IOException, InvalidChangeOperationException,
       PathConflictException {
-    Change change = chg;
-    if (!chg.currentPatchSetId().equals(patchSetId)) {
+    if (!change.currentPatchSetId().equals(patchSetId)) {
       throw new InvalidChangeOperationException("patch set is not current");
     }
     final PatchSet originalPatchSet = db.patchSets().get(patchSetId);
@@ -318,84 +280,31 @@
     final RevCommit rebasedCommit;
     ObjectId oldId = ObjectId.fromString(originalPatchSet.getRevision().get());
     ObjectId newId = rebaseCommit(git, inserter, revWalk.parseCommit(oldId),
-        baseCommit, mergeUtil, myIdent);
+        baseCommit, mergeUtil, committerIdent);
 
     rebasedCommit = revWalk.parseCommit(newId);
 
-    PatchSet.Id id = ChangeUtil.nextPatchSetId(git, change.currentPatchSetId());
-    final PatchSet newPatchSet = new PatchSet(id);
-    newPatchSet.setCreatedOn(new Timestamp(System.currentTimeMillis()));
-    newPatchSet.setUploader(uploader);
-    newPatchSet.setRevision(new RevId(rebasedCommit.name()));
-    newPatchSet.setDraft(originalPatchSet.isDraft());
+    final ChangeControl changeControl =
+        changeControlFactory.validateFor(change.getId(), uploader);
 
-    final PatchSetInfo info =
-        patchSetInfoFactory.get(rebasedCommit, newPatchSet.getId());
+    PatchSetInserter patchSetinserter = patchSetInserterFactory
+        .create(git, revWalk, changeControl.getRefControl(), change, rebasedCommit)
+        .setCopyLabels(true)
+        .setDraft(originalPatchSet.isDraft())
+        .setSendMail(sendMail)
+        .setRunHooks(runHooks);
 
-    final RefUpdate ru = git.updateRef(newPatchSet.getRefName());
-    ru.setExpectedOldObjectId(ObjectId.zeroId());
-    ru.setNewObjectId(rebasedCommit);
-    ru.disableRefLog();
-    if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-      throw new IOException(String.format("Failed to create ref %s in %s: %s",
-          newPatchSet.getRefName(), change.getDest().getParentKey().get(),
-          ru.getResult()));
-    }
-    gitRefUpdated.fire(change.getProject(), ru);
+    final ChangeMessage cmsg =
+        new ChangeMessage(new ChangeMessage.Key(change.getId(),
+            ChangeUtil.messageUUID(db)), uploader.getAccountId(), patchSetId);
+    cmsg.setMessage("Patch Set " + change.currentPatchSetId().get()
+        + ": Patch Set " + patchSetId.get() + " was rebased");
 
-    db.changes().beginTransaction(change.getId());
-    try {
-      Change updatedChange = db.changes().get(change.getId());
-      if (updatedChange != null && change.getStatus().isOpen()) {
-        change = updatedChange;
-      } else {
-        throw new InvalidChangeOperationException(String.format(
-            "Change %s is closed", change.getId()));
-      }
+    Change newChange = patchSetinserter
+        .setMessage(cmsg)
+        .insert();
 
-      ChangeUtil.insertAncestors(db, newPatchSet.getId(), rebasedCommit);
-      db.patchSets().insert(Collections.singleton(newPatchSet));
-      updatedChange =
-          db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
-            @Override
-            public Change update(Change change) {
-              if (change.getStatus().isClosed()) {
-                return null;
-              }
-              if (!change.currentPatchSetId().equals(patchSetId)) {
-                return null;
-              }
-              if (change.getStatus() != Change.Status.DRAFT) {
-                change.setStatus(Change.Status.NEW);
-              }
-              change.setLastSha1MergeTested(null);
-              change.setCurrentPatchSet(info);
-              ChangeUtil.updated(change);
-              return change;
-            }
-          });
-      if (updatedChange != null) {
-        change = updatedChange;
-      } else {
-        throw new InvalidChangeOperationException(String.format(
-            "Change %s was modified", change.getId()));
-      }
-
-      ApprovalsUtil.copyLabels(db, projectCache.get(change.getProject())
-          .getLabelTypes(), patchSetId, change.currentPatchSetId());
-
-      final ChangeMessage cmsg =
-          new ChangeMessage(new ChangeMessage.Key(change.getId(),
-              ChangeUtil.messageUUID(db)), uploader, patchSetId);
-      cmsg.setMessage("Patch Set " + change.currentPatchSetId().get()
-          + ": Patch Set " + patchSetId.get() + " was rebased");
-      db.changeMessages().insert(Collections.singleton(cmsg));
-      db.commit();
-    } finally {
-      db.rollback();
-    }
-
-    return newPatchSet;
+    return db.patchSets().get(newChange.currentPatchSetId());
   }
 
   /**
@@ -443,6 +352,34 @@
     return objectId;
   }
 
+  public boolean canRebase(RevisionResource r) {
+    Repository git;
+    try {
+      git = gitManager.openRepository(r.getChange().getProject());
+    } catch (RepositoryNotFoundException err) {
+      return false;
+    } catch (IOException err) {
+      return false;
+    }
+    try {
+      findBaseRevision(
+          r.getPatchSet().getId(),
+          db,
+          r.getChange().getDest(),
+          git,
+          null,
+          null,
+          null);
+      return true;
+    } catch (IOException e) {
+      return false;
+    } catch (OrmException e) {
+      return false;
+    } finally {
+      git.close();
+    }
+  }
+
   public static boolean canDoRebase(final ReviewDb db,
       final Change change, final GitRepositoryManager gitManager,
       List<PatchSetAncestor> patchSetAncestors,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 9a804c1..06d2a71 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -37,6 +37,7 @@
   private final AuthType authType;
   private final String httpHeader;
   private final boolean trustContainerAuth;
+  private final boolean enableRunAs;
   private final boolean userNameToLowerCase;
   private final boolean gitBasicAuth;
   private final String logoutUrl;
@@ -64,6 +65,7 @@
     cookiePath = cfg.getString("auth", null, "cookiepath");
     cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
     trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
+    enableRunAs = cfg.getBoolean("auth", null, "enableRunAs", true);
     gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
     userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
 
@@ -164,6 +166,11 @@
     return trustContainerAuth;
   }
 
+  /** @return true if users with Run As capability can impersonate others. */
+  public boolean isRunAsEnabled() {
+    return enableRunAs;
+  }
+
   /** Whether user name should be converted to lower-case before validation */
   public boolean isUserNameToLowerCase() {
     return userNameToLowerCase;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java
new file mode 100644
index 0000000..3a8bcc5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java
@@ -0,0 +1,52 @@
+// 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.config;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class CapabilitiesCollection implements
+    ChildCollection<ConfigResource, CapabilityResource> {
+  private final DynamicMap<RestView<CapabilityResource>> views;
+  private final Provider<ListCapabilities> list;
+
+  @Inject
+  CapabilitiesCollection(DynamicMap<RestView<CapabilityResource>> views,
+      Provider<ListCapabilities> list) {
+    this.views = views;
+    this.list = list;
+  }
+
+  @Override
+  public RestView<ConfigResource> list() throws ResourceNotFoundException {
+    return list.get();
+  }
+
+  @Override
+  public CapabilityResource parse(ConfigResource parent, IdString id)
+      throws ResourceNotFoundException {
+    throw new ResourceNotFoundException(id);
+  }
+
+  @Override
+  public DynamicMap<RestView<CapabilityResource>> views() {
+    return views;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
new file mode 100644
index 0000000..c0a014c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
@@ -0,0 +1,42 @@
+// 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.config;
+
+import org.eclipse.jgit.nls.NLS;
+import org.eclipse.jgit.nls.TranslationBundle;
+
+public class CapabilityConstants extends TranslationBundle {
+  public static CapabilityConstants get() {
+    return NLS.getBundleFor(CapabilityConstants.class);
+  }
+
+  public String accessDatabase;
+  public String administrateServer;
+  public String createAccount;
+  public String createGroup;
+  public String createProject;
+  public String emailReviewers;
+  public String flushCaches;
+  public String killTask;
+  public String priority;
+  public String queryLimit;
+  public String runAs;
+  public String runGC;
+  public String startReplication;
+  public String streamEvents;
+  public String viewCaches;
+  public String viewConnections;
+  public String viewQueue;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
similarity index 64%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
index b2fb901..7e3c87e 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.server.config;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class CapabilityResource extends ConfigResource {
+  public static final TypeLiteral<RestView<CapabilityResource>> CAPABILITY_KIND =
+      new TypeLiteral<RestView<CapabilityResource>>() {};
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java
new file mode 100644
index 0000000..5ed007a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java
@@ -0,0 +1,52 @@
+// 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.config;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestCollection;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.inject.Inject;
+
+public class ConfigCollection implements
+    RestCollection<TopLevelResource, ConfigResource> {
+  private final DynamicMap<RestView<ConfigResource>> views;
+
+  @Inject
+  ConfigCollection(DynamicMap<RestView<ConfigResource>> views) {
+    this.views = views;
+  }
+
+  @Override
+  public RestView<TopLevelResource> list() throws ResourceNotFoundException {
+    throw new ResourceNotFoundException();
+  }
+
+  @Override
+  public DynamicMap<RestView<ConfigResource>> views() {
+    return views;
+  }
+
+  @Override
+  public ConfigResource parse(TopLevelResource root, IdString id)
+      throws ResourceNotFoundException {
+    if (id.equals("server")) {
+      return new ConfigResource();
+    }
+    throw new ResourceNotFoundException(id);
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
similarity index 61%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
index b2fb901..ec0e0c2 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.server.config;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class ConfigResource implements RestResource {
+  public static final TypeLiteral<RestView<ConfigResource>> CONFIG_KIND =
+      new TypeLiteral<RestView<ConfigResource>>() {};
 }
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 62c6863..56f13be 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
@@ -65,7 +65,6 @@
 import com.google.gerrit.server.auth.ldap.LdapModule;
 import com.google.gerrit.server.avatar.AvatarProvider;
 import com.google.gerrit.server.cache.CacheRemovalListener;
-import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.ChangeCache;
@@ -88,10 +87,11 @@
 import com.google.gerrit.server.mail.FromAddressGeneratorProvider;
 import com.google.gerrit.server.mail.MergeFailSender;
 import com.google.gerrit.server.mail.MergedSender;
-import com.google.gerrit.server.mail.RebasedPatchSetSender;
+import com.google.gerrit.server.mail.RegisterNewEmailSender;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
 import com.google.gerrit.server.mail.VelocityRuntimeProvider;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
+import com.google.gerrit.server.patch.PatchScriptFactory;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.AccessControlModule;
 import com.google.gerrit.server.project.ChangeControl;
@@ -105,7 +105,6 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.SectionSortCache;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-import com.google.gerrit.server.query.change.ChangeQueryRewriter;
 import com.google.gerrit.server.ssh.SshAddressesModule;
 import com.google.gerrit.server.tools.ToolsCatalog;
 import com.google.gerrit.server.util.IdGenerator;
@@ -171,7 +170,6 @@
     install(ThreadLocalRequestContext.module());
 
     bind(AccountResolver.class);
-    bind(ChangeQueryRewriter.class);
 
     factory(AccountInfoCacheFactory.Factory.class);
     factory(AddReviewerSender.Factory.class);
@@ -186,12 +184,13 @@
     factory(MergedSender.Factory.class);
     factory(MergeFailSender.Factory.class);
     factory(MergeUtil.Factory.class);
+    factory(PatchScriptFactory.Factory.class);
     factory(PerformCreateGroup.Factory.class);
     factory(PerformRenameGroup.Factory.class);
     factory(PluginUser.Factory.class);
     factory(ProjectNode.Factory.class);
     factory(ProjectState.Factory.class);
-    factory(RebasedPatchSetSender.Factory.class);
+    factory(RegisterNewEmailSender.Factory.class);
     factory(ReplacePatchSetSender.Factory.class);
     factory(PerformCreateProject.Factory.class);
     factory(GarbageCollection.Factory.class);
@@ -218,7 +217,6 @@
     bind(TransferConfig.class);
 
     bind(ApprovalsUtil.class);
-    bind(ChangeInserter.class);
     bind(ChangeMergeQueue.class).in(SINGLETON);
     bind(MergeQueue.class).to(ChangeMergeQueue.class).in(SINGLETON);
     factory(ReloadSubmitQueueOp.Factory.class);
@@ -236,8 +234,10 @@
     bind(AccountControl.Factory.class);
 
     install(new AuditModule());
+    install(new com.google.gerrit.server.access.Module());
     install(new com.google.gerrit.server.account.Module());
     install(new com.google.gerrit.server.change.Module());
+    install(new com.google.gerrit.server.config.Module());
     install(new com.google.gerrit.server.group.Module());
     install(new com.google.gerrit.server.project.Module());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetVersion.java
new file mode 100644
index 0000000..f618959c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetVersion.java
@@ -0,0 +1,30 @@
+// 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.config;
+
+import com.google.gerrit.common.Version;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+
+public class GetVersion implements RestReadView<ConfigResource> {
+  @Override
+  public String apply(ConfigResource resource) throws ResourceNotFoundException {
+    String version = Version.getVersion();
+    if (version == null) {
+      throw new ResourceNotFoundException();
+    }
+    return version;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
new file mode 100644
index 0000000..d92dfa7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -0,0 +1,54 @@
+// 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.config;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+
+import java.util.Map;
+
+/** List capabilities visible to the calling user. */
+public class ListCapabilities implements RestReadView<ConfigResource> {
+  @Override
+  public Map<String, CapabilityInfo> apply(ConfigResource resource)
+      throws AuthException, BadRequestException, ResourceConflictException,
+      IllegalArgumentException, SecurityException, IllegalAccessException,
+      NoSuchFieldException {
+    Map<String, CapabilityInfo> output = Maps.newTreeMap();
+    Class<? extends CapabilityConstants> bundleClass =
+        CapabilityConstants.get().getClass();
+    CapabilityConstants c = CapabilityConstants.get();
+    for (String id : GlobalCapability.getAllNames()) {
+      String name = (String) bundleClass.getField(id).get(c);
+      output.put(id, new CapabilityInfo(id, name));
+    }
+    return output;
+  }
+
+  public static class CapabilityInfo {
+    final String kind = "gerritcodereview#capability";
+    public String id;
+    public String name;
+
+    public CapabilityInfo(String id, String name) {
+      this.id = id;
+      this.name = name;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
new file mode 100644
index 0000000..0ea7390
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -0,0 +1,31 @@
+// 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.config;
+
+import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
+import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+
+public class Module extends RestApiModule {
+  @Override
+  protected void configure() {
+    DynamicMap.mapOf(binder(), CONFIG_KIND);
+    DynamicMap.mapOf(binder(), CAPABILITY_KIND);
+    child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
+    get(CONFIG_KIND, "version").to(GetVersion.class);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
index 2116c0c..9d7c54a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java
@@ -40,6 +40,7 @@
   public final File hooks_dir;
   public final File static_dir;
   public final File themes_dir;
+  public final File index_dir;
 
   public final File gerrit_sh;
   public final File gerrit_war;
@@ -77,6 +78,7 @@
     hooks_dir = new File(site_path, "hooks");
     static_dir = new File(site_path, "static");
     themes_dir = new File(site_path, "themes");
+    index_dir = new File(site_path, "index");
 
     gerrit_sh = new File(bin_dir, "gerrit.sh");
     gerrit_war = new File(bin_dir, "gerrit.war");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/data/PatchSetAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/data/PatchSetAttribute.java
index 79d82e3..91df974 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/data/PatchSetAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/data/PatchSetAttribute.java
@@ -24,6 +24,7 @@
   public AccountAttribute uploader;
   public Long createdOn;
   public AccountAttribute author;
+  public boolean isDraft;
 
   public List<ApprovalAttribute> approvals;
   public List<PatchSetCommentAttribute> comments;
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 63bfa71..084b79b 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
@@ -110,6 +110,7 @@
     a.subject = change.getSubject();
     a.url = getChangeUrl(change);
     a.owner = asAccountAttribute(change.getOwner());
+    a.status = change.getStatus();
     return a;
   }
 
@@ -141,7 +142,6 @@
     a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
     a.sortKey = change.getSortKey();
     a.open = change.getStatus().isOpen();
-    a.status = change.getStatus();
   }
 
   /**
@@ -361,6 +361,7 @@
     p.ref = patchSet.getRefName();
     p.uploader = asAccountAttribute(patchSet.getUploader());
     p.createdOn = patchSet.getCreatedOn().getTime() / 1000L;
+    p.isDraft = patchSet.isDraft();
     final PatchSet.Id pId = patchSet.getId();
     try {
       final ReviewDb db = schema.open();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java
similarity index 63%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java
index b2fb901..e725eac 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java
@@ -12,12 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.server.events;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.ChangeAttribute;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
-}
+public class TopicChangedEvent extends ChangeEvent {
+  public final String type = "topic-changed";
+  public ChangeAttribute change;
+  public AccountAttribute changer;
+  public String oldTopic;
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
new file mode 100644
index 0000000..9aae367
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -0,0 +1,129 @@
+// 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.extensions.webui;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestCollection;
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
+import com.google.gerrit.extensions.webui.UiAction;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+public class UiActions {
+  private static final Logger log = LoggerFactory.getLogger(UiActions.class);
+
+  public static Predicate<UiAction.Description> enabled() {
+    return new Predicate<UiAction.Description>() {
+      @Override
+      public boolean apply(UiAction.Description input) {
+        return input.isEnabled();
+      }
+    };
+  }
+
+  public static List<UiAction.Description> sorted(Iterable<UiAction.Description> in) {
+    List<UiAction.Description> s = Lists.newArrayList(in);
+    Collections.sort(s, new Comparator<UiAction.Description>() {
+      @Override
+      public int compare(UiAction.Description a, UiAction.Description b) {
+        return a.getId().compareTo(b.getId());
+      }
+    });
+    return s;
+  }
+
+  public static Iterable<UiAction.Description> plugins(Iterable<UiAction.Description> in) {
+    return Iterables.filter(in,
+      new Predicate<UiAction.Description>() {
+        @Override
+        public boolean apply(UiAction.Description input) {
+          return input.getId().indexOf('~') > 0;
+        }
+      });
+  }
+
+  public static <R extends RestResource> Iterable<UiAction.Description> from(
+      RestCollection<?, R> collection,
+      R resource) {
+    return from(collection.views(), resource);
+  }
+
+  public static <R extends RestResource> Iterable<UiAction.Description> from(
+      DynamicMap<RestView<R>> views,
+      final R resource) {
+    return Iterables.filter(
+      Iterables.transform(
+        views,
+        new Function<DynamicMap.Entry<RestView<R>>, UiAction.Description> () {
+          @Override
+          @Nullable
+          public UiAction.Description apply(DynamicMap.Entry<RestView<R>> e) {
+            int d = e.getExportName().indexOf('.');
+            if (d < 0) {
+              return null;
+            }
+
+            RestView<R> view;
+            try {
+              view = e.getProvider().get();
+            } catch (RuntimeException err) {
+              log.error(String.format(
+                  "error creating view %s.%s",
+                  e.getPluginName(), e.getExportName()), err);
+              return null;
+            }
+
+            if (!(view instanceof UiAction)) {
+              return null;
+            }
+
+            UiAction.Description dsc =
+                ((UiAction<R>) view).getDescription(resource);
+            if (dsc == null || !dsc.isVisible()) {
+              return null;
+            }
+
+            String name = e.getExportName().substring(d + 1);
+            PrivateInternals_UiActionDescription.setMethod(
+                dsc,
+                e.getExportName().substring(0, d));
+            PrivateInternals_UiActionDescription.setId(
+                dsc,
+                "gerrit".equals(e.getPluginName())
+                  ? name
+                  : e.getPluginName() + '~' + name);
+            return dsc;
+          }
+        }),
+      Predicates.notNull());
+  }
+
+  private UiActions() {
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
index 6f71936..3262b23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.server.git;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
index ce5308f..f4ed7cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
@@ -148,8 +148,9 @@
     final String cherryPickCmtMsg = args.mergeUtil.createCherryPickCommitMessage(n);
 
     final CodeReviewCommit newCommit =
-        args.mergeUtil.createCherryPickFromCommit(args.repo, args.inserter, mergeTip, n,
-            cherryPickCommitterIdent, cherryPickCmtMsg, args.rw);
+        (CodeReviewCommit) args.mergeUtil.createCherryPickFromCommit(args.repo,
+            args.inserter, mergeTip, n, cherryPickCommitterIdent,
+            cherryPickCmtMsg, args.rw);
 
     if (newCommit == null) {
         return null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 9715d9c4..2c8aeb6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.git;
 
 import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
+
 import static java.util.concurrent.TimeUnit.DAYS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
@@ -43,6 +44,7 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.MergeFailSender;
 import com.google.gerrit.server.mail.MergedSender;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -153,6 +155,7 @@
   private final WorkQueue workQueue;
   private final RequestScopePropagator requestScopePropagator;
   private final AllProjectsName allProjectsName;
+  private final ChangeIndexer indexer;
 
   @Inject
   MergeOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> sf,
@@ -168,7 +171,8 @@
       final SubmoduleOp.Factory subOpFactory,
       final WorkQueue workQueue,
       final RequestScopePropagator requestScopePropagator,
-      final AllProjectsName allProjectsName) {
+      final AllProjectsName allProjectsName,
+      final ChangeIndexer indexer) {
     repoManager = grm;
     schemaFactory = sf;
     labelNormalizer = fs;
@@ -188,6 +192,7 @@
     this.workQueue = workQueue;
     this.requestScopePropagator = requestScopePropagator;
     this.allProjectsName = allProjectsName;
+    this.indexer = indexer;
     destBranch = branch;
     toMerge = ArrayListMultimap.create();
     potentiallyStillSubmittable = new ArrayList<CodeReviewCommit>();
@@ -763,6 +768,7 @@
       } catch (OrmException err) {
         log.warn("Error updating change status for " + c.getId(), err);
       }
+      indexer.index(c);
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index 862eb2b..3ca5848 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -180,8 +180,8 @@
     return submitter;
   }
 
-  public CodeReviewCommit createCherryPickFromCommit(Repository repo,
-      ObjectInserter inserter, CodeReviewCommit mergeTip, CodeReviewCommit originalCommit,
+  public RevCommit createCherryPickFromCommit(Repository repo,
+      ObjectInserter inserter, RevCommit mergeTip, RevCommit originalCommit,
       PersonIdent cherryPickCommitterIdent, String commitMsg, RevWalk rw)
       throws MissingObjectException, IncorrectObjectTypeException, IOException {
 
@@ -199,10 +199,8 @@
       mergeCommit.setMessage(commitMsg);
 
       final ObjectId id = commit(inserter, mergeCommit);
-      final CodeReviewCommit newCommit =
-          (CodeReviewCommit) rw.parseCommit(id);
 
-      return newCommit;
+      return rw.parseCommit(id);
     } else {
       return null;
     }
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 9c5633b..f7b5ab0 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
@@ -16,6 +16,8 @@
 
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
+import com.google.common.base.Strings;
+
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.slf4j.Logger;
@@ -62,7 +64,7 @@
   public class Task implements ProgressMonitor {
     private final String name;
     private final int total;
-    private volatile int count;
+    private int count;
     private int lastPercent;
 
     Task(final String subTaskName, final int totalWork) {
@@ -73,29 +75,35 @@
     /**
      * Indicate that work has been completed on this sub-task.
      * <p>
-     * Must be called from the worker thread.
+     * Must be called from a worker thread.
      *
      * @param completed number of work units completed.
      */
     @Override
     public void update(final int completed) {
-      count += completed;
-      if (total != UNKNOWN) {
-        int percent = count * 100 / total;
-        if (percent > lastPercent) {
-          lastPercent = percent;
-          wakeUp();
+      boolean w = false;
+      synchronized (this) {
+        count += completed;
+        if (total != UNKNOWN) {
+          int percent = count * 100 / total;
+          if (percent > lastPercent) {
+            lastPercent = percent;
+            w = true;
+          }
         }
       }
+      if (w) {
+        wakeUp();
+      }
     }
 
     /**
      * Indicate that this sub-task is finished.
      * <p>
-     * Must be called from the worker thread.
+     * Must be called from a worker thread.
      */
     public void end() {
-      if (total == UNKNOWN && count > 0) {
+      if (total == UNKNOWN && getCount() > 0) {
         wakeUp();
       }
     }
@@ -116,6 +124,10 @@
     public boolean isCancelled() {
       return false;
     }
+
+    public synchronized int getCount() {
+      return count;
+    }
   }
 
   private final OutputStream out;
@@ -165,19 +177,19 @@
   /**
    * Wait for a task managed by a {@link Future}.
    * <p>
-   * Must be called from the main thread, <em>not</em> the worker thread. Once
-   * the worker thread calls {@link #end()}, the future has an additional
+   * Must be called from the main thread, <em>not</em> a worker thread. Once a
+   * worker thread calls {@link #end()}, the future has an additional
    * <code>maxInterval</code> to finish before it is forcefully cancelled and
    * {@link ExecutionException} is thrown.
    *
-   * @param workerFuture a future that returns when the worker thread is
-   *     finished.
+   * @param workerFuture a future that returns when worker threads are finished.
    * @param timeoutTime overall timeout for the task; the future is forcefully
    *     cancelled if the task exceeds the timeout. Non-positive values indicate
    *     no timeout.
    * @param timeoutUnit unit for overall task timeout.
-   * @throws ExecutionException if this thread or the worker thread was
-   *     interrupted, the worker was cancelled, or the worker timed out.
+   * @throws ExecutionException if this thread or a worker thread was
+   *     interrupted, the worker was cancelled, or timed out waiting for a
+   *     worker to call {@link #end()}.
    */
   public void waitFor(final Future<?> workerFuture, final long timeoutTime,
       final TimeUnit timeoutUnit) throws ExecutionException {
@@ -268,7 +280,7 @@
   /**
    * End the overall task.
    * <p>
-   * Must be called from the worker thread.
+   * Must be called from a worker thread.
    */
   public synchronized void end() {
     done = true;
@@ -319,7 +331,10 @@
           first = false;
         }
 
-        s.append(' ').append(t.name).append(": ");
+        s.append(' ');
+        if (!Strings.isNullOrEmpty(t.name)) {
+          s.append(t.name).append(": ");
+        }
         if (t.total == UNKNOWN) {
           s.append(count);
         } else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index bfaa97e..b6d50ee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -107,6 +107,7 @@
   private static final String RECEIVE = "receive";
   private static final String KEY_REQUIRE_SIGNED_OFF_BY = "requireSignedOffBy";
   private static final String KEY_REQUIRE_CHANGE_ID = "requireChangeId";
+  private static final String KEY_MAX_OBJECT_SIZE_LIMIT = "maxObjectSizeLimit";
   private static final String KEY_REQUIRE_CONTRIBUTOR_AGREEMENT =
       "requireContributorAgreement";
 
@@ -145,6 +146,7 @@
   private List<CommentLinkInfo> commentLinkSections;
   private List<ValidationError> validationErrors;
   private ObjectId rulesId;
+  private long maxObjectSizeLimit;
 
   public static ProjectConfig read(MetaDataUpdate update) throws IOException,
       ConfigInvalidException {
@@ -319,6 +321,14 @@
   }
 
   /**
+   * @return the maxObjectSizeLimit for this project, if set. Zero if this
+   *         project doesn't define own maxObjectSizeLimit.
+   */
+  public long getMaxObjectSizeLimit() {
+    return maxObjectSizeLimit;
+  }
+
+  /**
    * Check all GroupReferences use current group name, repairing stale ones.
    *
    * @param groupBackend cache to use when looking up group information by UUID.
@@ -372,6 +382,7 @@
     p.setUseContributorAgreements(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, Project.InheritableBoolean.INHERIT));
     p.setUseSignedOffBy(getEnum(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, Project.InheritableBoolean.INHERIT));
     p.setRequireChangeID(getEnum(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, Project.InheritableBoolean.INHERIT));
+    p.setMaxObjectSizeLimit(rc.getString(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT));
 
     p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, defaultSubmitAction));
     p.setUseContentMerge(getEnum(rc, SUBMIT, null, KEY_MERGE_CONTENT, Project.InheritableBoolean.INHERIT));
@@ -386,6 +397,8 @@
     loadNotifySections(rc, groupsByName);
     loadLabelSections(rc);
     loadCommentLinkSections(rc);
+
+    maxObjectSizeLimit = rc.getLong(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, 0);
   }
 
   private void loadAccountsSection(
@@ -512,7 +525,7 @@
           if (isPermission(varName)) {
             Permission perm = as.getPermission(varName, true);
             loadPermissionRules(rc, ACCESS, refName, varName, groupsByName,
-                perm, perm.isLabel());
+                perm, Permission.hasRange(varName));
           }
         }
       }
@@ -711,11 +724,12 @@
     set(rc, RECEIVE, null, KEY_REQUIRE_CONTRIBUTOR_AGREEMENT, p.getUseContributorAgreements(), Project.InheritableBoolean.INHERIT);
     set(rc, RECEIVE, null, KEY_REQUIRE_SIGNED_OFF_BY, p.getUseSignedOffBy(), Project.InheritableBoolean.INHERIT);
     set(rc, RECEIVE, null, KEY_REQUIRE_CHANGE_ID, p.getRequireChangeID(), Project.InheritableBoolean.INHERIT);
+    set(rc, RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, validMaxObjectSizeLimit(p.getMaxObjectSizeLimit()));
 
     set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction);
     set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.getUseContentMerge(), Project.InheritableBoolean.INHERIT);
 
-    set(rc, PROJECT, null, KEY_STATE, p.getState(), null);
+    set(rc, PROJECT, null, KEY_STATE, p.getState(), defaultStateValue);
 
     set(rc, DASHBOARD, null, KEY_DEFAULT, p.getDefaultDashboard());
     set(rc, DASHBOARD, null, KEY_LOCAL_DEFAULT, p.getLocalDefaultDashboard());
@@ -732,6 +746,35 @@
     saveGroupList();
   }
 
+  public static final String validMaxObjectSizeLimit(String value)
+      throws ConfigInvalidException {
+    if (value == null) {
+      return null;
+    }
+    value = value.trim();
+    if (value.isEmpty()) {
+      return null;
+    }
+    Config cfg = new Config();
+    cfg.fromText("[s]\nn=" + value);
+    try {
+      long s = cfg.getLong("s", "n", 0);
+      if (s < 0) {
+        throw new ConfigInvalidException(String.format(
+            "Negative value '%s' not allowed as %s", value,
+            KEY_MAX_OBJECT_SIZE_LIMIT));
+      }
+      if (s == 0) {
+        // return null for the default so that it is not persisted
+        return null;
+      }
+      return value;
+    } catch (IllegalArgumentException e) {
+      throw new ConfigInvalidException(
+          String.format("Value '%s' not parseable as a Long", value), e);
+    }
+  }
+
   private void saveAccountsSection(Config rc, Set<AccountGroup.UUID> keepGroups) {
     if (accountsSection != null) {
       rc.setStringList(ACCOUNTS, null, KEY_SAME_GROUP_VISIBILITY,
@@ -870,7 +913,7 @@
       for (Permission permission : sort(as.getPermissions())) {
         have.add(permission.getName().toLowerCase());
 
-        boolean needRange = permission.isLabel();
+        boolean needRange = Permission.hasRange(permission.getName());
         List<String> rules = new ArrayList<String>();
         for (PermissionRule rule : sort(permission.getRules())) {
           GroupReference group = rule.getGroup();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
index 8490ea1..d237f49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.changedetail.PathConflictException;
 import com.google.gerrit.server.changedetail.RebaseChange;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -25,6 +26,7 @@
 import com.google.gwtorm.server.OrmException;
 
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -35,12 +37,14 @@
 
   private final RebaseChange rebaseChange;
   private final Map<Change.Id, CodeReviewCommit> newCommits;
+  private final PersonIdent committerIdent;
 
   RebaseIfNecessary(final SubmitStrategy.Arguments args,
-      final RebaseChange rebaseChange) {
+      final RebaseChange rebaseChange, PersonIdent committerIdent) {
     super(args);
     this.rebaseChange = rebaseChange;
     this.newCommits = new HashMap<Change.Id, CodeReviewCommit>();
+    this.committerIdent = committerIdent;
   }
 
   @Override
@@ -73,11 +77,14 @@
 
         } else {
           try {
+            final IdentifiedUser uploader =
+                args.identifiedUserFactory.create(
+                    args.mergeUtil.getSubmitter(n.patchsetId).getAccountId());
             final PatchSet newPatchSet =
                 rebaseChange.rebase(args.repo, args.rw, args.inserter,
-                    n.patchsetId, n.change,
-                    args.mergeUtil.getSubmitter(n.patchsetId).getAccountId(),
-                    newMergeTip, args.mergeUtil);
+                    n.patchsetId, n.change, uploader,
+                    newMergeTip, args.mergeUtil, committerIdent,
+                    false, false);
             List<PatchSetApproval> approvals = Lists.newArrayList();
             for (PatchSetApproval a : args.mergeUtil.getApprovalsForCommit(n)) {
               approvals.add(new PatchSetApproval(newPatchSet.getId(), a));
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 3e28b07..f581c7d 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
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.git;
 
-import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
 import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
 import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
 import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
@@ -62,6 +61,7 @@
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountResolver;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeResource;
@@ -76,6 +76,7 @@
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
 import com.google.gerrit.server.git.validators.CommitValidators;
+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.MergedSender;
@@ -261,10 +262,12 @@
   private final CommitValidators.Factory commitValidatorsFactory;
   private final TrackingFooters trackingFooters;
   private final TagCache tagCache;
-  private final ChangeInserter changeInserter;
+  private final AccountCache accountCache;
+  private final ChangeInserter.Factory changeInserterFactory;
   private final WorkQueue workQueue;
   private final ListeningExecutorService changeUpdateExector;
   private final RequestScopePropagator requestScopePropagator;
+  private final ChangeIndexer indexer;
   private final SshInfo sshInfo;
   private final AllProjectsName allProjectsName;
   private final ReceiveConfig receiveConfig;
@@ -316,8 +319,9 @@
       final ProjectCache projectCache,
       final GitRepositoryManager repoManager,
       final TagCache tagCache,
+      final AccountCache accountCache,
       final ChangeCache changeCache,
-      final ChangeInserter changeInserter,
+      final ChangeInserter.Factory changeInserterFactory,
       final CommitValidators.Factory commitValidatorsFactory,
       @CanonicalWebUrl @Nullable final String canonicalWebUrl,
       @GerritPersonIdent final PersonIdent gerritIdent,
@@ -325,6 +329,7 @@
       final WorkQueue workQueue,
       @ChangeUpdateExecutor ListeningExecutorService changeUpdateExector,
       final RequestScopePropagator requestScopePropagator,
+      final ChangeIndexer indexer,
       final SshInfo sshInfo,
       final AllProjectsName allProjectsName,
       ReceiveConfig config,
@@ -350,11 +355,13 @@
     this.canonicalWebUrl = canonicalWebUrl;
     this.trackingFooters = trackingFooters;
     this.tagCache = tagCache;
-    this.changeInserter = changeInserter;
+    this.accountCache = accountCache;
+    this.changeInserterFactory = changeInserterFactory;
     this.commitValidatorsFactory = commitValidatorsFactory;
     this.workQueue = workQueue;
     this.changeUpdateExector = changeUpdateExector;
     this.requestScopePropagator = requestScopePropagator;
+    this.indexer = indexer;
     this.sshInfo = sshInfo;
     this.allProjectsName = allProjectsName;
     this.receiveConfig = config;
@@ -1344,7 +1351,7 @@
         Change.Key changeKey = new Change.Key("I" + c.name());
         final List<String> idList = c.getFooterLines(CHANGE_ID);
         if (idList.isEmpty()) {
-          newChanges.add(new CreateRequest(c, changeKey));
+          newChanges.add(new CreateRequest(magicBranch.ctl, c, changeKey));
           continue;
         }
 
@@ -1394,7 +1401,7 @@
 
           newChangeIds.add(p.changeKey);
         }
-        newChanges.add(new CreateRequest(p.commit, p.changeKey));
+        newChanges.add(new CreateRequest(magicBranch.ctl, p.commit, p.changeKey));
       }
     } catch (IOException e) {
       // Should never happen, the core receive process would have
@@ -1460,34 +1467,22 @@
   private class CreateRequest {
     final RevCommit commit;
     final Change change;
-    final PatchSet ps;
     final ReceiveCommand cmd;
-    private final PatchSetInfo info;
+    final ChangeInserter ins;
     boolean created;
 
-    CreateRequest(RevCommit c, Change.Key changeKey) throws OrmException {
+    CreateRequest(RefControl ctl, RevCommit c, Change.Key changeKey)
+        throws OrmException {
       commit = c;
-
       change = new Change(changeKey,
           new Change.Id(db.nextChangeId()),
           currentUser.getAccountId(),
           magicBranch.dest);
       change.setTopic(magicBranch.topic);
-
-      ps = new PatchSet(new PatchSet.Id(change.getId(), INITIAL_PATCH_SET_ID));
-      ps.setCreatedOn(change.getCreatedOn());
-      ps.setUploader(change.getOwner());
-      ps.setRevision(toRevId(c));
-
-      if (magicBranch.isDraft()) {
-        change.setStatus(Change.Status.DRAFT);
-        ps.setDraft(true);
-      }
-
-      info = patchSetInfoFactory.get(c, ps.getId());
-      change.setCurrentPatchSet(info);
-      ChangeUtil.updated(change);
-      cmd = new ReceiveCommand(ObjectId.zeroId(), c, ps.getRefName());
+      ins = changeInserterFactory.create(ctl, change, c)
+          .setDraft(magicBranch.isDraft());
+      cmd = new ReceiveCommand(ObjectId.zeroId(), c,
+          ins.getPatchSet().getRefName());
     }
 
     CheckedFuture<Void, OrmException> insertChange() throws IOException {
@@ -1518,6 +1513,7 @@
     }
 
     private void insertChange(ReviewDb db) throws OrmException {
+      final PatchSet ps = ins.getPatchSet();
       final Account.Id me = currentUser.getAccountId();
       final List<FooterLine> footerLines = commit.getFooterLines();
       final MailRecipients recipients = new MailRecipients();
@@ -1527,9 +1523,10 @@
       recipients.add(getRecipientsFromFooters(accountResolver, ps, footerLines));
       recipients.remove(me);
 
-      changeInserter.insertChange(db, change, ps, commit, labelTypes, info,
-          recipients.getReviewers());
-
+      ins
+        .setReviewers(recipients.getReviewers())
+        .setSendMail(false)
+        .insert();
       created = true;
 
       workQueue.getDefaultQueue()
@@ -1540,7 +1537,7 @@
             CreateChangeSender cm =
                 createChangeSenderFactory.create(change);
             cm.setFrom(me);
-            cm.setPatchSet(ps, info);
+            cm.setPatchSet(ps, ins.getPatchSetInfo());
             cm.addReviewers(recipients.getReviewers());
             cm.addExtraCC(recipients.getCcOnly());
             cm.send();
@@ -1749,12 +1746,16 @@
             eq(newCommit.getFullMessage(), priorCommit.getFullMessage());
         final boolean parentsEq = parentsEqual(newCommit, priorCommit);
         final boolean authorEq = authorEqual(newCommit, priorCommit);
+        final ObjectReader reader = rp.getRevWalk().getObjectReader();
 
         if (messageEq && parentsEq && authorEq && !autoClose) {
+          addMessage(String.format(
+              "(W) No changes between prior commit %s and new commit %s",
+              reader.abbreviate(priorCommit).name(),
+              reader.abbreviate(newCommit).name()));
           reject(inputCommand, "no changes made");
           return false;
         } else {
-          ObjectReader reader = rp.getRevWalk().getObjectReader();
           StringBuilder msg = new StringBuilder();
           msg.append("(W) ");
           msg.append(reader.abbreviate(newCommit).name());
@@ -1924,6 +1925,7 @@
       if (cmd.getResult() == NOT_ATTEMPTED) {
         cmd.execute(rp);
       }
+      indexer.index(change);
       gitRefUpdated.fire(project.getNameKey(), newPatchSet.getRefName(),
           ObjectId.zeroId(), newCommit);
       hooks.doPatchsetCreatedHook(change, newPatchSet, db);
@@ -2057,6 +2059,7 @@
       return;
     }
 
+    boolean defaultName = Strings.isNullOrEmpty(currentUser.getAccount().getFullName());
     final RevWalk walk = rp.getRevWalk();
     walk.reset();
     walk.sort(RevSort.NONE);
@@ -2072,6 +2075,23 @@
         } else if (!validCommit(ctl, cmd, c)) {
           break;
         }
+
+        if (defaultName && currentUser.getEmailAddresses().contains(
+              c.getCommitterIdent().getEmailAddress())) {
+          try {
+            Account a = db.accounts().get(currentUser.getAccountId());
+            if (a != null && Strings.isNullOrEmpty(a.getFullName())) {
+              a.setFullName(c.getCommitterIdent().getName());
+              db.accounts().update(Collections.singleton(a));
+              currentUser.getAccount().setFullName(a.getFullName());
+              accountCache.evict(a.getId());
+            }
+          } catch (OrmException e) {
+            log.warn("Cannot default full_name", e);
+          } finally {
+            defaultName = false;
+          }
+        }
       }
     } catch (IOException err) {
       cmd.setResult(REJECTED_MISSING_OBJECT);
@@ -2195,6 +2215,7 @@
 
     ReplaceRequest result = new ReplaceRequest(cid, commit, cmd, false);
     result.change = change;
+    result.changeCtl = projectControl.controlFor(change);
     result.newPatchSet = ps;
     result.info = patchSetInfoFactory.get(commit, psi);
     result.mergedIntoRef = refName;
@@ -2228,7 +2249,7 @@
 
   private void markChangeMergedByPush(final ReviewDb db,
       final ReplaceRequest result) throws OrmException {
-    final Change change = result.change;
+    Change change = result.change;
     final String mergedIntoRef = result.mergedIntoRef;
 
     change.setCurrentPatchSet(result.info);
@@ -2256,17 +2277,19 @@
 
     db.changeMessages().insert(Collections.singleton(msg));
 
-    db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
-      @Override
-      public Change update(Change change) {
-        if (change.getStatus().isOpen()) {
-          change.setCurrentPatchSet(result.info);
-          change.setStatus(Change.Status.MERGED);
-          ChangeUtil.updated(change);
-        }
-        return change;
-      }
-    });
+    change = db.changes().atomicUpdate(
+        change.getId(), new AtomicUpdate<Change>() {
+          @Override
+          public Change update(Change change) {
+            if (change.getStatus().isOpen()) {
+              change.setCurrentPatchSet(result.info);
+              change.setStatus(Change.Status.MERGED);
+              ChangeUtil.updated(change);
+            }
+            return change;
+          }
+        });
+    indexer.index(change);
   }
 
   private void sendMergedEmail(final ReplaceRequest result) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
index 7c2ba86..9626e23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.index.ChangeIndexer;
 
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -55,13 +56,15 @@
     protected final Set<RevCommit> alreadyAccepted;
     protected final Branch.NameKey destBranch;
     protected final MergeUtil mergeUtil;
+    protected final ChangeIndexer indexer;
     protected final MergeSorter mergeSorter;
 
     Arguments(final IdentifiedUser.GenericFactory identifiedUserFactory,
         final PersonIdent myIdent, final ReviewDb db, final Repository repo,
         final RevWalk rw, final ObjectInserter inserter,
         final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
-        final Branch.NameKey destBranch, final MergeUtil mergeUtil) {
+        final Branch.NameKey destBranch, final MergeUtil mergeUtil,
+        final ChangeIndexer indexer) {
       this.identifiedUserFactory = identifiedUserFactory;
       this.myIdent = myIdent;
       this.db = db;
@@ -73,6 +76,7 @@
       this.alreadyAccepted = alreadyAccepted;
       this.destBranch = destBranch;
       this.mergeUtil = mergeUtil;
+      this.indexer = indexer;
       this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
index 8bf831c..845139d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.changedetail.RebaseChange;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
@@ -54,6 +55,7 @@
   private final RebaseChange rebaseChange;
   private final ProjectCache projectCache;
   private final MergeUtil.Factory mergeUtilFactory;
+  private final ChangeIndexer indexer;
 
   @Inject
   SubmitStrategyFactory(
@@ -63,7 +65,8 @@
       @CanonicalWebUrl @Nullable final Provider<String> urlProvider,
       final GitReferenceUpdated gitRefUpdated, final RebaseChange rebaseChange,
       final ProjectCache projectCache,
-      final MergeUtil.Factory mergeUtilFactory) {
+      final MergeUtil.Factory mergeUtilFactory,
+      final ChangeIndexer indexer) {
     this.identifiedUserFactory = identifiedUserFactory;
     this.myIdent = myIdent;
     this.patchSetInfoFactory = patchSetInfoFactory;
@@ -71,6 +74,7 @@
     this.rebaseChange = rebaseChange;
     this.projectCache = projectCache;
     this.mergeUtilFactory = mergeUtilFactory;
+    this.indexer = indexer;
   }
 
   public SubmitStrategy create(final SubmitType submitType, final ReviewDb db,
@@ -82,7 +86,7 @@
     final SubmitStrategy.Arguments args =
         new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db, repo,
             rw, inserter, canMergeFlag, alreadyAccepted, destBranch,
-            mergeUtilFactory.create(project));
+            mergeUtilFactory.create(project), indexer);
     switch (submitType) {
       case CHERRY_PICK:
         return new CherryPick(args, patchSetInfoFactory, gitRefUpdated);
@@ -93,7 +97,7 @@
       case MERGE_IF_NECESSARY:
         return new MergeIfNecessary(args);
       case REBASE_IF_NECESSARY:
-        return new RebaseIfNecessary(args, rebaseChange);
+        return new RebaseIfNecessary(args, rebaseChange, myIdent);
       default:
         final String errorMsg = "No submit strategy for: " + submitType;
         log.error(errorMsg);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagCache.java
index 3c64229..dec1768 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagCache.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.server.git;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagMatcher.java
index 7d95db2..b63378f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagMatcher.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.server.git;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
index c57942c..f469059 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSet.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.server.git;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
index d5120e0..d923e51 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.server.git;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
index a57f923..e7b9d31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -14,11 +14,13 @@
 
 package com.google.gerrit.server.git.validators;
 
+import com.google.common.base.CharMatcher;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
@@ -34,6 +36,7 @@
 import com.jcraft.jsch.HostKey;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.FooterKey;
@@ -57,6 +60,9 @@
 
   private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
 
+  private static final String GIT_HOOKS_COMMIT_MSG =
+      "`git rev-parse --git-dir`/hooks/commit-msg";
+
   public interface Factory {
     CommitValidators create(RefControl refControl, SshInfo sshInfo,
         Repository repo);
@@ -65,6 +71,7 @@
   private final PersonIdent gerritIdent;
   private final RefControl refControl;
   private final String canonicalWebUrl;
+  private final String installCommitMsgHookCommand;
   private final SshInfo sshInfo;
   private final Repository repo;
   private final DynamicSet<CommitValidationListener> commitValidationListeners;
@@ -72,12 +79,15 @@
   @Inject
   CommitValidators(@GerritPersonIdent final PersonIdent gerritIdent,
       @CanonicalWebUrl @Nullable final String canonicalWebUrl,
+      @GerritServerConfig final Config config,
       final DynamicSet<CommitValidationListener> commitValidationListeners,
       @Assisted final SshInfo sshInfo,
       @Assisted final Repository repo, @Assisted final RefControl refControl) {
     this.gerritIdent = gerritIdent;
     this.refControl = refControl;
     this.canonicalWebUrl = canonicalWebUrl;
+    this.installCommitMsgHookCommand =
+        config.getString("gerrit", null, "installCommitMsgHookCommand");
     this.sshInfo = sshInfo;
     this.repo = repo;
     this.commitValidationListeners = commitValidationListeners;
@@ -98,7 +108,8 @@
     if (MagicBranch.isMagicBranch(receiveEvent.command.getRefName())
         || ReceiveCommits.NEW_PATCHSET.matcher(
             receiveEvent.command.getRefName()).matches()) {
-      validators.add(new ChangeIdValidator(refControl, canonicalWebUrl, sshInfo));
+      validators.add(new ChangeIdValidator(refControl, canonicalWebUrl,
+          installCommitMsgHookCommand, sshInfo));
     }
     validators.add(new ConfigValidator(refControl, repo));
     validators.add(new PluginCommitValidationListener(commitValidationListeners));
@@ -132,7 +143,8 @@
     if (MagicBranch.isMagicBranch(receiveEvent.command.getRefName())
         || ReceiveCommits.NEW_PATCHSET.matcher(
             receiveEvent.command.getRefName()).matches()) {
-      validators.add(new ChangeIdValidator(refControl, canonicalWebUrl, sshInfo));
+      validators.add(new ChangeIdValidator(refControl, canonicalWebUrl,
+          installCommitMsgHookCommand, sshInfo));
     }
     validators.add(new ConfigValidator(refControl, repo));
     validators.add(new PluginCommitValidationListener(commitValidationListeners));
@@ -153,23 +165,24 @@
   }
 
   public static class ChangeIdValidator implements CommitValidationListener {
-    private final RefControl refControl;
+    private final ProjectControl projectControl;
     private final String canonicalWebUrl;
+    private final String installCommitMsgHookCommand;
     private final SshInfo sshInfo;
+    private final IdentifiedUser user;
 
     public ChangeIdValidator(RefControl refControl, String canonicalWebUrl,
-        SshInfo sshInfo) {
-      this.refControl = refControl;
+        String installCommitMsgHookCommand, SshInfo sshInfo) {
+      this.projectControl = refControl.getProjectControl();
       this.canonicalWebUrl = canonicalWebUrl;
+      this.installCommitMsgHookCommand = installCommitMsgHookCommand;
       this.sshInfo = sshInfo;
+      this.user = (IdentifiedUser) projectControl.getCurrentUser();
     }
 
     @Override
     public List<CommitValidationMessage> onCommitReceived(
         CommitReceivedEvent receiveEvent) throws CommitValidationException {
-
-      final ProjectControl projectControl = refControl.getProjectControl();
-      IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
       final List<String> idList = receiveEvent.commit.getFooterLines(CHANGE_ID);
 
       List<CommitValidationMessage> messages =
@@ -178,8 +191,8 @@
       if (idList.isEmpty()) {
         if (projectControl.getProjectState().isRequireChangeID()) {
           String errMsg = "missing Change-Id in commit message footer";
-          messages.add(getFixedCommitMsgWithChangeId(errMsg, receiveEvent.commit,
-              currentUser, canonicalWebUrl, sshInfo));
+          messages.add(getFixedCommitMsgWithChangeId(
+              errMsg, receiveEvent.commit));
           throw new CommitValidationException(errMsg, messages);
         }
       } else if (idList.size() > 1) {
@@ -190,13 +203,99 @@
         if (!v.matches("^I[0-9a-f]{8,}.*$")) {
           final String errMsg =
               "missing or invalid Change-Id line format in commit message footer";
-          messages.add(getFixedCommitMsgWithChangeId(errMsg, receiveEvent.commit,
-              currentUser, canonicalWebUrl, sshInfo));
+          messages.add(
+              getFixedCommitMsgWithChangeId(errMsg, receiveEvent.commit));
           throw new CommitValidationException(errMsg, messages);
         }
       }
       return Collections.<CommitValidationMessage>emptyList();
     }
+
+    /**
+     * We handle 3 cases:
+     * 1. No change id in the commit message at all.
+     * 2. Change id last in the commit message but missing empty line to create the footer.
+     * 3. There is a change-id somewhere in the commit message, but we ignore it.
+     *
+     * @return The fixed up commit message
+     */
+    private CommitValidationMessage getFixedCommitMsgWithChangeId(
+        final String errMsg, final RevCommit c) {
+      final String changeId = "Change-Id:";
+      StringBuilder sb = new StringBuilder();
+      sb.append("ERROR: ").append(errMsg);
+      sb.append('\n');
+      sb.append("Suggestion for commit message:\n");
+
+      if (c.getFullMessage().indexOf(changeId) == -1) {
+        sb.append(c.getFullMessage());
+        sb.append('\n');
+        sb.append(changeId).append(" I").append(c.name());
+      } else {
+        String lines[] = c.getFullMessage().trim().split("\n");
+        String lastLine = lines.length > 0 ? lines[lines.length - 1] : "";
+
+        if (lastLine.indexOf(changeId) == 0) {
+          for (int i = 0; i < lines.length - 1; i++) {
+            sb.append(lines[i]);
+            sb.append('\n');
+          }
+
+          sb.append('\n');
+          sb.append(lastLine);
+        } else {
+          sb.append(c.getFullMessage());
+          sb.append('\n');
+          sb.append(changeId).append(" I").append(c.name());
+          sb.append('\n');
+          sb.append("Hint: A potential Change-Id was found, but it was not in the ");
+          sb.append("footer (last paragraph) of the commit message.");
+        }
+      }
+      sb.append('\n');
+      sb.append('\n');
+      sb.append("Hint: To automatically insert Change-Id, install the hook:\n");
+      sb.append(getCommitMessageHookInstallationHint()).append('\n');
+      sb.append('\n');
+
+      return new CommitValidationMessage(sb.toString(), false);
+    }
+
+    private String getCommitMessageHookInstallationHint() {
+      if (installCommitMsgHookCommand != null) {
+        return installCommitMsgHookCommand;
+      }
+      final List<HostKey> hostKeys = sshInfo.getHostKeys();
+
+      // If there are no SSH keys, the commit-msg hook must be installed via
+      // HTTP(S)
+      String p = GIT_HOOKS_COMMIT_MSG;
+      if (hostKeys.isEmpty()) {
+        return String.format(
+            "  curl -Lo %s %s/tools/hooks/commit-msg ; chmod +x %s", p,
+            getGerritUrl(canonicalWebUrl), p);
+      }
+
+      // SSH keys exist, so the hook can be installed with scp.
+      String sshHost;
+      int sshPort;
+      String host = hostKeys.get(0).getHost();
+      int c = host.lastIndexOf(':');
+      if (0 <= c) {
+        if (host.startsWith("*:")) {
+          sshHost = getGerritHost(canonicalWebUrl);
+        } else {
+          sshHost = host.substring(0, c);
+        }
+        sshPort = Integer.parseInt(host.substring(c + 1));
+      } else {
+        sshHost = host;
+        sshPort = 22;
+      }
+
+      return String.format("  scp -p -P %d %s@%s:hooks/commit-msg %s",
+          sshPort, user.getUserName(), sshHost, p);
+    }
   }
 
   /**
@@ -448,93 +547,6 @@
   }
 
   /**
-   * We handle 3 cases:
-   * 1. No change id in the commit message at all.
-   * 2. Change id last in the commit message but missing empty line to create the footer.
-   * 3. There is a change-id somewhere in the commit message, but we ignore it.
-   *
-   * @return The fixed up commit message
-   */
-  private static CommitValidationMessage getFixedCommitMsgWithChangeId(final String errMsg,
-      final RevCommit c, final IdentifiedUser currentUser,
-      String canonicalWebUrl, final SshInfo sshInfo) {
-    final String changeId = "Change-Id:";
-    StringBuilder sb = new StringBuilder();
-    sb.append("ERROR: ").append(errMsg);
-    sb.append('\n');
-    sb.append("Suggestion for commit message:\n");
-
-    if (c.getFullMessage().indexOf(changeId) == -1) {
-      sb.append(c.getFullMessage());
-      sb.append('\n');
-      sb.append(changeId).append(" I").append(c.name());
-    } else {
-      String lines[] = c.getFullMessage().trim().split("\n");
-      String lastLine = lines.length > 0 ? lines[lines.length - 1] : "";
-
-      if (lastLine.indexOf(changeId) == 0) {
-        for (int i = 0; i < lines.length - 1; i++) {
-          sb.append(lines[i]);
-          sb.append('\n');
-        }
-
-        sb.append('\n');
-        sb.append(lastLine);
-      } else {
-        sb.append(c.getFullMessage());
-        sb.append('\n');
-        sb.append(changeId).append(" I").append(c.name());
-        sb.append('\n');
-        sb.append("Hint: A potential Change-Id was found, but it was not in the ");
-        sb.append("footer (last paragraph) of the commit message.");
-      }
-    }
-    sb.append('\n');
-    sb.append('\n');
-    sb.append("Hint: To automatically insert Change-Id, install the hook:\n");
-    sb.append(getCommitMessageHookInstallationHint(currentUser,
-        canonicalWebUrl, sshInfo)).append('\n');
-    sb.append('\n');
-
-    return new CommitValidationMessage(sb.toString(), false);
-  }
-
-  private static String getCommitMessageHookInstallationHint(
-      final IdentifiedUser currentUser, String canonicalWebUrl,
-      final SshInfo sshInfo) {
-    final List<HostKey> hostKeys = sshInfo.getHostKeys();
-
-    // If there are no SSH keys, the commit-msg hook must be installed via
-    // HTTP(S)
-    if (hostKeys.isEmpty()) {
-      String p = ".git/hooks/commit-msg";
-      return String.format(
-          "  curl -o %s %s/tools/hooks/commit-msg ; chmod +x %s", p,
-          getGerritUrl(canonicalWebUrl), p);
-    }
-
-    // SSH keys exist, so the hook can be installed with scp.
-    String sshHost;
-    int sshPort;
-    String host = hostKeys.get(0).getHost();
-    int c = host.lastIndexOf(':');
-    if (0 <= c) {
-      if (host.startsWith("*:")) {
-        sshHost = getGerritHost(canonicalWebUrl);
-      } else {
-        sshHost = host.substring(0, c);
-      }
-      sshPort = Integer.parseInt(host.substring(c + 1));
-    } else {
-      sshHost = host;
-      sshPort = 22;
-    }
-
-    return String.format("  scp -p -P %d %s@%s:hooks/commit-msg .git/hooks/",
-        sshPort, currentUser.getUserName(), sshHost);
-  }
-
-  /**
    * Get the Gerrit URL.
    *
    * @return the canonical URL (with any trailing slash removed) if it is
@@ -543,10 +555,7 @@
    */
   private static String getGerritUrl(String canonicalWebUrl) {
     if (canonicalWebUrl != null) {
-      if (canonicalWebUrl.endsWith("/")) {
-        return canonicalWebUrl.substring(0, canonicalWebUrl.lastIndexOf("/"));
-      }
-      return canonicalWebUrl;
+      return CharMatcher.is('/').trimTrailingFrom(canonicalWebUrl);
     } else {
       return "http://" + getGerritHost(canonicalWebUrl);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
index aa3f30e..9da60f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
@@ -43,12 +43,18 @@
 import java.util.Map;
 
 public class AddIncludedGroups implements RestModifyView<GroupResource, Input> {
-  static class Input {
+  public static class Input {
     @DefaultInput
     String _oneGroup;
 
     List<String> groups;
 
+    public static Input fromGroups(List<String> groups) {
+      Input in = new Input();
+      in.groups = groups;
+      return in;
+    }
+
     static Input init(Input in) {
       if (in == null) {
         in = new Input();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
index 7f32a4d..8cda95a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
@@ -46,12 +46,17 @@
 import java.util.List;
 import java.util.Map;
 
-class AddMembers implements RestModifyView<GroupResource, Input> {
-  static class Input {
+public class AddMembers implements RestModifyView<GroupResource, Input> {
+  public static class Input {
     @DefaultInput
     String _oneMember;
 
     List<String> members;
+    public static Input fromMembers(List<String> members) {
+      Input in = new Input();
+      in.members = members;
+      return in;
+    }
 
     static Input init(Input in) {
       if (in == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupResource.java
index 32513a7..54fc787 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupResource.java
@@ -28,7 +28,7 @@
 
   private final GroupControl control;
 
-  GroupResource(GroupControl control) {
+  public GroupResource(GroupControl control) {
     this.control = control;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
index d32c632..28f908c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
@@ -49,7 +49,7 @@
   private boolean recursive;
 
   @Inject
-  ListMembers(GroupCache groupCache,
+  protected ListMembers(GroupCache groupCache,
       GroupDetailFactory.Factory groupDetailFactory,
       AccountInfo.Loader.Factory accountLoaderFactory) {
     this.groupCache = groupCache;
@@ -63,8 +63,19 @@
     if (resource.toAccountGroup() == null) {
       throw new MethodNotAllowedException();
     }
+
+    return apply(resource.getGroupUUID());
+  }
+
+  public List<AccountInfo> apply(AccountGroup group)
+      throws MethodNotAllowedException, OrmException {
+    return apply(group.getGroupUUID());
+  }
+
+  public List<AccountInfo> apply(AccountGroup.UUID groupId)
+      throws MethodNotAllowedException, OrmException {
     final Map<Account.Id, AccountInfo> members =
-        getMembers(resource.getGroupUUID(), new HashSet<AccountGroup.UUID>());
+        getMembers(groupId, new HashSet<AccountGroup.UUID>());
     final List<AccountInfo> memberInfos = Lists.newArrayList(members.values());
     Collections.sort(memberInfos, new Comparator<AccountInfo>() {
       @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
new file mode 100644
index 0000000..261a439
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
@@ -0,0 +1,362 @@
+// 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.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MultiProgressMonitor;
+import com.google.gerrit.server.git.MultiProgressMonitor.Task;
+import com.google.gerrit.server.patch.PatchListLoader;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.io.DisabledOutputStream;
+import org.eclipse.jgit.util.io.NullOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class ChangeBatchIndexer {
+  private static final Logger log =
+      LoggerFactory.getLogger(ChangeBatchIndexer.class);
+
+  public static class Result {
+    private final long elapsedNanos;
+    private final boolean success;
+    private final int done;
+    private final int failed;
+
+    private Result(Stopwatch sw, boolean success, int done, int failed) {
+      this.elapsedNanos = sw.elapsed(TimeUnit.NANOSECONDS);
+      this.success = success;
+      this.done = done;
+      this.failed = failed;
+    }
+
+    public boolean success() {
+      return success;
+    }
+
+    public int doneCount() {
+      return done;
+    }
+
+    public int failedCount() {
+      return failed;
+    }
+
+    public long elapsed(TimeUnit timeUnit) {
+      return timeUnit.convert(elapsedNanos, TimeUnit.NANOSECONDS);
+    }
+  }
+
+  private final SchemaFactory<ReviewDb> schemaFactory;
+  private final GitRepositoryManager repoManager;
+  private final ListeningScheduledExecutorService executor;
+  private final ChangeIndexer.Factory indexerFactory;
+
+  @Inject
+  ChangeBatchIndexer(SchemaFactory<ReviewDb> schemaFactory,
+      GitRepositoryManager repoManager,
+      @IndexExecutor ListeningScheduledExecutorService executor,
+      ChangeIndexer.Factory indexerFactory) {
+    this.schemaFactory = schemaFactory;
+    this.repoManager = repoManager;
+    this.executor = executor;
+    this.indexerFactory = indexerFactory;
+  }
+
+  public Result indexAll(ChangeIndex index, Iterable<Project.NameKey> projects,
+      int numProjects, int numChanges, OutputStream progressOut,
+      OutputStream verboseOut) {
+    if (progressOut == null) {
+      progressOut = NullOutputStream.INSTANCE;
+    }
+    PrintWriter verboseWriter = verboseOut != null ? new PrintWriter(verboseOut)
+        : null;
+
+    Stopwatch sw = new Stopwatch().start();
+    final MultiProgressMonitor mpm =
+        new MultiProgressMonitor(progressOut, "Reindexing changes");
+    final Task projTask = mpm.beginSubTask("projects",
+        numProjects >= 0 ? numProjects : MultiProgressMonitor.UNKNOWN);
+    final Task doneTask = mpm.beginSubTask(null,
+        numChanges >= 0 ? numChanges : MultiProgressMonitor.UNKNOWN);
+    final Task failedTask = mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
+
+    final List<ListenableFuture<?>> futures = Lists.newArrayList();
+    final AtomicBoolean ok = new AtomicBoolean(true);
+
+    for (final Project.NameKey project : projects) {
+      final ListenableFuture<?> future = executor.submit(new ReindexProject(
+          indexerFactory.create(index), project, doneTask, failedTask,
+          verboseWriter));
+      futures.add(future);
+      future.addListener(new Runnable() {
+        @Override
+        public void run() {
+          try {
+            future.get();
+          } catch (InterruptedException e) {
+            fail(project, e);
+          } catch (ExecutionException e) {
+            fail(project, e);
+          } catch (RuntimeException e) {
+            failAndThrow(project, e);
+          } catch (Error e) {
+            failAndThrow(project, e);
+          } finally {
+            projTask.update(1);
+          }
+        }
+
+        private void fail(Project.NameKey project, Throwable t) {
+          log.error("Failed to index project " + project, t);
+          ok.set(false);
+        }
+
+        private void failAndThrow(Project.NameKey project, RuntimeException e) {
+          fail(project, e);
+          throw e;
+        }
+
+        private void failAndThrow(Project.NameKey project, Error e) {
+          fail(project, e);
+          throw e;
+        }
+      }, MoreExecutors.sameThreadExecutor());
+    }
+
+    try {
+      mpm.waitFor(Futures.transform(Futures.successfulAsList(futures),
+          new AsyncFunction<List<?>, Void>() {
+            @Override
+            public ListenableFuture<Void> apply(List<?> input) {
+              mpm.end();
+              return Futures.immediateFuture(null);
+            }
+      }));
+    } catch (ExecutionException e) {
+      log.error("Error in batch indexer", e);
+      ok.set(false);
+    }
+    return new Result(sw, ok.get(), doneTask.getCount(), failedTask.getCount());
+  }
+
+  private class ReindexProject implements Callable<Void> {
+    private final ChangeIndexer indexer;
+    private final Project.NameKey project;
+    private final ListMultimap<ObjectId, ChangeData> byId;
+    private final Task done;
+    private final Task failed;
+    private final PrintWriter verboseWriter;
+    private Repository repo;
+    private RevWalk walk;
+
+    private ReindexProject(ChangeIndexer indexer, Project.NameKey project,
+        Task done, Task failed, PrintWriter verboseWriter) {
+      this.indexer = indexer;
+      this.project = project;
+      this.byId = ArrayListMultimap.create();
+      this.done = done;
+      this.verboseWriter = verboseWriter;
+      this.failed = failed;
+    }
+
+    @Override
+    public Void call() throws Exception {
+      ReviewDb db = schemaFactory.open();
+      try {
+        repo = repoManager.openRepository(project);
+        try {
+          Map<String, Ref> refs = repo.getAllRefs();
+          for (Change c : db.changes().byProject(project)) {
+            Ref r = refs.get(c.currentPatchSetId().toRefName());
+            if (r != null) {
+              byId.put(r.getObjectId(), new ChangeData(c));
+            }
+          }
+          walk();
+        } finally {
+          repo.close();
+        // TODO(dborowitz): Opening all repositories in a live server may be
+        // wasteful; see if we can determine which ones it is safe to close with
+        // RepositoryCache.close(repo).
+        }
+      } finally {
+        db.close();
+      }
+      return null;
+    }
+
+    private void walk() throws Exception {
+      walk = new RevWalk(repo);
+      try {
+        // Walk only refs first to cover as many changes as we can without having
+        // to mark every single change.
+        for (Ref ref : repo.getRefDatabase().getRefs(Constants.R_HEADS).values()) {
+          RevObject o = walk.parseAny(ref.getObjectId());
+          if (o instanceof RevCommit) {
+            walk.markStart((RevCommit) o);
+          }
+        }
+
+        RevCommit bCommit;
+        while ((bCommit = walk.next()) != null && !byId.isEmpty()) {
+          if (byId.containsKey(bCommit)) {
+            getPathsAndIndex(bCommit);
+            byId.removeAll(bCommit);
+          }
+        }
+
+        for (ObjectId id : byId.keySet()) {
+          getPathsAndIndex(walk.parseCommit(id));
+        }
+      } finally {
+        walk.release();
+      }
+    }
+
+    private void getPathsAndIndex(RevCommit bCommit) throws Exception {
+      RevTree bTree = bCommit.getTree();
+      List<ChangeData> cds = Lists.newArrayList(byId.get(bCommit));
+      try {
+        RevTree aTree = aFor(bCommit, walk);
+        DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
+        try {
+          df.setRepository(repo);
+          if (!cds.isEmpty()) {
+            List<String> paths = (aTree != null)
+                ? getPaths(df.scan(aTree, bTree))
+                : Collections.<String>emptyList();
+            Iterator<ChangeData> cdit = cds.iterator();
+            for (ChangeData cd ; cdit.hasNext(); cdit.remove()) {
+              cd = cdit.next();
+              try {
+                cd.setCurrentFilePaths(paths);
+                indexer.indexTask(cd).call();
+                done.update(1);
+                if (verboseWriter != null) {
+                  verboseWriter.println("Reindexed change " + cd.getId());
+                }
+              } catch (Exception e) {
+                fail("Failed to index change " + cd.getId(), true, e);
+              }
+            }
+          }
+        } finally {
+          df.release();
+        }
+      } catch (Exception e) {
+        fail("Failed to index commit " + bCommit.name(), false, e);
+        for (ChangeData cd : cds) {
+          fail("Failed to index change " + cd.getId(), true, null);
+        }
+      }
+    }
+
+    private List<String> getPaths(List<DiffEntry> filenames) {
+      Set<String> paths = Sets.newTreeSet();
+      for (DiffEntry e : filenames) {
+        if (e.getOldPath() != null) {
+          paths.add(e.getOldPath());
+        }
+        if (e.getNewPath() != null) {
+          paths.add(e.getNewPath());
+        }
+      }
+      return ImmutableList.copyOf(paths);
+    }
+
+    private RevTree aFor(RevCommit b, RevWalk walk) throws IOException {
+      switch (b.getParentCount()) {
+        case 0:
+          return walk.parseTree(emptyTree());
+        case 1:
+          RevCommit a = b.getParent(0);
+          walk.parseBody(a);
+          return walk.parseTree(a.getTree());
+        case 2:
+          return PatchListLoader.automerge(repo, walk, b);
+        default:
+          return null;
+      }
+    }
+
+    private ObjectId emptyTree() throws IOException {
+      ObjectInserter oi = repo.newObjectInserter();
+      try {
+        ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
+        oi.flush();
+        return id;
+      } finally {
+        oi.release();
+      }
+    }
+
+    private void fail(String error, boolean failed, Exception e) {
+      if (failed) {
+        this.failed.update(1);
+      }
+
+      if (e != null) {
+        log.warn(error, e);
+      } else {
+        log.warn(error);
+      }
+
+      if (verboseWriter != null) {
+        verboseWriter.println(error);
+      }
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
new file mode 100644
index 0000000..d62df95
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -0,0 +1,276 @@
+// 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.index;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Account;
+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.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.TrackingId;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gwtorm.server.OrmException;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Set;
+
+/**
+ * Fields indexed on change documents.
+ * <p>
+ * Each field corresponds to both a field name supported by
+ * {@link ChangeQueryBuilder} for querying that field, and a method on
+ * {@link ChangeData} used for populating the corresponding document fields in
+ * the secondary index.
+ */
+public class ChangeField {
+  /** Legacy change ID. */
+  public static final FieldDef<ChangeData, Integer> LEGACY_ID =
+      new FieldDef.Single<ChangeData, Integer>("_id",
+          FieldType.INTEGER, true) {
+        @Override
+        public Integer get(ChangeData input, FillArgs args) {
+          return input.getId().get();
+        }
+      };
+
+  /** Newer style Change-Id key. */
+  public static final FieldDef<ChangeData, String> ID =
+      new FieldDef.Single<ChangeData, String>("change_id",
+          FieldType.PREFIX, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change(args.db).getKey().get();
+        }
+      };
+
+  /** Change status string, in the same format as {@code status:}. */
+  public static final FieldDef<ChangeData, String> STATUS =
+      new FieldDef.Single<ChangeData, String>(ChangeQueryBuilder.FIELD_STATUS,
+          FieldType.EXACT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return ChangeStatusPredicate.VALUES.get(
+              input.change(args.db).getStatus());
+        }
+      };
+
+  /** Project containing the change. */
+  public static final FieldDef<ChangeData, String> PROJECT =
+      new FieldDef.Single<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_PROJECT, FieldType.EXACT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change(args.db).getProject().get();
+        }
+      };
+
+  /** Reference (aka branch) the change will submit onto. */
+  public static final FieldDef<ChangeData, String> REF =
+      new FieldDef.Single<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_REF, FieldType.EXACT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change(args.db).getDest().get();
+        }
+      };
+
+  /** Topic, a short annotation on the branch. */
+  public static final FieldDef<ChangeData, String> TOPIC =
+      new FieldDef.Single<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_TOPIC, FieldType.EXACT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change(args.db).getTopic();
+        }
+      };
+
+  /** Last update time since January 1, 1970. */
+  public static final FieldDef<ChangeData, Timestamp> UPDATED =
+      new FieldDef.Single<ChangeData, Timestamp>(
+          "updated", FieldType.TIMESTAMP, true) {
+        @Override
+        public Timestamp get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change(args.db).getLastUpdatedOn();
+        }
+      };
+
+  /** Sort key field, duplicates {@link #UPDATED}. */
+  @Deprecated
+  public static final FieldDef<ChangeData, Long> SORTKEY =
+      new FieldDef.Single<ChangeData, Long>(
+          "sortkey", FieldType.LONG, true) {
+        @Override
+        public Long get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return ChangeUtil.parseSortKey(input.change(args.db).getSortKey());
+        }
+      };
+
+  /** List of filenames modified in the current patch set. */
+  public static final FieldDef<ChangeData, Iterable<String>> FILE =
+      new FieldDef.Repeatable<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_FILE, FieldType.EXACT, false) {
+        @Override
+        public Iterable<String> get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.currentFilePaths(args.db, args.patchListCache);
+        }
+      };
+
+  /** Owner/creator of the change. */
+  public static final FieldDef<ChangeData, Integer> OWNER =
+      new FieldDef.Single<ChangeData, Integer>(
+          ChangeQueryBuilder.FIELD_OWNER, FieldType.INTEGER, false) {
+        @Override
+        public Integer get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change(args.db).getOwner().get();
+        }
+      };
+
+  /** Reviewer(s) associated with the change. */
+  public static final FieldDef<ChangeData, Iterable<Integer>> REVIEWER =
+      new FieldDef.Repeatable<ChangeData, Integer>(
+          ChangeQueryBuilder.FIELD_REVIEWER, FieldType.INTEGER, false) {
+        @Override
+        public Iterable<Integer> get(ChangeData input, FillArgs args)
+            throws OrmException {
+          Set<Integer> r = Sets.newHashSet();
+          for (PatchSetApproval a : input.allApprovals(args.db)) {
+            r.add(a.getAccountId().get());
+          }
+          return r;
+        }
+      };
+
+  /** Commit id of any PatchSet on the change */
+  public static final FieldDef<ChangeData, Iterable<String>> COMMIT =
+      new FieldDef.Repeatable<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_COMMIT, FieldType.PREFIX, false) {
+        @Override
+        public Iterable<String> get(ChangeData input, FillArgs args)
+            throws OrmException {
+          Set<String> revisions = Sets.newHashSet();
+          for (PatchSet ps : input.patches(args.db)) {
+            revisions.add(ps.getRevision().get());
+          }
+          return revisions;
+        }
+      };
+
+  /** Tracking id extracted from a footer. */
+  public static final FieldDef<ChangeData, Iterable<String>> TR =
+      new FieldDef.Repeatable<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_TR, FieldType.EXACT, false) {
+        @Override
+        public Iterable<String> get(ChangeData input, FillArgs args)
+            throws OrmException {
+          Set<String> r = Sets.newHashSet();
+          for (TrackingId id : input.trackingIds(args.db)) {
+            r.add(id.getTrackingId());
+          }
+          return r;
+        }
+      };
+
+  /** List of labels on the current patch set. */
+  public static final FieldDef<ChangeData, Iterable<String>> LABEL =
+      new FieldDef.Repeatable<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_LABEL, FieldType.EXACT, false) {
+        @Override
+        public Iterable<String> get(ChangeData input, FillArgs args)
+            throws OrmException {
+          Set<String> allApprovals = Sets.newHashSet();
+          Set<String> distinctApprovals = Sets.newHashSet();
+          for (PatchSetApproval a : input.currentApprovals(args.db)) {
+            if (a.getValue() != 0) {
+              allApprovals.add(formatLabel(a.getLabel(), a.getValue(),
+                  a.getAccountId()));
+              distinctApprovals.add(formatLabel(a.getLabel(), a.getValue()));
+            }
+          }
+          allApprovals.addAll(distinctApprovals);
+          return allApprovals;
+        }
+      };
+
+  /** Set true if the change has a non-zero label score. */
+  public static final FieldDef<ChangeData, String> REVIEWED =
+      new FieldDef.Single<ChangeData, String>(
+          "reviewed", FieldType.EXACT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          for (PatchSetApproval a : input.currentApprovals(args.db)) {
+            if (a.getValue() != 0) {
+              return "1";
+            }
+          }
+          return null;
+        }
+      };
+
+  public static String formatLabel(String label, int value) {
+    return formatLabel(label, value, null);
+  }
+
+  public static String formatLabel(String label, int value, Account.Id accountId) {
+    return label.toLowerCase() + (value >= 0 ? "+" : "") + value
+        + (accountId != null ? "," + accountId.get() : "");
+  }
+
+  /** Commit message of the current patch set. */
+  public static final FieldDef<ChangeData, String> COMMIT_MESSAGE =
+      new FieldDef.Single<ChangeData, String>(ChangeQueryBuilder.FIELD_MESSAGE,
+          FieldType.FULL_TEXT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args) throws OrmException {
+          try {
+            return input.commitMessage(args.repoManager, args.db);
+          } catch (IOException e) {
+            throw new OrmException(e);
+          }
+        }
+      };
+
+  /** Summary or inline comment. */
+  public static final FieldDef<ChangeData, Iterable<String>> COMMENT =
+      new FieldDef.Repeatable<ChangeData, String>(ChangeQueryBuilder.FIELD_COMMENT,
+          FieldType.FULL_TEXT, false) {
+        @Override
+        public Iterable<String> get(ChangeData input, FillArgs args)
+            throws OrmException {
+          Set<String> r = Sets.newHashSet();
+          for (PatchLineComment c : input.comments(args.db)) {
+            r.add(c.getMessage());
+          }
+          for (ChangeMessage m : input.messages(args.db)) {
+            r.add(m.getMessage());
+          }
+          return r;
+        }
+      };
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
new file mode 100644
index 0000000..e411956
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
@@ -0,0 +1,151 @@
+// 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.index;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+
+import java.io.IOException;
+
+/**
+ * Secondary index implementation for change documents.
+ * <p>
+ * {@link ChangeData} objects are inserted into the index and are queried by
+ * converting special {@link com.google.gerrit.server.query.Predicate} instances
+ * into index-aware predicates that use the index search results as a source.
+ * <p>
+ * Implementations must be thread-safe and should batch inserts/updates where
+ * appropriate.
+ */
+public interface ChangeIndex {
+  /** Instance indicating secondary index is disabled. */
+  public static final ChangeIndex DISABLED = new ChangeIndex() {
+    @Override
+    public Schema<ChangeData> getSchema() {
+      return null;
+    }
+
+    @Override
+    public ListenableFuture<Void> insert(ChangeData cd) throws IOException {
+      return Futures.immediateFuture(null);
+    }
+
+    @Override
+    public ListenableFuture<Void> replace(ChangeData cd) throws IOException {
+      return Futures.immediateFuture(null);
+    }
+
+    @Override
+    public ListenableFuture<Void> delete(ChangeData cd) throws IOException {
+      return Futures.immediateFuture(null);
+    }
+
+    @Override
+    public void deleteAll() throws IOException {
+      // Do nothing.
+    }
+
+    @Override
+    public ChangeDataSource getSource(Predicate<ChangeData> p) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void close() {
+      // Do nothing.
+    }
+
+    @Override
+    public void markReady(boolean ready) {
+      throw new UnsupportedOperationException();
+    }
+  };
+
+  /** @return the schema version used by this index. */
+  public Schema<ChangeData> getSchema();
+
+  /** Close this index. */
+  public void close();
+
+  /**
+   * Insert a change document into the index.
+   * <p>
+   * Results may not be immediately visible to searchers, but should be visible
+   * within a reasonable amount of time.
+   *
+   * @param cd change document with all index fields prepopulated; see
+   *     {@link ChangeData#fillIndexFields}.
+   *
+   * @throws IOException if the change could not be inserted.
+   */
+  public ListenableFuture<Void> insert(ChangeData cd) throws IOException;
+
+  /**
+   * Update a change document in the index.
+   * <p>
+   * Semantically equivalent to deleting the document and reinserting it with
+   * new field values. Results may not be immediately visible to searchers, but
+   * should be visible within a reasonable amount of time.
+   *
+   * @param cd change document with all index fields prepopulated; see
+   *     {@link ChangeData#fillIndexFields}.
+   *
+   * @throws IOException
+   */
+  public ListenableFuture<Void> replace(ChangeData cd) throws IOException;
+
+  /**
+   * Delete a change document from the index.
+   *
+   * @param cd change document.
+   *
+   * @throws IOException
+   */
+  public ListenableFuture<Void> delete(ChangeData cd) throws IOException;
+
+  /**
+   * Delete all change documents from the index.
+   *
+   * @throws IOException
+   */
+  public void deleteAll() throws IOException;
+
+  /**
+   * Convert the given operator predicate into a source searching the index and
+   * returning only the documents matching that predicate.
+   *
+   * @param p the predicate to match. Must be a tree containing only AND, OR,
+   *     or NOT predicates as internal nodes, and {@link IndexPredicate}s as
+   *     leaves.
+   * @return a source of documents matching the predicate.
+   *
+   * @throws QueryParseException if the predicate could not be converted to an
+   *     indexed data source.
+   */
+  public ChangeDataSource getSource(Predicate<ChangeData> p)
+      throws QueryParseException;
+
+  /**
+   * Mark whether this index is up-to-date and ready to serve reads.
+   *
+   * @param ready whether the index is ready
+   * @throws IOException
+   */
+  public void markReady(boolean ready) throws IOException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
new file mode 100644
index 0000000..0aa3fc8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
@@ -0,0 +1,82 @@
+// 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.index;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.query.change.ChangeData;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Helper for (re)indexing a change document.
+ * <p>
+ * Indexing is run in the background, as it may require substantial work to
+ * compute some of the fields and/or update the index.
+ */
+public abstract class ChangeIndexer {
+  public interface Factory {
+    ChangeIndexer create(ChangeIndex index);
+    ChangeIndexer create(IndexCollection indexes);
+  }
+
+  /** Instance indicating secondary index is disabled. */
+  public static final ChangeIndexer DISABLED = new ChangeIndexer(null) {
+    @Override
+    public ListenableFuture<?> index(ChangeData cd) {
+      return Futures.immediateFuture(null);
+    }
+
+    public Callable<Void> indexTask(ChangeData cd) {
+      return new Callable<Void>() {
+        @Override
+        public Void call() {
+          return null;
+        }
+      };
+    }
+  };
+
+  private final ListeningScheduledExecutorService executor;
+
+  protected ChangeIndexer(ListeningScheduledExecutorService executor) {
+    this.executor = executor;
+  }
+
+  /**
+   * Start indexing a change.
+   *
+   * @param change change to index.
+   * @return future for the indexing task.
+   */
+  public ListenableFuture<?> index(Change change) {
+    return index(new ChangeData(change));
+  }
+
+  /**
+   * Start indexing a change.
+   *
+   * @param change change to index.
+   * @param prop propagator to wrap any created runnables in.
+   * @return future for the indexing task.
+   */
+  public ListenableFuture<?> index(ChangeData cd) {
+    return executor.submit(indexTask(cd));
+  }
+
+  public abstract Callable<Void> indexTask(ChangeData cd);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java
new file mode 100644
index 0000000..1de5c99
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java
@@ -0,0 +1,127 @@
+// 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.index;
+
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import com.google.inject.util.Providers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Helper for (re)indexing a change document.
+ * <p>
+ * Indexing is run in the background, as it may require substantial work to
+ * compute some of the fields and/or update the index.
+ */
+public class ChangeIndexerImpl extends ChangeIndexer {
+  private static final Logger log =
+      LoggerFactory.getLogger(ChangeIndexerImpl.class);
+
+  private final IndexCollection indexes;
+  private final ChangeIndex index;
+  private final SchemaFactory<ReviewDb> schemaFactory;
+  private final ThreadLocalRequestContext context;
+
+  @AssistedInject
+  ChangeIndexerImpl(@IndexExecutor ListeningScheduledExecutorService executor,
+      SchemaFactory<ReviewDb> schemaFactory,
+      ThreadLocalRequestContext context,
+      @Assisted ChangeIndex index) {
+    super(executor);
+    this.schemaFactory = schemaFactory;
+    this.context = context;
+    this.index = index;
+    this.indexes = null;
+  }
+
+  @AssistedInject
+  ChangeIndexerImpl(@IndexExecutor ListeningScheduledExecutorService executor,
+      SchemaFactory<ReviewDb> schemaFactory,
+      ThreadLocalRequestContext context,
+      @Assisted IndexCollection indexes) {
+    super(executor);
+    this.schemaFactory = schemaFactory;
+    this.context = context;
+    this.index = null;
+    this.indexes = indexes;
+  }
+
+  @Override
+  public Callable<Void> indexTask(ChangeData cd) {
+    return new Task(cd);
+  }
+
+  private class Task implements Callable<Void> {
+    private final ChangeData cd;
+
+    private Task(ChangeData cd) {
+      this.cd = cd;
+    }
+
+    @Override
+    public Void call() throws Exception {
+      try {
+        final ReviewDb db = schemaFactory.open();
+        try {
+          context.setContext(new RequestContext() {
+            @Override
+            public Provider<ReviewDb> getReviewDbProvider() {
+              return Providers.of(db);
+            }
+
+            @Override
+            public CurrentUser getCurrentUser() {
+              throw new OutOfScopeException("No user during ChangeIndexer");
+            }
+          });
+          if (indexes != null) {
+            for (ChangeIndex i : indexes.getWriteIndexes()) {
+              i.replace(cd); // TODO(dborowitz): Parallelize these
+            }
+          } else {
+            index.replace(cd);
+          }
+          return null;
+        } finally  {
+          context.setContext(null);
+          db.close();
+        }
+      } catch (Exception e) {
+        log.error(String.format(
+            "Failed to index change %d in %s",
+            cd.getId().get(), cd.getChange().getProject().get()), e);
+        throw e;
+      }
+    }
+
+    @Override
+    public String toString() {
+      return "index-change-" + cd.getId().get();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
new file mode 100644
index 0000000..87a4df1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -0,0 +1,103 @@
+// 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.git;
+
+package com.google.gerrit.server.index;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.gerrit.server.query.change.ChangeData;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.util.Arrays;
+import java.util.Map;
+
+/** Secondary index schemas for changes. */
+public class ChangeSchemas {
+  @SuppressWarnings("unchecked")
+  static final Schema<ChangeData> V1 = release(
+        ChangeField.LEGACY_ID,
+        ChangeField.ID,
+        ChangeField.STATUS,
+        ChangeField.PROJECT,
+        ChangeField.REF,
+        ChangeField.TOPIC,
+        ChangeField.UPDATED,
+        ChangeField.SORTKEY,
+        ChangeField.FILE,
+        ChangeField.OWNER,
+        ChangeField.REVIEWER,
+        ChangeField.COMMIT,
+        ChangeField.TR,
+        ChangeField.LABEL,
+        ChangeField.REVIEWED,
+        ChangeField.COMMIT_MESSAGE,
+        ChangeField.COMMENT);
+
+  private static Schema<ChangeData> release(FieldDef<ChangeData, ?>... fields) {
+    return new Schema<ChangeData>(true, Arrays.asList(fields));
+  }
+
+  @SuppressWarnings("unused")
+  private static Schema<ChangeData> developer(FieldDef<ChangeData, ?>... fields) {
+    return new Schema<ChangeData>(false, Arrays.asList(fields));
+  }
+
+  public static final ImmutableMap<Integer, Schema<ChangeData>> ALL;
+
+  public static Schema<ChangeData> get(int version) {
+    Schema<ChangeData> schema = ALL.get(version);
+    checkArgument(schema != null, "Unrecognized schema version: %s", version);
+    return schema;
+  }
+
+  public static Schema<ChangeData> getLatest() {
+    return Iterables.getLast(ALL.values());
+  }
+
+  static {
+    Map<Integer, Schema<ChangeData>> all = Maps.newTreeMap();
+    for (Field f : ChangeSchemas.class.getDeclaredFields()) {
+      if (Modifier.isStatic(f.getModifiers())
+          && Modifier.isFinal(f.getModifiers())
+          && Schema.class.isAssignableFrom(f.getType())) {
+        ParameterizedType t = (ParameterizedType) f.getGenericType();
+        if (t.getActualTypeArguments()[0] == ChangeData.class) {
+          try {
+            @SuppressWarnings("unchecked")
+            Schema<ChangeData> schema = (Schema<ChangeData>) f.get(null);
+            checkArgument(f.getName().startsWith("V"));
+            schema.setVersion(Integer.parseInt(f.getName().substring(1)));
+            all.put(schema.getVersion(), schema);
+          } catch (IllegalArgumentException e) {
+            throw new ExceptionInInitializerError(e);
+          } catch (IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+          }
+        } else {
+          throw new ExceptionInInitializerError(
+              "non-ChangeData schema: " + f);
+        }
+      }
+    }
+    if (all.isEmpty()) {
+      throw new ExceptionInInitializerError("no ChangeSchemas found");
+    }
+    ALL = ImmutableMap.copyOf(all);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
new file mode 100644
index 0000000..f40f534
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
@@ -0,0 +1,116 @@
+// 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.index;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/**
+ * Definition of a field stored in the secondary index.
+ *
+ * @param I input type from which documents are created and search results are
+ *     returned.
+ * @param T type that should be extracted from the input object when converting
+ *     to an index document.
+ */
+public abstract class FieldDef<I, T> {
+  /** Definition of a single (non-repeatable) field. */
+  public static abstract class Single<I, T> extends FieldDef<I, T> {
+    Single(String name, FieldType<T> type, boolean stored) {
+      super(name, type, stored);
+    }
+
+    @Override
+    public final boolean isRepeatable() {
+      return false;
+    }
+  }
+
+  /** Definition of a repeatable field. */
+  public static abstract class Repeatable<I, T>
+      extends FieldDef<I, Iterable<T>> {
+    Repeatable(String name, FieldType<T> type, boolean stored) {
+      super(name, type, stored);
+    }
+
+    @Override
+    public final boolean isRepeatable() {
+      return true;
+    }
+  }
+
+  /** Arguments needed to fill in missing data in the input object. */
+  public static class FillArgs {
+    final Provider<ReviewDb> db;
+    final GitRepositoryManager repoManager;
+    final PatchListCache patchListCache;
+
+    @Inject
+    FillArgs(Provider<ReviewDb> db,
+        GitRepositoryManager repoManager,
+        PatchListCache patchListCache) {
+      this.db = db;
+      this.repoManager = repoManager;
+      this.patchListCache = patchListCache;
+    }
+  }
+
+  private final String name;
+  private final FieldType<?> type;
+  private final boolean stored;
+
+  private FieldDef(String name, FieldType<?> type, boolean stored) {
+    this.name = name;
+    this.type = type;
+    this.stored = stored;
+  }
+
+  /** @return name of the field. */
+  public final String getName() {
+    return name;
+  }
+
+  /**
+   * @return type of the field; for repeatable fields, the inner type, not the
+   *     iterable type.
+   */
+  public final FieldType<?> getType() {
+    return type;
+  }
+
+  /** @return whether the field should be stored in the index. */
+  public final boolean isStored() {
+    return stored;
+  }
+
+  /**
+   * Get the field contents from the input object.
+   *
+   * @param input input object.
+   * @param args arbitrary arguments needed to fill in indexable fields of the
+   *     input object.
+   * @return the field value(s) to index.
+   *
+   * @throws OrmException
+   */
+  public abstract T get(I input, FillArgs args) throws OrmException;
+
+  /** @return whether the field is repeatable. */
+  public abstract boolean isRepeatable();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
new file mode 100644
index 0000000..ded98ea
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
@@ -0,0 +1,60 @@
+// 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.index;
+
+import java.sql.Timestamp;
+
+
+/** Document field types supported by the secondary index system. */
+public class FieldType<T> {
+  /** A single integer-valued field. */
+  public static final FieldType<Integer> INTEGER =
+      new FieldType<Integer>("INTEGER");
+
+  /** A single integer-valued field. */
+  public static final FieldType<Long> LONG =
+      new FieldType<Long>("LONG");
+
+  /** A single date/time-valued field. */
+  public static final FieldType<Timestamp> TIMESTAMP =
+      new FieldType<Timestamp>("TIMESTAMP");
+
+  /** A string field searched using exact-match semantics. */
+  public static final FieldType<String> EXACT =
+      new FieldType<String>("EXACT");
+
+  /** A string field searched using prefix. */
+  public static final FieldType<String> PREFIX =
+      new FieldType<String>("PREFIX");
+
+  /** A string field searched using fuzzy-match semantics. */
+  public static final FieldType<String> FULL_TEXT =
+      new FieldType<String>("FULL_TEXT");
+
+  private final String name;
+
+  private FieldType(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
new file mode 100644
index 0000000..09561208
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
@@ -0,0 +1,109 @@
+// 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.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.Nullable;
+
+/** Dynamic pointers to the index versions used for searching and writing. */
+@Singleton
+public class IndexCollection implements LifecycleListener {
+  private final CopyOnWriteArrayList<ChangeIndex> writeIndexes;
+  private final AtomicReference<ChangeIndex> searchIndex;
+
+  @Inject
+  @VisibleForTesting
+  public IndexCollection() {
+    this.writeIndexes = Lists.newCopyOnWriteArrayList();
+    this.searchIndex = new AtomicReference<ChangeIndex>();
+  }
+
+  /**
+   * @return the current search index version, or null if the secondary index is
+   *     disabled.
+   */
+  @Nullable
+  public ChangeIndex getSearchIndex() {
+    return searchIndex.get();
+  }
+
+  public void setSearchIndex(ChangeIndex index) {
+    searchIndex.set(index);
+  }
+
+  public Collection<ChangeIndex> getWriteIndexes() {
+    return Collections.unmodifiableCollection(writeIndexes);
+  }
+
+  public synchronized void addWriteIndex(ChangeIndex index) {
+    int version = index.getSchema().getVersion();
+    for (ChangeIndex i : writeIndexes) {
+      if (i.getSchema().getVersion() == version) {
+        throw new IllegalArgumentException(
+            "Write index version " + version + " already in list");
+      }
+    }
+    writeIndexes.add(index);
+  }
+
+  public synchronized void removeWriteIndex(int version) {
+    int removeIndex = -1;
+    for (int i = 0; i < writeIndexes.size(); i++) {
+      if (writeIndexes.get(i).getSchema().getVersion() == version) {
+        removeIndex = i;
+        break;
+      }
+    }
+    if (removeIndex >= 0) {
+      writeIndexes.remove(removeIndex);
+    }
+  }
+
+  public ChangeIndex getWriteIndex(int version) {
+    for (ChangeIndex i : writeIndexes) {
+      if (i.getSchema().getVersion() == version) {
+        return i;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public void start() {
+  }
+
+  @Override
+  public void stop() {
+    ChangeIndex read = searchIndex.get();
+    if (read != null) {
+      read.close();
+    }
+    for (ChangeIndex write : writeIndexes) {
+      if (write != read) {
+        write.close();
+      }
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexExecutor.java
new file mode 100644
index 0000000..d794d4e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexExecutor.java
@@ -0,0 +1,31 @@
+// 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.index;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Marker on {@link ListeningScheduledExecutorService} used by secondary
+ * indexing threads.
+ */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface IndexExecutor {
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
new file mode 100644
index 0000000..f46fe7c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -0,0 +1,92 @@
+// 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.index;
+
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.git.WorkQueue.Executor;
+import com.google.gerrit.server.query.change.ChangeQueryRewriter;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Module for non-indexer-specific secondary index setup.
+ * <p>
+ * This module should not be used directly except by specific secondary indexer
+ * implementations (e.g. Lucene).
+ */
+public class IndexModule extends LifecycleModule {
+  public enum IndexType {
+    SQL, LUCENE, SOLR;
+  }
+
+  /** Type of secondary index. */
+  public static IndexType getIndexType(Injector injector) {
+    Config cfg = injector.getInstance(
+        Key.get(Config.class, GerritServerConfig.class));
+    return cfg.getEnum("index", null, "type", IndexType.SQL);
+  }
+
+  private final int threads;
+
+  public IndexModule(int threads) {
+    this.threads = threads;
+  }
+
+  @Override
+  protected void configure() {
+    bind(ChangeQueryRewriter.class).to(IndexRewriteImpl.class);
+    bind(IndexRewriteImpl.BasicRewritesImpl.class);
+    bind(IndexCollection.class);
+    listener().to(IndexCollection.class);
+    install(new FactoryModuleBuilder()
+        .implement(ChangeIndexer.class, ChangeIndexerImpl.class)
+        .build(ChangeIndexer.Factory.class));
+  }
+
+  @Provides
+  @Singleton
+  @IndexExecutor
+  ListeningScheduledExecutorService getIndexExecutor(
+      @GerritServerConfig Config config,
+      WorkQueue workQueue) {
+    int threads = this.threads;
+    if (threads <= 0) {
+      threads = config.getInt("index", null, "threads", 0);
+    }
+    Executor executor;
+    if (threads <= 0) {
+      executor = workQueue.getDefaultQueue();
+    } else {
+      executor = workQueue.createQueue(threads, "index");
+    }
+    return MoreExecutors.listeningDecorator(executor);
+  }
+
+  @Provides
+  ChangeIndexer getChangeIndexer(
+      ChangeIndexer.Factory factory,
+      IndexCollection indexes) {
+    return factory.create(indexes);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java
new file mode 100644
index 0000000..d3b9e95
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java
@@ -0,0 +1,40 @@
+// 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.index;
+
+import com.google.gerrit.server.query.OperatorPredicate;
+
+/** Index-aware predicate that includes a field type annotation. */
+public abstract class IndexPredicate<I> extends OperatorPredicate<I> {
+  private final FieldDef<I, ?> def;
+
+  public IndexPredicate(FieldDef<I, ?> def, String value) {
+    super(def.getName(), value);
+    this.def = def;
+  }
+
+  protected IndexPredicate(FieldDef<I, ?> def, String name, String value) {
+    super(name, value);
+    this.def = def;
+  }
+
+  public FieldDef<I, ?> getField() {
+    return def;
+  }
+
+  public FieldType<?> getType() {
+    return def.getType();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
new file mode 100644
index 0000000..e0c4fe9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
@@ -0,0 +1,273 @@
+// 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.index;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.NotPredicate;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.QueryRewriter;
+import com.google.gerrit.server.query.change.AndSource;
+import com.google.gerrit.server.query.change.BasicChangeRewrites;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryRewriter;
+import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gerrit.server.query.change.OrSource;
+import com.google.gerrit.server.query.change.SqlRewriterImpl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.BitSet;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+/** Rewriter that pushes boolean logic into the secondary index. */
+public class IndexRewriteImpl implements ChangeQueryRewriter {
+  /** Set of all open change statuses. */
+  public static final Set<Change.Status> OPEN_STATUSES;
+
+  /** Set of all closed change statuses. */
+  public static final Set<Change.Status> CLOSED_STATUSES;
+
+  static {
+    EnumSet<Change.Status> open = EnumSet.noneOf(Change.Status.class);
+    EnumSet<Change.Status> closed = EnumSet.noneOf(Change.Status.class);
+    for (Change.Status s : Change.Status.values()) {
+      if (s.isOpen()) {
+        open.add(s);
+      } else {
+        closed.add(s);
+      }
+    }
+    OPEN_STATUSES = Sets.immutableEnumSet(open);
+    CLOSED_STATUSES = Sets.immutableEnumSet(closed);
+  }
+
+  /**
+   * Get the set of statuses that changes matching the given predicate may have.
+   *
+   * @param in predicate
+   * @return the maximal set of statuses that any changes matching the input
+   *     predicates may have, based on examining boolean and
+   *     {@link ChangeStatusPredicate}s.
+   */
+  public static EnumSet<Change.Status> getPossibleStatus(Predicate<ChangeData> in) {
+    EnumSet<Change.Status> s = extractStatus(in);
+    return s != null ? s : EnumSet.allOf(Change.Status.class);
+  }
+
+  private static EnumSet<Change.Status> extractStatus(Predicate<ChangeData> in) {
+    if (in instanceof ChangeStatusPredicate) {
+      return EnumSet.of(((ChangeStatusPredicate) in).getStatus());
+    } else if (in instanceof NotPredicate) {
+      EnumSet<Status> s = extractStatus(in.getChild(0));
+      return s != null ? EnumSet.complementOf(s) : null;
+    } else if (in instanceof OrPredicate) {
+      EnumSet<Change.Status> r = null;
+      int childrenWithStatus = 0;
+      for (int i = 0; i < in.getChildCount(); i++) {
+        EnumSet<Status> c = extractStatus(in.getChild(i));
+        if (c != null) {
+          if (r == null) {
+            r = EnumSet.noneOf(Change.Status.class);
+          }
+          r.addAll(c);
+          childrenWithStatus++;
+        }
+      }
+      if (r != null && childrenWithStatus < in.getChildCount()) {
+        // At least one child supplied a status but another did not.
+        // Assume all statuses for the children that did not feed a
+        // status at this part of the tree. This matches behavior if
+        // the child was used at the root of a query.
+        return EnumSet.allOf(Change.Status.class);
+      }
+      return r;
+    } else if (in instanceof AndPredicate) {
+      EnumSet<Change.Status> r = null;
+      for (int i = 0; i < in.getChildCount(); i++) {
+        EnumSet<Change.Status> c = extractStatus(in.getChild(i));
+        if (c != null) {
+          if (r == null) {
+            r = EnumSet.allOf(Change.Status.class);
+          }
+          r.retainAll(c);
+        }
+      }
+      return r;
+    }
+    return null;
+  }
+
+  private final IndexCollection indexes;
+  private final Provider<ReviewDb> db;
+  private final BasicRewritesImpl basicRewrites;
+
+  @Inject
+  IndexRewriteImpl(IndexCollection indexes,
+      Provider<ReviewDb> db,
+      BasicRewritesImpl basicRewrites) {
+    this.indexes = indexes;
+    this.db = db;
+    this.basicRewrites = basicRewrites;
+  }
+
+  @Override
+  public Predicate<ChangeData> rewrite(Predicate<ChangeData> in) {
+    in = basicRewrites.rewrite(in);
+
+    ChangeIndex index = indexes.getSearchIndex();
+    Predicate<ChangeData> out = rewriteImpl(in, index);
+    if (in == out || out instanceof IndexPredicate) {
+      return query(out, index);
+    } else if (out == null /* cannot rewrite */) {
+      return in;
+    } else {
+      return out;
+    }
+  }
+
+  /**
+   * Rewrite a single predicate subtree.
+   *
+   * @param in predicate to rewrite.
+   * @param index index whose schema determines which fields are indexed.
+   * @return {@code null} if no part of this subtree can be queried in the
+   *     index directly. {@code in} if this subtree and all its children can be
+   *     queried directly in the index. Otherwise, a predicate that is
+   *     semantically equivalent, with some of its subtrees wrapped to query the
+   *     index directly.
+   */
+  private Predicate<ChangeData> rewriteImpl(Predicate<ChangeData> in,
+      ChangeIndex index) {
+    if (isIndexPredicate(in, index)) {
+      return in;
+    } else if (!isRewritePossible(in)) {
+      return null; // magic to indicate "in" cannot be rewritten
+    }
+
+    int n = in.getChildCount();
+    BitSet isIndexed = new BitSet(n);
+    BitSet notIndexed = new BitSet(n);
+    BitSet rewritten = new BitSet(n);
+    List<Predicate<ChangeData>> newChildren = Lists.newArrayListWithCapacity(n);
+    for (int i = 0; i < n; i++) {
+      Predicate<ChangeData> c = in.getChild(i);
+      Predicate<ChangeData> nc = rewriteImpl(c, index);
+      if (nc == c) {
+        isIndexed.set(i);
+        newChildren.add(c);
+      } else if (nc == null /* cannot rewrite c */) {
+        notIndexed.set(i);
+        newChildren.add(c);
+      } else {
+        rewritten.set(i);
+        newChildren.add(nc);
+      }
+    }
+
+    if (isIndexed.cardinality() == n) {
+      return in; // All children are indexed, leave as-is for parent.
+    } else if (notIndexed.cardinality() == n) {
+      return null; // Can't rewrite any children, so cannot rewrite in.
+    } else if (rewritten.cardinality() == n) {
+      return in.copy(newChildren); // All children were rewritten.
+    }
+    return partitionChildren(in, newChildren, isIndexed, index);
+  }
+
+  private boolean isIndexPredicate(Predicate<ChangeData> in, ChangeIndex index) {
+    if (!(in instanceof IndexPredicate)) {
+      return false;
+    }
+    IndexPredicate<ChangeData> p = (IndexPredicate<ChangeData>) in;
+    return index.getSchema().getFields().containsKey(p.getField().getName());
+  }
+
+  private Predicate<ChangeData> partitionChildren(
+      Predicate<ChangeData> in,
+      List<Predicate<ChangeData>> newChildren,
+      BitSet isIndexed,
+      ChangeIndex index) {
+    if (isIndexed.cardinality() == 1) {
+      int i = isIndexed.nextSetBit(0);
+      newChildren.add(0, query(newChildren.remove(i), index));
+      return copy(in, newChildren);
+    }
+
+    // Group all indexed predicates into a wrapped subtree.
+    List<Predicate<ChangeData>> indexed =
+        Lists.newArrayListWithCapacity(isIndexed.cardinality());
+
+    List<Predicate<ChangeData>> all =
+        Lists.newArrayListWithCapacity(
+            newChildren.size() - isIndexed.cardinality() + 1);
+
+    for (int i = 0; i < newChildren.size(); i++) {
+      Predicate<ChangeData> c = newChildren.get(i);
+      if (isIndexed.get(i)) {
+        indexed.add(c);
+      } else {
+        all.add(c);
+      }
+    }
+    all.add(0, query(in.copy(indexed), index));
+    return copy(in, all);
+  }
+
+  private Predicate<ChangeData> copy(
+      Predicate<ChangeData> in,
+      List<Predicate<ChangeData>> all) {
+    if (in instanceof AndPredicate) {
+      return new AndSource(db, all);
+    } else if (in instanceof OrPredicate) {
+      return new OrSource(all);
+    }
+    return in.copy(all);
+  }
+
+  private IndexedChangeQuery query(Predicate<ChangeData> p, ChangeIndex index) {
+    try {
+      return new IndexedChangeQuery(index, p);
+    } catch (QueryParseException e) {
+      throw new IllegalStateException(
+          "Failed to convert " + p + " to index predicate", e);
+    }
+  }
+
+  private static boolean isRewritePossible(Predicate<ChangeData> p) {
+    return p.getChildCount() > 0 && (
+           p instanceof AndPredicate
+        || p instanceof OrPredicate
+        || p instanceof NotPredicate);
+  }
+
+  static class BasicRewritesImpl extends BasicChangeRewrites {
+    private static final QueryRewriter.Definition<ChangeData, BasicRewritesImpl> mydef =
+        new QueryRewriter.Definition<ChangeData, BasicRewritesImpl>(
+            BasicRewritesImpl.class, SqlRewriterImpl.BUILDER);
+    @Inject
+    BasicRewritesImpl(Provider<ReviewDb> db) {
+      super(mydef, db);
+    }
+  }
+}
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
new file mode 100644
index 0000000..175208a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
@@ -0,0 +1,144 @@
+// 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.index;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Wrapper combining an {@link IndexPredicate} together with a
+ * {@link ChangeDataSource} that returns matching results from the index.
+ * <p>
+ * Appropriate to return as the rootmost predicate that can be processed using
+ * the secondary index; such predicates must also implement
+ * {@link ChangeDataSource} to be chosen by the query processor.
+ */
+public class IndexedChangeQuery extends Predicate<ChangeData>
+    implements ChangeDataSource {
+  private final Predicate<ChangeData> pred;
+  private final ChangeDataSource source;
+
+  public IndexedChangeQuery(ChangeIndex index, Predicate<ChangeData> pred)
+      throws QueryParseException {
+    this.pred = pred;
+    this.source = index.getSource(pred);
+  }
+
+  @Override
+  public int getChildCount() {
+    return 1;
+  }
+
+  @Override
+  public Predicate<ChangeData> getChild(int i) {
+    if (i == 0) {
+      return pred;
+    }
+    throw new ArrayIndexOutOfBoundsException(i);
+  }
+
+  @Override
+  public List<Predicate<ChangeData>> getChildren() {
+    return ImmutableList.of(pred);
+  }
+
+  @Override
+  public int getCardinality() {
+    return source.getCardinality();
+  }
+
+  @Override
+  public boolean hasChange() {
+    return source.hasChange();
+  }
+
+  @Override
+  public ResultSet<ChangeData> read() throws OrmException {
+    final ResultSet<ChangeData> rs = source.read();
+    return new ResultSet<ChangeData>() {
+      @Override
+      public Iterator<ChangeData> iterator() {
+        return Iterables.transform(
+            rs,
+            new Function<ChangeData, ChangeData>() {
+              @Override
+              public
+              ChangeData apply(ChangeData input) {
+                input.cacheFromSource(source);
+                return input;
+              }
+            }).iterator();
+      }
+
+      @Override
+      public List<ChangeData> toList() {
+        List<ChangeData> r = rs.toList();
+        for (ChangeData cd : r) {
+          cd.cacheFromSource(source);
+        }
+        return r;
+      }
+
+      @Override
+      public void close() {
+        rs.close();
+      }
+    };
+  }
+
+  @Override
+  public Predicate<ChangeData> copy(
+      Collection<? extends Predicate<ChangeData>> children) {
+    return this;
+  }
+
+  @Override
+  public boolean match(ChangeData cd) throws OrmException {
+    return cd.isFromSource(source) || pred.match(cd);
+  }
+
+  @Override
+  public int getCost() {
+    return pred.getCost();
+  }
+
+  @Override
+  public int hashCode() {
+    return pred.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return other != null
+        && getClass() == other.getClass()
+        && pred.equals(((IndexedChangeQuery) other).pred);
+  }
+
+  @Override
+  public String toString() {
+    return "index(" + source + ")";
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java
new file mode 100644
index 0000000..8c552d8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java
@@ -0,0 +1,32 @@
+// 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.index;
+
+import com.google.gerrit.server.query.change.ChangeQueryRewriter;
+import com.google.gerrit.server.query.change.SqlRewriterImpl;
+import com.google.inject.AbstractModule;
+
+public class NoIndexModule extends AbstractModule {
+  // TODO(dborowitz): This module should go away when the index becomes
+  // obligatory, as should the interfaces that exist only to support the
+  // non-index case.
+
+  @Override
+  protected void configure() {
+    bind(ChangeIndex.class).toInstance(ChangeIndex.DISABLED);
+    bind(ChangeIndexer.class).toInstance(ChangeIndexer.DISABLED);
+    bind(ChangeQueryRewriter.class).to(SqlRewriterImpl.class);
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
similarity index 71%
rename from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
index b2fb901..198c7b0 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
@@ -12,12 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.server.index;
 
-import java.io.IOException;
-import java.io.OutputStream;
-
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public abstract class RegexPredicate<I> extends IndexPredicate<I> {
+  protected RegexPredicate(FieldDef<I, ?> def, String value) {
+    super(def, value);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
new file mode 100644
index 0000000..94d1f9c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
@@ -0,0 +1,57 @@
+// 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.git;
+
+package com.google.gerrit.server.index;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+
+/** Specific version of a secondary index schema. */
+public class Schema<T> {
+  private final boolean release;
+  private final ImmutableMap<String, FieldDef<T, ?>> fields;
+  private int version;
+
+  protected Schema(boolean release, Iterable<FieldDef<T, ?>> fields) {
+    this(0, release, fields);
+  }
+
+  @VisibleForTesting
+  public Schema(int version, boolean release,
+      Iterable<FieldDef<T, ?>> fields) {
+    this.version = version;
+    this.release = release;
+    ImmutableMap.Builder<String, FieldDef<T, ?>> b = ImmutableMap.builder();
+    for (FieldDef<T, ?> f : fields) {
+      b.put(f.getName(), f);
+    }
+    this.fields = b.build();
+  }
+
+  public final boolean isRelease() {
+    return release;
+  }
+
+  public final int getVersion() {
+    return version;
+  }
+
+  public final ImmutableMap<String, FieldDef<T, ?>> getFields() {
+    return fields;
+  }
+
+  void setVersion(int version) {
+    this.version = version;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
similarity index 61%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
index b2fb901..62fba12 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
@@ -12,12 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.server.index;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import java.sql.Timestamp;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public abstract class TimestampRangePredicate<I> extends IndexPredicate<I> {
+  protected TimestampRangePredicate(FieldDef<I, Timestamp> def,
+      String name, String value) {
+    super(def, name, value);
+  }
+
+  public abstract Timestamp getMinTimestamp();
+  public abstract Timestamp getMaxTimestamp();
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index b16ab2a..716a22b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.mail;
 
+import com.google.common.collect.Ordering;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
@@ -29,7 +30,6 @@
 import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -62,9 +62,7 @@
         paths.add(p.getFileName());
       }
     }
-    String[] names = paths.toArray(new String[paths.size()]);
-    Arrays.sort(names);
-    changeData.setCurrentFilePaths(names);
+    changeData.setCurrentFilePaths(Ordering.natural().sortedCopy(paths));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
index ce50002..dc09a9c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -87,6 +87,7 @@
 
     init();
     format();
+    appendText(velocifyFile("Footer.vm"));
     if (shouldSendMessage()) {
       if (fromId != null) {
         final Account fromUser = args.accountCache.get(fromId).getAccount();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 84304b8..69cc947 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -24,8 +24,8 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupMember;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -202,7 +202,7 @@
     }
 
     if (filter != null) {
-      qb.setAllowFile(true);
+      qb.setAllowFileRegex(true);
       Predicate<ChangeData> filterPredicate = qb.parse(filter);
       if (p == null) {
         p = filterPredicate;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java
deleted file mode 100644
index f9a85b9..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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.mail;
-
-import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-/** Send notice to reviewers that a change has been rebased. */
-public class RebasedPatchSetSender extends ReplacePatchSetSender {
-  public static interface Factory {
-    RebasedPatchSetSender create(Change change);
-  }
-
-  @Inject
-  public RebasedPatchSetSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c);
-  }
-
-  @Override
-  protected void formatChange() throws EmailException {
-    appendText(velocifyFile("RebasedPatchSet.vm"));
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
index ff9e6cf..852165c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -171,7 +171,10 @@
     final List<String> headerLines = new ArrayList<String>(m.size() - 1);
     for (int i = 1; i < m.size() - 1; i++) {
       final int b = m.get(i);
-      final int e = m.get(i + 1);
+      int e = m.get(i + 1);
+      if (header[e - 1] == '\n') {
+        e--;
+      }
       headerLines.add(RawParseUtils.decode(Constants.CHARSET, header, b, e));
     }
     return headerLines;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index bb231a5..267ae49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -64,7 +64,7 @@
 import java.util.List;
 import java.util.Map;
 
-class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
+public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
   static final Logger log = LoggerFactory.getLogger(PatchListLoader.class);
 
   private final GitRepositoryManager repoManager;
@@ -241,7 +241,7 @@
     }
   }
 
-  private static RevObject automerge(Repository repo, RevWalk rw, RevCommit b)
+  public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b)
       throws IOException {
     String hash = b.name();
     String refName = GitRepositoryManager.REFS_CACHE_AUTOMERGE
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
similarity index 97%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 9a4b89f..c92f515 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.httpd.rpc.patch;
+package com.google.gerrit.server.patch;
 
 import com.google.gerrit.common.data.CommentDetail;
 import com.google.gerrit.common.data.PatchScript;
@@ -26,11 +26,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.server.FileTypeRegistry;
-import com.google.gerrit.server.patch.IntraLineDiff;
-import com.google.gerrit.server.patch.IntraLineDiffKey;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListEntry;
-import com.google.gerrit.server.patch.Text;
 import com.google.inject.Inject;
 
 import eu.medsea.mimeutil.MimeType;
@@ -216,7 +211,8 @@
     return new PatchScript(change.getKey(), content.getChangeType(),
         content.getOldName(), content.getNewName(), a.fileMode, b.fileMode,
         content.getHeaderLines(), diffPrefs, a.dst, b.dst, edits,
-        a.displayMethod, b.displayMethod, comments, history, hugeFile,
+        a.displayMethod, b.displayMethod, a.mimeType.toString(),
+        b.mimeType.toString(), comments, history, hugeFile,
         intralineDifferenceIsPossible, intralineFailure, intralineTimeout);
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
similarity index 89%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index 797229c..fc1b808 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -12,11 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.httpd.rpc.patch;
+package com.google.gerrit.server.patch;
 
 import com.google.gerrit.common.data.CommentDetail;
 import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.Change;
@@ -32,11 +31,6 @@
 import com.google.gerrit.server.account.AccountInfoCacheFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LargeObjectException;
-import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListEntry;
-import com.google.gerrit.server.patch.PatchListKey;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
@@ -55,13 +49,16 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 import javax.annotation.Nullable;
 
 
-class PatchScriptFactory extends Handler<PatchScript> {
-  interface Factory {
-    PatchScriptFactory create(Patch.Key patchKey,
+public class PatchScriptFactory implements Callable<PatchScript> {
+  public interface Factory {
+    PatchScriptFactory create(
+        ChangeControl control,
+        String fileName,
         @Assisted("patchSetA") PatchSet.Id patchSetA,
         @Assisted("patchSetB") PatchSet.Id patchSetB,
         AccountDiffPreference diffPrefs);
@@ -74,20 +71,17 @@
   private final Provider<PatchScriptBuilder> builderFactory;
   private final PatchListCache patchListCache;
   private final ReviewDb db;
-  private final ChangeControl.Factory changeControlFactory;
   private final AccountInfoCacheFactory.Factory aicFactory;
 
-  private final Patch.Key patchKey;
+  private final String fileName;
   @Nullable
   private final PatchSet.Id psa;
   private final PatchSet.Id psb;
   private final AccountDiffPreference diffPrefs;
 
-  private final PatchSet.Id patchSetId;
   private final Change.Id changeId;
 
   private Change change;
-  private PatchSet patchSet;
   private Project.NameKey projectKey;
   private ChangeControl control;
   private ObjectId aId;
@@ -99,9 +93,9 @@
   PatchScriptFactory(final GitRepositoryManager grm,
       Provider<PatchScriptBuilder> builderFactory,
       final PatchListCache patchListCache, final ReviewDb db,
-      final ChangeControl.Factory changeControlFactory,
       final AccountInfoCacheFactory.Factory aicFactory,
-      @Assisted final Patch.Key patchKey,
+      @Assisted ChangeControl control,
+      @Assisted final String fileName,
       @Assisted("patchSetA") @Nullable final PatchSet.Id patchSetA,
       @Assisted("patchSetB") final PatchSet.Id patchSetB,
       @Assisted final AccountDiffPreference diffPrefs) {
@@ -109,16 +103,15 @@
     this.builderFactory = builderFactory;
     this.patchListCache = patchListCache;
     this.db = db;
-    this.changeControlFactory = changeControlFactory;
+    this.control = control;
     this.aicFactory = aicFactory;
 
-    this.patchKey = patchKey;
+    this.fileName = fileName;
     this.psa = patchSetA;
     this.psb = patchSetB;
     this.diffPrefs = diffPrefs;
 
-    patchSetId = patchKey.getParentKey();
-    changeId = patchSetId.getParentKey();
+    changeId = patchSetB.getParentKey();
   }
 
   @Override
@@ -127,13 +120,8 @@
     validatePatchSetId(psa);
     validatePatchSetId(psb);
 
-    control = changeControlFactory.validateFor(changeId);
     change = control.getChange();
     projectKey = change.getProject();
-    patchSet = db.patchSets().get(patchSetId);
-    if (patchSet == null) {
-      throw new NoSuchChangeException(changeId);
-    }
 
     aId = psa != null ? toObjectId(db, psa) : null;
     bId = toObjectId(db, psb);
@@ -156,7 +144,7 @@
     try {
       final PatchList list = listFor(keyFor(diffPrefs.getIgnoreWhitespace()));
       final PatchScriptBuilder b = newBuilder(list, git);
-      final PatchListEntry content = list.get(patchKey.getFileName());
+      final PatchListEntry content = list.get(fileName);
 
       loadCommentsAndHistory(content.getChangeType(), //
           content.getOldName(), //
@@ -240,7 +228,7 @@
       if (!control.isPatchVisible(ps, db)) {
         continue;
       }
-      String name = patchKey.get();
+      String name = fileName;
       if (psa != null) {
         switch (changeType) {
           case COPIED:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
index e2cac6e..6684bfb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
@@ -18,7 +18,7 @@
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
-import com.google.gerrit.extensions.restapi.PutInput;
+import com.google.gerrit.extensions.restapi.RawInput;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -38,7 +38,7 @@
   static class Input {
     @DefaultInput
     String url;
-    PutInput raw;
+    RawInput raw;
   }
 
   private final PluginLoader loader;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
new file mode 100644
index 0000000..6681d94
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
@@ -0,0 +1,44 @@
+// 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.project;
+
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.server.project.ListBranches.BranchInfo;
+import com.google.inject.TypeLiteral;
+
+public class BranchResource extends ProjectResource {
+  public static final TypeLiteral<RestView<BranchResource>> BRANCH_KIND =
+      new TypeLiteral<RestView<BranchResource>>() {};
+
+  private final BranchInfo branchInfo;
+
+  public BranchResource(ProjectControl control, BranchInfo branchInfo) {
+    super(control);
+    this.branchInfo = branchInfo;
+  }
+
+  public BranchInfo getBranchInfo() {
+    return branchInfo;
+  }
+
+  public Branch.NameKey getBranchKey() {
+    return new Branch.NameKey(getNameKey(), branchInfo.ref);
+  }
+
+  public String getRef() {
+    return branchInfo.ref;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
new file mode 100644
index 0000000..8ae07de
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
@@ -0,0 +1,79 @@
+// 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.project;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.project.ListBranches.BranchInfo;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.Constants;
+
+import java.io.IOException;
+import java.util.List;
+
+public class BranchesCollection implements
+    ChildCollection<ProjectResource, BranchResource>,
+    AcceptsCreate<ProjectResource> {
+  private final DynamicMap<RestView<BranchResource>> views;
+  private final Provider<ListBranches> list;
+  private final CreateBranch.Factory createBranchFactory;
+
+  @Inject
+  BranchesCollection(DynamicMap<RestView<BranchResource>> views,
+      Provider<ListBranches> list, CreateBranch.Factory createBranchFactory) {
+    this.views = views;
+    this.list = list;
+    this.createBranchFactory = createBranchFactory;
+  }
+
+  @Override
+  public RestView<ProjectResource> list() {
+    return list.get();
+  }
+
+  @Override
+  public BranchResource parse(ProjectResource parent, IdString id)
+      throws ResourceNotFoundException, IOException {
+    String branchName = id.get();
+    if (!branchName.startsWith(Constants.R_REFS)
+        && !branchName.equals(Constants.HEAD)) {
+      branchName = Constants.R_HEADS + branchName;
+    }
+    List<BranchInfo> branches = list.get().apply(parent);
+    for (BranchInfo b : branches) {
+      if (branchName.equals(b.ref)) {
+        return new BranchResource(parent.getControl(), b);
+      }
+    }
+    throw new ResourceNotFoundException();
+  }
+
+  @Override
+  public DynamicMap<RestView<BranchResource>> views() {
+    return views;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public CreateBranch create(ProjectResource parent, IdString name) {
+    return createBranchFactory.create(name.get());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 6397b96..d09dcb7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -42,6 +42,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -56,10 +57,12 @@
 
   public static class GenericFactory {
     private final ProjectControl.GenericFactory projectControl;
+    private final Provider<ReviewDb> db;
 
     @Inject
-    GenericFactory(ProjectControl.GenericFactory p) {
+    GenericFactory(ProjectControl.GenericFactory p, Provider<ReviewDb> d) {
       projectControl = p;
+      db = d;
     }
 
     public ChangeControl controlFor(Change change, CurrentUser user)
@@ -69,8 +72,34 @@
         return projectControl.controlFor(projectKey, user).controlFor(change);
       } catch (NoSuchProjectException e) {
         throw new NoSuchChangeException(change.getId(), e);
+      } catch (IOException e) {
+        // TODO: propagate this exception
+        throw new NoSuchChangeException(change.getId(), e);
       }
     }
+
+    public ChangeControl controlFor(Change.Id id, CurrentUser user)
+        throws NoSuchChangeException {
+      final Change change;
+      try {
+        change = db.get().changes().get(id);
+        if (change == null) {
+          throw new NoSuchChangeException(id);
+        }
+      } catch (OrmException e) {
+        throw new NoSuchChangeException(id, e);
+      }
+      return controlFor(change, user);
+    }
+
+    public ChangeControl validateFor(Change.Id id, CurrentUser user)
+        throws NoSuchChangeException, OrmException {
+      ChangeControl c = controlFor(id, user);
+      if (!c.isVisible(db.get())) {
+        throw new NoSuchChangeException(c.getChange().getId());
+      }
+      return c;
+    }
   }
 
   public static class Factory {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java
new file mode 100644
index 0000000..2a386a4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectResource.java
@@ -0,0 +1,42 @@
+// 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.project;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class ChildProjectResource extends ProjectResource {
+  public static final TypeLiteral<RestView<ChildProjectResource>> CHILD_PROJECT_KIND =
+      new TypeLiteral<RestView<ChildProjectResource>>() {};
+
+  private final ProjectControl child;
+
+  ChildProjectResource(ProjectResource project, ProjectControl child) {
+    super(project);
+    this.child = child;
+  }
+
+  public ProjectControl getChild() {
+    return child;
+  }
+
+  public boolean isDirectChild() {
+    ProjectState parent =
+        Iterables.getFirst(child.getProjectState().parents(), null);
+    return parent != null
+        && getNameKey().equals(parent.getProject().getNameKey());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java
new file mode 100644
index 0000000..27f1648
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java
@@ -0,0 +1,67 @@
+// 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.project;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class ChildProjectsCollection implements
+    ChildCollection<ProjectResource, ChildProjectResource> {
+  private final Provider<ListChildProjects> list;
+  private final Provider<ProjectsCollection> projectsCollection;
+  private final DynamicMap<RestView<ChildProjectResource>> views;
+
+  @Inject
+  ChildProjectsCollection(Provider<ListChildProjects> list,
+      Provider<ProjectsCollection> projectsCollection,
+      DynamicMap<RestView<ChildProjectResource>> views) {
+    this.list = list;
+    this.projectsCollection = projectsCollection;
+    this.views = views;
+  }
+
+  @Override
+  public RestView<ProjectResource> list() throws ResourceNotFoundException,
+      AuthException {
+    return list.get();
+  }
+
+  @Override
+  public ChildProjectResource parse(ProjectResource parent, IdString id)
+      throws ResourceNotFoundException, IOException {
+    ProjectResource p =
+        projectsCollection.get().parse(TopLevelResource.INSTANCE, id);
+    for (ProjectState pp : p.getControl().getProjectState().parents()) {
+      if (parent.getNameKey().equals(pp.getProject().getNameKey())) {
+        return new ChildProjectResource(parent, p.getControl());
+      }
+    }
+    throw new ResourceNotFoundException(id);
+  }
+
+  @Override
+  public DynamicMap<RestView<ChildProjectResource>> views() {
+    return views;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
new file mode 100644
index 0000000..0c29ec8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -0,0 +1,236 @@
+// 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.project;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.errors.InvalidRevisionException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.CreateBranch.Input;
+import com.google.gerrit.server.project.ListBranches.BranchInfo;
+import com.google.gerrit.server.util.MagicBranch;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RevisionSyntaxException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class CreateBranch implements RestModifyView<ProjectResource, Input> {
+  private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
+
+  static class Input {
+    String ref;
+
+    @DefaultInput
+    String revision;
+  }
+
+  static interface Factory {
+    CreateBranch create(String ref);
+  }
+
+  private final IdentifiedUser identifiedUser;
+  private final GitRepositoryManager repoManager;
+  private final GitReferenceUpdated referenceUpdated;
+  private final ChangeHooks hooks;
+  private String ref;
+
+  @Inject
+  CreateBranch(IdentifiedUser identifiedUser, GitRepositoryManager repoManager,
+      GitReferenceUpdated referenceUpdated, ChangeHooks hooks,
+      @Assisted String ref) {
+    this.identifiedUser = identifiedUser;
+    this.repoManager = repoManager;
+    this.referenceUpdated = referenceUpdated;
+    this.hooks = hooks;
+    this.ref = ref;
+  }
+
+  @Override
+  public BranchInfo apply(ProjectResource rsrc, Input input)
+      throws BadRequestException, AuthException, ResourceConflictException,
+      IOException {
+    if (input == null) {
+      input = new Input();
+    }
+    if (input.ref != null && !ref.equals(input.ref)) {
+      throw new BadRequestException("ref must match URL");
+    }
+    if (input.revision == null) {
+      input.revision = Constants.HEAD;
+    }
+    while (ref.startsWith("/")) {
+      ref = ref.substring(1);
+    }
+    if (!ref.startsWith(Constants.R_REFS)) {
+      ref = Constants.R_HEADS + ref;
+    }
+    if (!Repository.isValidRefName(ref)) {
+      throw new BadRequestException("invalid branch name \"" + ref + "\"");
+    }
+    if (MagicBranch.isMagicBranch(ref)) {
+      throw new BadRequestException("not allowed to create branches under \""
+          + MagicBranch.getMagicRefNamePrefix(ref) + "\"");
+    }
+
+    final Branch.NameKey name = new Branch.NameKey(rsrc.getNameKey(), ref);
+    final RefControl refControl = rsrc.getControl().controlForRef(name);
+    final Repository repo = repoManager.openRepository(rsrc.getNameKey());
+    try {
+      final ObjectId revid = parseBaseRevision(repo, rsrc.getNameKey(), input.revision);
+      final RevWalk rw = verifyConnected(repo, revid);
+      RevObject object = rw.parseAny(revid);
+
+      if (ref.startsWith(Constants.R_HEADS)) {
+        // Ensure that what we start the branch from is a commit. If we
+        // were given a tag, deference to the commit instead.
+        //
+        try {
+          object = rw.parseCommit(object);
+        } catch (IncorrectObjectTypeException notCommit) {
+          throw new BadRequestException("\"" + input.revision + "\" not a commit");
+        }
+      }
+
+      if (!refControl.canCreate(rw, object)) {
+        throw new AuthException("Cannot create \"" + ref + "\"");
+      }
+
+      try {
+        final RefUpdate u = repo.updateRef(ref);
+        u.setExpectedOldObjectId(ObjectId.zeroId());
+        u.setNewObjectId(object.copy());
+        u.setRefLogIdent(identifiedUser.newRefLogIdent());
+        u.setRefLogMessage("created via REST from " + input.revision, false);
+        final RefUpdate.Result result = u.update(rw);
+        switch (result) {
+          case FAST_FORWARD:
+          case NEW:
+          case NO_CHANGE:
+            referenceUpdated.fire(name.getParentKey(), u);
+            hooks.doRefUpdatedHook(name, u, identifiedUser.getAccount());
+            break;
+          case LOCK_FAILURE:
+            if (repo.getRef(ref) != null) {
+              throw new ResourceConflictException("branch \"" + ref
+                  + "\" already exists");
+            }
+            String refPrefix = getRefPrefix(ref);
+            while (!Constants.R_HEADS.equals(refPrefix)) {
+              if (repo.getRef(refPrefix) != null) {
+                throw new ResourceConflictException("Cannot create branch \""
+                    + ref + "\" since it conflicts with branch \"" + refPrefix
+                    + "\".");
+              }
+              refPrefix = getRefPrefix(refPrefix);
+            }
+          default: {
+            throw new IOException(result.name());
+          }
+        }
+
+        BranchInfo b = new BranchInfo();
+        b.ref = ref;
+        b.revision = revid.getName();
+        b.setCanDelete(refControl.canDelete());
+        return b;
+      } catch (IOException err) {
+        log.error("Cannot create branch \"" + name + "\"", err);
+        throw err;
+      }
+    } catch (InvalidRevisionException e) {
+      throw new BadRequestException("invalid revision \"" + input.revision + "\"");
+    } finally {
+      repo.close();
+    }
+  }
+
+  private static String getRefPrefix(final String refName) {
+    final int i = refName.lastIndexOf('/');
+    if (i > Constants.R_HEADS.length() - 1) {
+      return refName.substring(0, i);
+    }
+    return Constants.R_HEADS;
+  }
+
+  private ObjectId parseBaseRevision(Repository repo,
+      Project.NameKey projectName, String baseRevision)
+      throws InvalidRevisionException {
+    try {
+      final ObjectId revid = repo.resolve(baseRevision);
+      if (revid == null) {
+        throw new InvalidRevisionException();
+      }
+      return revid;
+    } catch (IOException err) {
+      log.error("Cannot resolve \"" + baseRevision + "\" in project \""
+          + projectName.get() + "\"", err);
+      throw new InvalidRevisionException();
+    } catch (RevisionSyntaxException err) {
+      log.error("Invalid revision syntax \"" + baseRevision + "\"", err);
+      throw new InvalidRevisionException();
+    }
+  }
+
+  private RevWalk verifyConnected(final Repository repo, final ObjectId revid)
+      throws InvalidRevisionException {
+    try {
+      final ObjectWalk rw = new ObjectWalk(repo);
+      try {
+        rw.markStart(rw.parseCommit(revid));
+      } catch (IncorrectObjectTypeException err) {
+        throw new InvalidRevisionException();
+      }
+      for (final Ref r : repo.getAllRefs().values()) {
+        try {
+          rw.markUninteresting(rw.parseAny(r.getObjectId()));
+        } catch (MissingObjectException err) {
+          continue;
+        }
+      }
+      rw.checkConnectivity();
+      return rw;
+    } catch (IncorrectObjectTypeException err) {
+      throw new InvalidRevisionException();
+    } catch (MissingObjectException err) {
+      throw new InvalidRevisionException();
+    } catch (IOException err) {
+      log.error("Repository \"" + repo.getDirectory()
+          + "\" may be corrupt; suggest running git fsck", err);
+      throw new InvalidRevisionException();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 5494146..c5217c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -29,12 +29,16 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.group.GroupsCollection;
 import com.google.gerrit.server.project.CreateProject.Input;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+import java.io.IOException;
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.CREATE_PROJECT)
@@ -52,6 +56,7 @@
     InheritableBoolean useSignedOffBy;
     InheritableBoolean useContentMerge;
     InheritableBoolean requireChangeId;
+    String maxObjectSizeLimit;
   }
 
   static interface Factory {
@@ -79,7 +84,7 @@
   @Override
   public Object apply(TopLevelResource resource, Input input)
       throws BadRequestException, UnprocessableEntityException,
-      ProjectCreationFailedException {
+      ProjectCreationFailedException, IOException {
     if (input == null) {
       input = new Input();
     }
@@ -117,6 +122,12 @@
                 input.useContentMerge, InheritableBoolean.INHERIT);
     args.changeIdRequired =
         Objects.firstNonNull(input.requireChangeId, InheritableBoolean.INHERIT);
+    try {
+      args.maxObjectSizeLimit =
+          ProjectConfig.validMaxObjectSizeLimit(input.maxObjectSizeLimit);
+    } catch (ConfigInvalidException e) {
+      throw new BadRequestException(e.getMessage());
+    }
 
     Project p = createProjectFactory.create(args).createProject();
     return Response.created(json.format(p));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
index 7bbd2e7..ea20cea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -35,6 +35,7 @@
   public InheritableBoolean contentMerge;
   public InheritableBoolean changeIdRequired;
   public boolean createEmptyCommit;
+  public String maxObjectSizeLimit;
 
   public CreateProjectArgs() {
     contributorAgreements = InheritableBoolean.INHERIT;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
new file mode 100644
index 0000000..2978c89
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -0,0 +1,108 @@
+// 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.project;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.DeleteBranch.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class DeleteBranch implements RestModifyView<BranchResource, Input>{
+  private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
+
+  static class Input {
+  }
+
+  private final IdentifiedUser identifiedUser;
+  private final GitRepositoryManager repoManager;
+  private final Provider<ReviewDb> dbProvider;
+  private final GitReferenceUpdated referenceUpdated;
+  private final ChangeHooks hooks;
+
+  @Inject
+  DeleteBranch(IdentifiedUser identifiedUser, GitRepositoryManager repoManager,
+      Provider<ReviewDb> dbProvider, GitReferenceUpdated referenceUpdated,
+      ChangeHooks hooks) {
+    this.identifiedUser = identifiedUser;
+    this.repoManager = repoManager;
+    this.dbProvider = dbProvider;
+    this.referenceUpdated = referenceUpdated;
+    this.hooks = hooks;
+  }
+
+  @Override
+  public Object apply(BranchResource rsrc, Input input) throws AuthException,
+      ResourceConflictException, OrmException, IOException {
+    if (!rsrc.getControl().controlForRef(rsrc.getBranchKey()).canDelete()) {
+      throw new AuthException("Cannot delete branch");
+    }
+    if (dbProvider.get().changes().byBranchOpenAll(rsrc.getBranchKey())
+        .iterator().hasNext()) {
+      throw new ResourceConflictException("branch " + rsrc.getBranchKey()
+          + " has open changes");
+    }
+
+    Repository r = repoManager.openRepository(rsrc.getNameKey());
+    try {
+      RefUpdate.Result result;
+      RefUpdate u;
+      try {
+        u = r.updateRef(rsrc.getRef());
+        u.setForceUpdate(true);
+        result = u.delete();
+      } catch (IOException e) {
+        log.error("Cannot delete " + rsrc.getBranchKey(), e);
+        throw e;
+      }
+
+      switch (result) {
+        case NEW:
+        case NO_CHANGE:
+        case FAST_FORWARD:
+        case FORCED:
+          referenceUpdated.fire(rsrc.getNameKey(), u);
+          hooks.doRefUpdatedHook(rsrc.getBranchKey(), u, identifiedUser.getAccount());
+          break;
+
+        case REJECTED_CURRENT_BRANCH:
+          log.warn("Cannot delete " + rsrc.getBranchKey() + ": " + result.name());
+          throw new ResourceConflictException("cannot delete current branch");
+
+        default:
+          log.error("Cannot delete " + rsrc.getBranchKey() + ": " + result.name());
+          throw new ResourceConflictException("cannot delete branch: " + result.name());
+      }
+    } finally {
+      r.close();
+    }
+    return Response.none();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
index d4d923e..ca7f6f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
@@ -18,8 +18,8 @@
 import com.google.gerrit.common.data.GarbageCollectionResult;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.restapi.StreamingResponse;
 import com.google.gerrit.server.git.GarbageCollection;
 import com.google.gerrit.server.project.GarbageCollect.Input;
 import com.google.inject.Inject;
@@ -43,15 +43,10 @@
   }
 
   @Override
-  public StreamingResponse apply(final ProjectResource rsrc, Input input) {
-    return new StreamingResponse() {
+  public BinaryResult apply(final ProjectResource rsrc, Input input) {
+    return new BinaryResult() {
       @Override
-      public String getContentType() {
-        return "text/plain;charset=UTF-8";
-      }
-
-      @Override
-      public void stream(OutputStream out) throws IOException {
+      public void writeTo(OutputStream out) throws IOException {
         PrintWriter writer = new PrintWriter(
             new OutputStreamWriter(out, Charsets.UTF_8)) {
           @Override
@@ -88,6 +83,8 @@
           writer.flush();
         }
       }
-    };
+    }.setContentType("text/plain")
+     .setCharacterEncoding(Charsets.UTF_8.name())
+     .disableGzip();
   }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetBranch.java
similarity index 64%
copy from gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/project/GetBranch.java
index b2fb901..781cf01 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/StreamingResponse.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetBranch.java
@@ -12,12 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.extensions.restapi;
+package com.google.gerrit.server.project;
 
-import java.io.IOException;
-import java.io.OutputStream;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.project.ListBranches.BranchInfo;
 
-public interface StreamingResponse {
-  public String getContentType();
-  public void stream(OutputStream out) throws IOException;
+public class GetBranch implements RestReadView<BranchResource> {
+
+  @Override
+  public BranchInfo apply(BranchResource rsrc) {
+    return rsrc.getBranchInfo();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetChildProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetChildProject.java
new file mode 100644
index 0000000..1659cb7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetChildProject.java
@@ -0,0 +1,43 @@
+// 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.project;
+
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.inject.Inject;
+
+import org.kohsuke.args4j.Option;
+
+public class GetChildProject implements RestReadView<ChildProjectResource> {
+  @Option(name = "--recursive", usage = "to list child projects recursively")
+  private boolean recursive;
+
+  private final ProjectJson json;
+
+  @Inject
+  GetChildProject(ProjectJson json) {
+    this.json = json;
+  }
+
+  @Override
+  public ProjectInfo apply(ChildProjectResource rsrc)
+      throws ResourceNotFoundException {
+    if (recursive || rsrc.isDirectChild()) {
+      return json.format(rsrc.getChild().getProject());
+    }
+    throw new ResourceNotFoundException(rsrc.getName());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
new file mode 100644
index 0000000..b01c757
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -0,0 +1,160 @@
+// 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.project;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ListBranches implements RestReadView<ProjectResource> {
+
+  private final GitRepositoryManager repoManager;
+
+  @Inject
+  public ListBranches(GitRepositoryManager repoManager) {
+    this.repoManager = repoManager;
+  }
+
+  @Override
+  public List<BranchInfo> apply(ProjectResource rsrc)
+      throws ResourceNotFoundException, IOException {
+    List<BranchInfo> branches = Lists.newArrayList();
+
+    BranchInfo headBranch = null;
+    BranchInfo configBranch = null;
+    final Set<String> targets = Sets.newHashSet();
+
+    final Repository db;
+    try {
+      db = repoManager.openRepository(rsrc.getNameKey());
+    } catch (RepositoryNotFoundException noGitRepository) {
+      throw new ResourceNotFoundException();
+    }
+
+    try {
+      final Map<String, Ref> all = db.getAllRefs();
+
+      if (!all.containsKey(Constants.HEAD)) {
+        // The branch pointed to by HEAD doesn't exist yet, so getAllRefs
+        // filtered it out. If we ask for it individually we can find the
+        // underlying target and put it into the map anyway.
+        //
+        try {
+          Ref head = db.getRef(Constants.HEAD);
+          if (head != null) {
+            all.put(Constants.HEAD, head);
+          }
+        } catch (IOException e) {
+          // Ignore the failure reading HEAD.
+        }
+      }
+
+      for (final Ref ref : all.values()) {
+        if (ref.isSymbolic()) {
+          targets.add(ref.getTarget().getName());
+        }
+      }
+
+      for (final Ref ref : all.values()) {
+        if (ref.isSymbolic()) {
+          // A symbolic reference to another branch, instead of
+          // showing the resolved value, show the name it references.
+          //
+          String target = ref.getTarget().getName();
+          RefControl targetRefControl = rsrc.getControl().controlForRef(target);
+          if (!targetRefControl.isVisible()) {
+            continue;
+          }
+          if (target.startsWith(Constants.R_HEADS)) {
+            target = target.substring(Constants.R_HEADS.length());
+          }
+
+          BranchInfo b = new BranchInfo();
+          b.ref = ref.getName();
+          b.revision = target;
+
+          if (Constants.HEAD.equals(ref.getName())) {
+            b.setCanDelete(false);
+            headBranch = b;
+          } else {
+            b.setCanDelete(targetRefControl.canDelete());
+            branches.add(b);
+          }
+          continue;
+        }
+
+        final RefControl refControl = rsrc.getControl().controlForRef(ref.getName());
+        if (refControl.isVisible()) {
+          if (ref.getName().startsWith(Constants.R_HEADS)) {
+            branches.add(createBranchInfo(ref, refControl, targets));
+          } else if (GitRepositoryManager.REF_CONFIG.equals(ref.getName())) {
+            configBranch = createBranchInfo(ref, refControl, targets);
+          }
+        }
+      }
+    } finally {
+      db.close();
+    }
+    Collections.sort(branches, new Comparator<BranchInfo>() {
+      @Override
+      public int compare(final BranchInfo a, final BranchInfo b) {
+        return a.ref.compareTo(b.ref);
+      }
+    });
+    if (configBranch != null) {
+      branches.add(0, configBranch);
+    }
+    if (headBranch != null) {
+      branches.add(0, headBranch);
+    }
+    return branches;
+  }
+
+  private static BranchInfo createBranchInfo(Ref ref, RefControl refControl,
+      Set<String> targets) {
+    BranchInfo b = new BranchInfo();
+    b.ref = ref.getName();
+    if (ref.getObjectId() != null) {
+      b.revision = ref.getObjectId().name();
+    }
+    b.setCanDelete(!targets.contains(ref.getName()) && refControl.canDelete());
+    return b;
+  }
+
+  public static class BranchInfo {
+    public String ref;
+    public String revision;
+    public Boolean canDelete;
+
+    void setCanDelete(boolean canDelete) {
+      this.canDelete = canDelete ? true : null;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
new file mode 100644
index 0000000..d4b84dd
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
@@ -0,0 +1,106 @@
+// 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.project;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.inject.Inject;
+
+import org.kohsuke.args4j.Option;
+
+import java.util.List;
+import java.util.Map;
+
+public class ListChildProjects implements RestReadView<ProjectResource> {
+
+  @Option(name = "--recursive", usage = "to list child projects recursively")
+  private boolean recursive;
+
+  private final ProjectCache projectCache;
+  private final AllProjectsName allProjects;
+  private final ProjectJson json;
+  private final ProjectNode.Factory projectNodeFactory;
+
+  @Inject
+  ListChildProjects(ProjectCache projectCache, AllProjectsName allProjects,
+      ProjectJson json, ProjectNode.Factory projectNodeFactory) {
+    this.projectCache = projectCache;
+    this.allProjects = allProjects;
+    this.json = json;
+    this.projectNodeFactory = projectNodeFactory;
+  }
+
+  @Override
+  public List<ProjectInfo> apply(ProjectResource rsrc) {
+    if (recursive) {
+      return getChildProjectsRecursively(rsrc.getNameKey(),
+          rsrc.getControl().getCurrentUser());
+    } else {
+      return getDirectChildProjects(rsrc.getNameKey());
+    }
+  }
+
+  private List<ProjectInfo> getDirectChildProjects(Project.NameKey parent) {
+    List<ProjectInfo> childProjects = Lists.newArrayList();
+    for (Project.NameKey projectName : projectCache.all()) {
+      ProjectState e = projectCache.get(projectName);
+      if (e == null) {
+        // If we can't get it from the cache, pretend it's not present.
+        continue;
+      }
+      if (parent.equals(e.getProject().getParent(allProjects))) {
+        childProjects.add(json.format(e.getProject()));
+      }
+    }
+    return childProjects;
+  }
+
+  private List<ProjectInfo> getChildProjectsRecursively(Project.NameKey parent,
+      CurrentUser user) {
+    Map<Project.NameKey, ProjectNode> projects = Maps.newHashMap();
+    for (Project.NameKey name : projectCache.all()) {
+      ProjectState p = projectCache.get(name);
+      if (p == null) {
+        // If we can't get it from the cache, pretend it's not present.
+        continue;
+      }
+      projects.put(name, projectNodeFactory.create(p.getProject(),
+          p.controlFor(user).isVisible()));
+    }
+    for (ProjectNode key : projects.values()) {
+      ProjectNode node = projects.get(key.getParentName());
+      if (node != null) {
+        node.addChild(key);
+      }
+    }
+    return getChildProjectsRecursively(projects.get(parent));
+  }
+
+  private List<ProjectInfo> getChildProjectsRecursively(ProjectNode p) {
+    List<ProjectInfo> allChildren = Lists.newArrayList();
+    for (ProjectNode c : p.getChildren()) {
+      if (c.isVisible()) {
+        allChildren.add(json.format(c.getProject()));
+        allChildren.addAll(getChildProjectsRecursively(c));
+      }
+    }
+    return allChildren;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index 1c61d96..82b016c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -14,12 +14,13 @@
 
 package com.google.gerrit.server.project;
 
+import static com.google.gerrit.server.project.BranchResource.BRANCH_KIND;
+import static com.google.gerrit.server.project.ChildProjectResource.CHILD_PROJECT_KIND;
 import static com.google.gerrit.server.project.DashboardResource.DASHBOARD_KIND;
 import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
 
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.RestApiModule;
-import com.google.gerrit.server.project.CreateProject;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 public class Module extends RestApiModule {
@@ -29,6 +30,8 @@
     bind(DashboardsCollection.class);
 
     DynamicMap.mapOf(binder(), PROJECT_KIND);
+    DynamicMap.mapOf(binder(), CHILD_PROJECT_KIND);
+    DynamicMap.mapOf(binder(), BRANCH_KIND);
     DynamicMap.mapOf(binder(), DASHBOARD_KIND);
 
     put(PROJECT_KIND).to(PutProject.class);
@@ -40,12 +43,21 @@
     get(PROJECT_KIND, "parent").to(GetParent.class);
     put(PROJECT_KIND, "parent").to(SetParent.class);
 
+    child(PROJECT_KIND, "children").to(ChildProjectsCollection.class);
+    get(CHILD_PROJECT_KIND).to(GetChildProject.class);
+
     get(PROJECT_KIND, "HEAD").to(GetHead.class);
     put(PROJECT_KIND, "HEAD").to(SetHead.class);
 
     get(PROJECT_KIND, "statistics.git").to(GetStatistics.class);
     post(PROJECT_KIND, "gc").to(GarbageCollect.class);
 
+    child(PROJECT_KIND, "branches").to(BranchesCollection.class);
+    put(BRANCH_KIND).to(PutBranch.class);
+    get(BRANCH_KIND).to(GetBranch.class);
+    delete(BRANCH_KIND).to(DeleteBranch.class);
+    install(new FactoryModuleBuilder().build(CreateBranch.Factory.class));
+
     child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class);
     get(DASHBOARD_KIND).to(GetDashboard.class);
     put(DASHBOARD_KIND).to(SetDashboard.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
index d68725f..29317440 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
@@ -181,6 +181,7 @@
       newProject.setUseSignedOffBy(createProjectArgs.signedOffBy);
       newProject.setUseContentMerge(createProjectArgs.contentMerge);
       newProject.setRequireChangeID(createProjectArgs.changeIdRequired);
+      newProject.setMaxObjectSizeLimit(createProjectArgs.maxObjectSizeLimit);
       if (createProjectArgs.newParent != null) {
         newProject.setParentName(createProjectArgs.newParent.getProject()
             .getNameKey());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
index 697b838..0e5cecb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 
+import java.io.IOException;
 import java.util.Set;
 
 /** Cache of project information, including access rights. */
@@ -28,13 +29,27 @@
    * Get the cached data for a project by its unique name.
    *
    * @param projectName name of the project.
-   * @return the cached data; null if no such project exists.
+   * @return the cached data; null if no such project exists or a error occured.
+   * @see #checkedGet(com.google.gerrit.reviewdb.client.Project.NameKey)
    */
   public ProjectState get(Project.NameKey projectName);
 
+  /**
+   * Get the cached data for a project by its unique name.
+   *
+   * @param projectName name of the project.
+   * @throws IOException when there was an error.
+   * @return the cached data; null if no such project exists.
+   */
+  public ProjectState checkedGet(Project.NameKey projectName)
+      throws IOException;
+
   /** Invalidate the cached information about the given project. */
   public void evict(Project p);
 
+  /** Invalidate the cached information about the given project. */
+  public void evict(Project.NameKey p);
+
   /**
    * Remove information about the given project from the cache. It will no
    * longer be returned from {@link #all()}.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 272c128..8ccbca3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.base.Throwables;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.Sets;
@@ -34,6 +35,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
@@ -101,13 +103,18 @@
     return state;
   }
 
-  /**
-   * Get the cached data for a project by its unique name.
-   *
-   * @param projectName name of the project.
-   * @return the cached data; null if no such project exists.
-   */
+  @Override
   public ProjectState get(final Project.NameKey projectName) {
+     try {
+      return checkedGet(projectName);
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  @Override
+  public ProjectState checkedGet(Project.NameKey projectName)
+      throws IOException {
     if (projectName == null) {
       return null;
     }
@@ -121,18 +128,27 @@
     } catch (ExecutionException e) {
       if (!(e.getCause() instanceof RepositoryNotFoundException)) {
         log.warn(String.format("Cannot read project %s", projectName.get()), e);
+        Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
+        throw new IOException(e);
       }
       return null;
     }
   }
 
-  /** Invalidate the cached information about the given project. */
+  @Override
   public void evict(final Project p) {
     if (p != null) {
       byName.invalidate(p.getNameKey().get());
     }
   }
 
+  /** Invalidate the cached information about the given project. */
+  public void evict(final Project.NameKey p) {
+    if (p != null) {
+      byName.invalidate(p.get());
+    }
+  }
+
   @Override
   public void remove(final Project p) {
     listLock.lock();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 56d4be3..b82c1b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -38,6 +38,7 @@
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
+import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -62,13 +63,25 @@
     }
 
     public ProjectControl controlFor(Project.NameKey nameKey, CurrentUser user)
-        throws NoSuchProjectException {
-      final ProjectState p = projectCache.get(nameKey);
+        throws NoSuchProjectException, IOException {
+      final ProjectState p = projectCache.checkedGet(nameKey);
       if (p == null) {
         throw new NoSuchProjectException(nameKey);
       }
       return p.controlFor(user);
     }
+
+    public ProjectControl validateFor(Project.NameKey nameKey, int need,
+        CurrentUser user) throws NoSuchProjectException, IOException {
+      final ProjectControl c = controlFor(nameKey, user);
+      if ((need & VISIBLE) == VISIBLE && c.isVisible()) {
+        return c;
+      }
+      if ((need & OWNER) == OWNER && c.isOwner()) {
+        return c;
+      }
+      throw new NoSuchProjectException(nameKey);
+    }
   }
 
   public static class Factory {
@@ -204,6 +217,20 @@
         || isOwnerAnyRef());
   }
 
+  public boolean canUpload() {
+    for (SectionMatcher matcher : access()) {
+      AccessSection section = matcher.section;
+      if (section.getName().startsWith("refs/for/")) {
+        Permission permission = section.getPermission(Permission.PUSH);
+        if (permission != null
+            && controlForRef(section.getName()).canPerform(Permission.PUSH)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   /** Can this user see all the refs in this projects? */
   public boolean allRefsAreVisible() {
     return allRefsAreVisibleExcept(Collections.<String> emptySet());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java
index 1c4d7c4..5b4b334 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java
@@ -57,6 +57,10 @@
     return allProjectsName.equals(project.getNameKey());
   }
 
+  public Project getProject() {
+    return project;
+  }
+
   @Override
   public String getDisplayName() {
     return project.getName();
@@ -68,7 +72,7 @@
   }
 
   @Override
-  public SortedSet<? extends TreeNode> getChildren() {
+  public SortedSet<? extends ProjectNode> getChildren() {
     return children;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
index aeca0e8..f4449f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
@@ -25,10 +25,14 @@
 
   private final ProjectControl control;
 
-  ProjectResource(ProjectControl control) {
+  public ProjectResource(ProjectControl control) {
     this.control = control;
   }
 
+  ProjectResource(ProjectResource rsrc) {
+    this.control = rsrc.getControl();
+  }
+
   public String getName() {
     return control.getProject().getName();
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 6f80841..800fa6e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -216,6 +216,10 @@
     return config;
   }
 
+  public long getMaxObjectSizeLimit() {
+    return config.getMaxObjectSizeLimit();
+  }
+
   /** Get the sections that pertain only to this project. */
   List<SectionMatcher> getLocalAccessSections() {
     List<SectionMatcher> sm = localAccessSections;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
index 4c00439..6e9c5d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
@@ -28,6 +28,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import java.io.IOException;
+
 public class ProjectsCollection implements
     RestCollection<TopLevelResource, ProjectResource>,
     AcceptsCreate<TopLevelResource> {
@@ -56,7 +58,7 @@
 
   @Override
   public ProjectResource parse(TopLevelResource parent, IdString id)
-      throws ResourceNotFoundException {
+      throws ResourceNotFoundException, IOException {
     ProjectResource rsrc = _parse(id.get());
     if (rsrc == null) {
       throw new ResourceNotFoundException(id);
@@ -71,8 +73,10 @@
    * @return the project
    * @throws UnprocessableEntityException thrown if the project ID cannot be
    *         resolved or if the project is not visible to the calling user
+   * @throws IOException thrown when there is an error.
    */
-  public ProjectResource parse(String id) throws UnprocessableEntityException {
+  public ProjectResource parse(String id)
+      throws UnprocessableEntityException, IOException {
     ProjectResource rsrc = _parse(id);
     if (rsrc == null) {
       throw new UnprocessableEntityException(String.format(
@@ -81,7 +85,7 @@
     return rsrc;
   }
 
-  private ProjectResource _parse(String id) {
+  private ProjectResource _parse(String id) throws IOException {
     ProjectControl ctl;
     try {
       ctl = controlFactory.controlFor(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutBranch.java
new file mode 100644
index 0000000..2cd5659
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutBranch.java
@@ -0,0 +1,29 @@
+// 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.project;
+
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.project.CreateBranch.Input;
+
+public class PutBranch implements RestModifyView<BranchResource, Input> {
+
+  @Override
+  public Object apply(BranchResource rsrc, Input input)
+      throws ResourceConflictException {
+    throw new ResourceConflictException("Branch \"" + rsrc.getBranchInfo().ref
+        + "\" already exists");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 59b7670..e191436 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -395,7 +395,7 @@
 
   /** The range of permitted values associated with a label permission. */
   public PermissionRange getRange(String permission) {
-    if (Permission.isLabel(permission)) {
+    if (Permission.hasRange(permission)) {
       return toRange(permission, access(permission));
     }
     return null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
index db879de..aeb92d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionSortCache.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.server.project;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ThemeInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ThemeInfo.java
index 8362b572..4fcc4a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ThemeInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ThemeInfo.java
@@ -10,7 +10,7 @@
 // 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.git;
+// limitations under the License.
 
 package com.google.gerrit.server.project;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
index 096e12e..5966dde 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
@@ -45,9 +45,6 @@
         c += p.getCost();
       }
     }
-    if (t.size() < 2) {
-      throw new IllegalArgumentException("Need at least two predicates");
-    }
     children = t;
     cost = c;
   }
@@ -101,7 +98,7 @@
   }
 
   @Override
-  public final String toString() {
+  public String toString() {
     final StringBuilder r = new StringBuilder();
     r.append("(");
     for (int i = 0; i < getChildCount(); i++) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/ObjectIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/ObjectIdPredicate.java
deleted file mode 100644
index bea4da12..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/ObjectIdPredicate.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2009 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.query;
-
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
-import org.eclipse.jgit.lib.ObjectId;
-
-
-/** Predicate for a field of {@link ObjectId}. */
-public abstract class ObjectIdPredicate<T> extends OperatorPredicate<T> {
-  private final AbbreviatedObjectId id;
-
-  public ObjectIdPredicate(final String name, final AbbreviatedObjectId id) {
-    super(name, id.name());
-    this.id = id;
-  }
-
-  public boolean isComplete() {
-    return id.isComplete();
-  }
-
-  public AbbreviatedObjectId abbreviated() {
-    return id;
-  }
-
-  public ObjectId full() {
-    return id.toObjectId();
-  }
-
-  @Override
-  public int hashCode() {
-    return getOperator().hashCode() * 31 + id.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (other instanceof ObjectIdPredicate) {
-      final ObjectIdPredicate<?> p = (ObjectIdPredicate<?>) other;
-      return getOperator().equals(p.getOperator()) && id.equals(p.id);
-    }
-    return false;
-  }
-
-  @Override
-  public String toString() {
-    return getOperator() + ":" + id.name();
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
index 57d21b1..8a0ac68 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
@@ -45,9 +45,6 @@
         c += p.getCost();
       }
     }
-    if (t.size() < 2) {
-      throw new IllegalArgumentException("Need at least two predicates");
-    }
     children = t;
     cost = c;
   }
@@ -101,7 +98,7 @@
   }
 
   @Override
-  public final String toString() {
+  public String toString() {
     final StringBuilder r = new StringBuilder();
     r.append("(");
     for (int i = 0; i < getChildCount(); i++) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
index 2a6bae0..6088d8e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.query;
 
+import com.google.common.collect.Lists;
 import com.google.inject.name.Named;
 
 import java.lang.annotation.Annotation;
@@ -27,7 +28,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Comparator;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -65,19 +66,13 @@
     private final List<RewriteRule<T>> rewriteRules;
 
     public Definition(Class<R> clazz, QueryBuilder<T> qb) {
-      rewriteRules = new ArrayList<RewriteRule<T>>();
+      rewriteRules = Lists.newArrayList();
 
       Class<?> c = clazz;
       while (c != QueryRewriter.class) {
-        final Method[] declared = c.getDeclaredMethods();
-        Arrays.sort(declared, new Comparator<Method>() {
-          @Override
-          public int compare(Method o1, Method o2) {
-            return o1.getName().compareTo(o2.getName());
-          }
-        });
+        Method[] declared = c.getDeclaredMethods();
         for (Method m : declared) {
-          final Rewrite rp = m.getAnnotation(Rewrite.class);
+          Rewrite rp = m.getAnnotation(Rewrite.class);
           if ((m.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT
               && (m.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC
               && rp != null) {
@@ -86,6 +81,7 @@
         }
         c = c.getSuperclass();
       }
+      Collections.sort(rewriteRules);
     }
   }
 
@@ -120,13 +116,22 @@
     return Predicate.not(that);
   }
 
+  protected Predicate<T> preRewrite(Predicate<T> in) {
+    return in;
+  }
+
   /**
    * Apply rewrites to a graph until it stops changing.
    *
    * @param in the graph to rewrite.
    * @return the rewritten graph.
    */
-  public Predicate<T> rewrite(Predicate<T> in) {
+  public final Predicate<T> rewrite(Predicate<T> in) {
+    in = preRewrite(in);
+    return rewriteImpl(in);
+  }
+
+  private Predicate<T> rewriteImpl(Predicate<T> in) {
     Predicate<T> old;
     do {
       old = in;
@@ -135,7 +140,7 @@
       if (old.equals(in) && in.getChildCount() > 0) {
         List<Predicate<T>> n = new ArrayList<Predicate<T>>(in.getChildCount());
         for (Predicate<T> p : in.getChildren()) {
-          n.add(rewrite(p));
+          n.add(rewriteImpl(p));
         }
         n = removeDuplicates(n);
         if (n.size() == 1 && (isAND(in) || isOR(in))) {
@@ -150,17 +155,17 @@
   }
 
   protected Predicate<T> replaceGenericNodes(final Predicate<T> in) {
-    if (in.getClass() == NotPredicate.class) {
+    if (in instanceof NotPredicate) {
       return not(replaceGenericNodes(in.getChild(0)));
 
-    } else if (in.getClass() == AndPredicate.class) {
+    } else if (in instanceof AndPredicate) {
       List<Predicate<T>> n = new ArrayList<Predicate<T>>(in.getChildCount());
       for (Predicate<T> c : in.getChildren()) {
         n.add(replaceGenericNodes(c));
       }
       return and(n);
 
-    } else if (in.getClass() == OrPredicate.class) {
+    } else if (in instanceof OrPredicate) {
       List<Predicate<T>> n = new ArrayList<Predicate<T>>(in.getChildCount());
       for (Predicate<T> c : in.getChildren()) {
         n.add(replaceGenericNodes(c));
@@ -331,7 +336,7 @@
   }
 
   /** Applies a rewrite rule to a Predicate. */
-  protected interface RewriteRule<T> {
+  protected interface RewriteRule<T> extends Comparable<RewriteRule<T>> {
     /**
      * Apply a rewrite rule to the Predicate.
      *
@@ -454,6 +459,15 @@
       final String msg = "Cannot apply " + method.getName();
       return new IllegalArgumentException(msg, e);
     }
+
+    @Override
+    public int compareTo(RewriteRule<T> in) {
+      if (in instanceof MethodRewrite) {
+        return method.getName().compareTo(
+            ((MethodRewrite<T>) in).method.getName());
+      }
+      return 1;
+    }
   }
 
   private static <T> Predicate<T> removeDuplicates(Predicate<T> in) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
index d0c9e4c..5f457c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
@@ -20,31 +20,42 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.TimestampRangePredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-class AgePredicate extends OperatorPredicate<ChangeData> {
+import java.sql.Timestamp;
+
+public class AgePredicate extends TimestampRangePredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
   private final long cut;
 
   AgePredicate(Provider<ReviewDb> dbProvider, String value) {
-    super(ChangeQueryBuilder.FIELD_AGE, value);
+    super(ChangeField.UPDATED, ChangeQueryBuilder.FIELD_AGE, value);
     this.dbProvider = dbProvider;
 
     long s = ConfigUtil.getTimeUnit(getValue(), 0, SECONDS);
     long ms = MILLISECONDS.convert(s, SECONDS);
-    this.cut = (System.currentTimeMillis() - ms) + 1;
+    this.cut = System.currentTimeMillis() - ms;
+  }
+
+  public Timestamp getMinTimestamp() {
+    return new Timestamp(0);
+  }
+
+  public Timestamp getMaxTimestamp() {
+    return new Timestamp(cut);
   }
 
   long getCut() {
-    return cut;
+    return cut + 1;
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
     Change change = object.change(dbProvider);
-    return change != null && change.getLastUpdatedOn().getTime() < cut;
+    return change != null && change.getLastUpdatedOn().getTime() <= cut;
   }
 
   @Override
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 7f726a7..0555fc7 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
@@ -34,7 +34,8 @@
 import java.util.Comparator;
 import java.util.List;
 
-class AndSource extends AndPredicate<ChangeData> implements ChangeDataSource {
+public class AndSource extends AndPredicate<ChangeData>
+    implements ChangeDataSource {
   private static final Comparator<Predicate<ChangeData>> CMP =
       new Comparator<Predicate<ChangeData>>() {
         @Override
@@ -75,7 +76,8 @@
   private final Provider<ReviewDb> db;
   private int cardinality = -1;
 
-  AndSource(Provider<ReviewDb> db, Collection<? extends Predicate<ChangeData>> that) {
+  public AndSource(Provider<ReviewDb> db,
+      Collection<? extends Predicate<ChangeData>> that) {
     super(sort(that));
     this.db = db;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
new file mode 100644
index 0000000..1d2c9d2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
@@ -0,0 +1,109 @@
+// 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.query.change;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.query.IntPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryRewriter;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+
+public abstract class BasicChangeRewrites extends QueryRewriter<ChangeData> {
+  protected static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
+      new ChangeQueryBuilder.Arguments( //
+          new InvalidProvider<ReviewDb>(), //
+          new InvalidProvider<ChangeQueryRewriter>(), //
+          null, null, null, null, null, //
+          null, null, null, null, null), null);
+
+  protected final Provider<ReviewDb> dbProvider;
+
+  protected BasicChangeRewrites(
+      Definition<ChangeData, ? extends QueryRewriter<ChangeData>> def,
+      Provider<ReviewDb> dbProvider) {
+    super(def);
+    this.dbProvider = dbProvider;
+  }
+
+  @Rewrite("-status:open")
+  @NoCostComputation
+  public Predicate<ChangeData> r00_notOpen() {
+    return ChangeStatusPredicate.closed(dbProvider);
+  }
+
+  @Rewrite("-status:closed")
+  @NoCostComputation
+  public Predicate<ChangeData> r00_notClosed() {
+    return ChangeStatusPredicate.open(dbProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  @NoCostComputation
+  @Rewrite("-status:merged")
+  public Predicate<ChangeData> r00_notMerged() {
+    return or(ChangeStatusPredicate.open(dbProvider),
+        new ChangeStatusPredicate(dbProvider, Change.Status.ABANDONED));
+  }
+
+  @SuppressWarnings("unchecked")
+  @NoCostComputation
+  @Rewrite("-status:abandoned")
+  public Predicate<ChangeData> r00_notAbandoned() {
+    return or(ChangeStatusPredicate.open(dbProvider),
+        new ChangeStatusPredicate(dbProvider, Change.Status.MERGED));
+  }
+
+  @SuppressWarnings("unchecked")
+  @NoCostComputation
+  @Rewrite("sortkey_before:z A=(age:*)")
+  public Predicate<ChangeData> r00_ageToSortKey(@Named("A") AgePredicate a) {
+    String cut = ChangeUtil.sortKey(a.getCut(), Integer.MAX_VALUE);
+    return and(new SortKeyPredicate.Before(dbProvider, cut), a);
+  }
+
+  @NoCostComputation
+  @Rewrite("A=(limit:*) B=(limit:*)")
+  public Predicate<ChangeData> r00_smallestLimit(
+      @Named("A") IntPredicate<ChangeData> a,
+      @Named("B") IntPredicate<ChangeData> b) {
+    return a.intValue() <= b.intValue() ? a : b;
+  }
+
+  @NoCostComputation
+  @Rewrite("A=(sortkey_before:*) B=(sortkey_before:*)")
+  public Predicate<ChangeData> r00_oldestSortKey(
+      @Named("A") SortKeyPredicate.Before a,
+      @Named("B") SortKeyPredicate.Before b) {
+    return a.getValue().compareTo(b.getValue()) <= 0 ? a : b;
+  }
+
+  @NoCostComputation
+  @Rewrite("A=(sortkey_after:*) B=(sortkey_after:*)")
+  public Predicate<ChangeData> r00_newestSortKey(
+      @Named("A") SortKeyPredicate.After a, @Named("B") SortKeyPredicate.After b) {
+    return a.getValue().compareTo(b.getValue()) >= 0 ? a : b;
+  }
+
+  private static final class InvalidProvider<T> implements Provider<T> {
+    @Override
+    public T get() {
+      throw new OutOfScopeException("Not available at init");
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
deleted file mode 100644
index e5b4143..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2010 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.query.change;
-
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
-
-class BranchPredicate extends OperatorPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
-
-  BranchPredicate(Provider<ReviewDb> dbProvider, String branch) {
-    super(ChangeQueryBuilder.FIELD_BRANCH, branch.startsWith(Branch.R_HEADS)
-        ? branch : Branch.R_HEADS + branch);
-    this.dbProvider = dbProvider;
-  }
-
-  @Override
-  public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
-    if (change == null) {
-      return false;
-    }
-    return change.getDest().get().startsWith(Branch.R_HEADS)
-        && getValue().equals(change.getDest().get());
-  }
-
-  @Override
-  public int getCost() {
-    return 1;
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 506fabc..ff44c73 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -51,7 +51,6 @@
 import java.io.IOException;
 import java.sql.Timestamp;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -87,6 +86,13 @@
     }
   }
 
+  public static void ensureAllPatchSetsLoaded(Provider<ReviewDb> db,
+      List<ChangeData> changes) throws OrmException {
+    for (ChangeData cd : changes) {
+      cd.patches(db);
+    }
+  }
+
   public static void ensureCurrentPatchSetLoaded(
       Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
     Map<PatchSet.Id, ChangeData> missing = Maps.newHashMap();
@@ -126,6 +132,7 @@
   }
 
   private final Change.Id legacyId;
+  private ChangeDataSource returnedBySource;
   private Change change;
   private String commitMessage;
   private PatchSet currentPatchSet;
@@ -134,13 +141,14 @@
   private ListMultimap<PatchSet.Id, PatchSetApproval> limitedApprovals;
   private ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals;
   private List<PatchSetApproval> currentApprovals;
-  private String[] currentFiles;
+  private List<String> currentFiles;
   private Collection<PatchLineComment> comments;
   private Collection<TrackingId> trackingIds;
   private CurrentUser visibleTo;
   private ChangeControl changeControl;
   private List<ChangeMessage> messages;
   private List<SubmitRecord> submitRecords;
+  private boolean patchesLoaded;
 
   public ChangeData(final Change.Id id) {
     legacyId = id;
@@ -157,6 +165,14 @@
     changeControl = c;
   }
 
+  public boolean isFromSource(ChangeDataSource s) {
+    return s == returnedBySource;
+  }
+
+  public void cacheFromSource(ChangeDataSource s) {
+    returnedBySource = s;
+  }
+
   public void limitToPatchSets(Collection<PatchSet.Id> ids) {
     limitedIds = Sets.newLinkedHashSetWithExpectedSize(ids.size());
     for (PatchSet.Id id : ids) {
@@ -172,11 +188,11 @@
     return limitedIds;
   }
 
-  public void setCurrentFilePaths(String[] filePaths) {
+  public void setCurrentFilePaths(List<String> filePaths) {
     currentFiles = filePaths;
   }
 
-  public String[] currentFilePaths(Provider<ReviewDb> db,
+  public List<String> currentFilePaths(Provider<ReviewDb> db,
       PatchListCache cache) throws OrmException {
     if (currentFiles == null) {
       Change c = change(db);
@@ -192,7 +208,7 @@
       try {
         p = cache.get(c, ps);
       } catch (PatchListNotAvailableException e) {
-        currentFiles = new String[0];
+        currentFiles = Collections.emptyList();
         return currentFiles;
       }
 
@@ -218,8 +234,8 @@
             break;
         }
       }
-      currentFiles = r.toArray(new String[r.size()]);
-      Arrays.sort(currentFiles);
+      Collections.sort(r);
+      currentFiles = Collections.unmodifiableList(r);
     }
     return currentFiles;
   }
@@ -321,7 +337,7 @@
    */
   public Collection<PatchSet> patches(Provider<ReviewDb> db)
       throws OrmException {
-    if (patches == null) {
+    if (patches == null || !patchesLoaded) {
       if (limitedIds != null) {
         patches = Lists.newArrayList();
         for (PatchSet ps : db.get().patchSets().byChange(legacyId)) {
@@ -332,6 +348,7 @@
       } else {
         patches = db.get().patchSets().byChange(legacyId).toList();
       }
+      patchesLoaded = true;
     }
     return patches;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
index 7116aa9..457d657 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
@@ -16,17 +16,18 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Provider;
 
-class ChangeIdPredicate extends OperatorPredicate<ChangeData> implements
+class ChangeIdPredicate extends IndexPredicate<ChangeData> implements
     ChangeDataSource {
   private final Provider<ReviewDb> dbProvider;
 
   ChangeIdPredicate(Provider<ReviewDb> dbProvider, String id) {
-    super(ChangeQueryBuilder.FIELD_CHANGE, id);
+    super(ChangeField.ID, ChangeQueryBuilder.FIELD_CHANGE, id);
     this.dbProvider = dbProvider;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index e74172e..1104599 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -14,10 +14,12 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RevId;
@@ -30,6 +32,8 @@
 import com.google.gerrit.server.account.GroupBackends;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
@@ -49,6 +53,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
 
@@ -76,6 +81,7 @@
   public static final String FIELD_AGE = "age";
   public static final String FIELD_BRANCH = "branch";
   public static final String FIELD_CHANGE = "change";
+  public static final String FIELD_COMMENT = "comment";
   public static final String FIELD_COMMIT = "commit";
   public static final String FIELD_DRAFTBY = "draftby";
   public static final String FIELD_FILE = "file";
@@ -97,11 +103,16 @@
   public static final String FIELD_VISIBLETO = "visibleto";
   public static final String FIELD_WATCHEDBY = "watchedby";
 
+  public static final String ARG_ID_USER = "user";
+  public static final String ARG_ID_GROUP = "group";
+
+
   private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> mydef =
       new QueryBuilder.Definition<ChangeData, ChangeQueryBuilder>(
           ChangeQueryBuilder.class);
 
-  static class Arguments {
+  @VisibleForTesting
+  public static class Arguments {
     final Provider<ReviewDb> dbProvider;
     final Provider<ChangeQueryRewriter> rewriter;
     final IdentifiedUser.GenericFactory userFactory;
@@ -113,9 +124,11 @@
     final PatchListCache patchListCache;
     final GitRepositoryManager repoManager;
     final ProjectCache projectCache;
+    final IndexCollection indexes;
 
     @Inject
-    Arguments(Provider<ReviewDb> dbProvider,
+    @VisibleForTesting
+    public Arguments(Provider<ReviewDb> dbProvider,
         Provider<ChangeQueryRewriter> rewriter,
         IdentifiedUser.GenericFactory userFactory,
         CapabilityControl.Factory capabilityControlFactory,
@@ -125,7 +138,8 @@
         AllProjectsName allProjectsName,
         PatchListCache patchListCache,
         GitRepositoryManager repoManager,
-        ProjectCache projectCache) {
+        ProjectCache projectCache,
+        IndexCollection indexes) {
       this.dbProvider = dbProvider;
       this.rewriter = rewriter;
       this.userFactory = userFactory;
@@ -137,6 +151,7 @@
       this.patchListCache = patchListCache;
       this.repoManager = repoManager;
       this.projectCache = projectCache;
+      this.indexes = indexes;
     }
   }
 
@@ -146,17 +161,26 @@
 
   private final Arguments args;
   private final CurrentUser currentUser;
-  private boolean allowsFile;
+  private boolean allowFileRegex;
 
   @Inject
-  ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
+  public ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
     super(mydef);
     this.args = args;
     this.currentUser = currentUser;
   }
 
-  public void setAllowFile(boolean on) {
-    allowsFile = on;
+  @VisibleForTesting
+  protected ChangeQueryBuilder(
+      QueryBuilder.Definition<ChangeData, ? extends ChangeQueryBuilder> def,
+      Arguments args, CurrentUser currentUser) {
+    super(def);
+    this.args = args;
+    this.currentUser = currentUser;
+  }
+
+  public void setAllowFileRegex(boolean on) {
+    allowFileRegex = on;
   }
 
   @Operator
@@ -181,6 +205,15 @@
   }
 
   @Operator
+  public Predicate<ChangeData> comment(String value) throws QueryParseException {
+    ChangeIndex index = args.indexes.getSearchIndex();
+    if (index == null) {
+      throw error("secondary index must be enabled for comment:" + value);
+    }
+    return new CommentPredicate(args.dbProvider, index, value);
+  }
+
+  @Operator
   public Predicate<ChangeData> status(String statusName) {
     if ("open".equals(statusName)) {
       return status_open();
@@ -220,7 +253,7 @@
     }
 
     if ("watched".equalsIgnoreCase(value)) {
-      return new IsWatchedByPredicate(args, currentUser);
+      return new IsWatchedByPredicate(args, currentUser, false);
     }
 
     if ("visible".equalsIgnoreCase(value)) {
@@ -264,8 +297,14 @@
   @Operator
   public Predicate<ChangeData> branch(String name) {
     if (name.startsWith("^"))
-      return new RegexBranchPredicate(args.dbProvider, name);
-    return new BranchPredicate(args.dbProvider, name);
+      return ref("^" + branchToRef(name.substring(1)));
+    return ref(branchToRef(name));
+  }
+
+  private static String branchToRef(String name) {
+    if (!name.startsWith(Branch.R_HEADS))
+      return Branch.R_HEADS + name;
+    return name;
   }
 
   @Operator
@@ -284,27 +323,82 @@
 
   @Operator
   public Predicate<ChangeData> file(String file) throws QueryParseException {
-    if (!allowsFile) {
-      throw error("operator not permitted here: file:" + file);
-    }
-
     if (file.startsWith("^")) {
-      return new RegexFilePredicate(args.dbProvider, args.patchListCache, file);
+      if (allowFileRegex || args.indexes.getSearchIndex() != null) {
+        return new RegexFilePredicate(args.dbProvider, args.patchListCache, file);
+      } else {
+        throw error("secondary index must be enabled for file:" + file);
+      }
+    } else {
+      if (args.indexes.getSearchIndex() == null) {
+        throw error("secondary index must be enabled for file:" + file);
+      }
+      return new EqualsFilePredicate(args.dbProvider, args.patchListCache, file);
     }
-
-    throw new IllegalArgumentException();
   }
 
   @Operator
-  public Predicate<ChangeData> label(String name) {
+  public Predicate<ChangeData> label(String name) throws QueryParseException,
+      OrmException {
+    Set<Account.Id> accounts = null;
+    AccountGroup.UUID group = null;
+
+    // Parse for:
+    // label:CodeReview=1,user=jsmith or
+    // label:CodeReview=1,jsmith or
+    // label:CodeReview=1,group=android_approvers or
+    // label:CodeReview=1,android_approvers
+    //  user/groups without a label will first attempt to match user
+    String[] splitReviewer = name.split(",", 2);
+    name = splitReviewer[0];        // remove all but the vote piece, e.g.'CodeReview=1'
+
+    if (splitReviewer.length == 2) {
+      // process the user/group piece
+      PredicateArgs lblArgs = new PredicateArgs(splitReviewer[1]);
+
+      for (Map.Entry<String, String> pair : lblArgs.keyValue.entrySet()) {
+        if (pair.getKey().equalsIgnoreCase(ARG_ID_USER)) {
+          accounts = parseAccount(pair.getValue());
+        } else if (pair.getKey().equalsIgnoreCase(ARG_ID_GROUP)) {
+          group = parseGroup(pair.getValue()).getUUID();
+        } else {
+          throw new QueryParseException(
+              "Invalid argument identifier '"   + pair.getKey() + "'");
+        }
+      }
+
+      for (String value : lblArgs.positional) {
+       if (accounts != null || group != null) {
+          throw new QueryParseException("more than one user/group specified (" +
+              value + ")");
+        }
+        try {
+          accounts = parseAccount(value);
+        } catch (QueryParseException qpex) {
+          // If it doesn't match an account, see if it matches a group
+          // (accounts get precedence)
+          try {
+            group = parseGroup(value).getUUID();
+          } catch (QueryParseException e) {
+            throw error("Neither user nor group " + value + " found");
+          }
+        }
+      }
+    }
+
     return new LabelPredicate(args.projectCache,
         args.changeControlGenericFactory, args.userFactory, args.dbProvider,
-        name);
+        name, accounts, group);
   }
 
   @Operator
-  public Predicate<ChangeData> message(String text) {
-    return new MessagePredicate(args.dbProvider, args.repoManager, text);
+  public Predicate<ChangeData> message(String text) throws QueryParseException {
+    ChangeIndex index = args.indexes.getSearchIndex();
+    if (index == null) {
+      return new LegacyMessagePredicate(args.dbProvider, args.repoManager, text);
+    }
+
+    return new MessagePredicate(args.dbProvider, index, text);
   }
 
   @Operator
@@ -330,10 +424,10 @@
     for (Account.Id id : m) {
       if (currentUser instanceof IdentifiedUser
           && id.equals(((IdentifiedUser) currentUser).getAccountId())) {
-        p.add(new IsWatchedByPredicate(args, currentUser));
+        p.add(new IsWatchedByPredicate(args, currentUser, false));
       } else {
         p.add(new IsWatchedByPredicate(args,
-            args.userFactory.create(args.dbProvider, id)));
+            args.userFactory.create(args.dbProvider, id), true));
       }
     }
     return Predicate.or(p);
@@ -511,7 +605,11 @@
       }
 
     } else if (PAT_LABEL.matcher(query).find()) {
-      return label(query);
+      try {
+        return label(query);
+      } catch (OrmException err) {
+        throw error("Cannot lookup user", err);
+      }
 
     } else {
       // Try to match a project name by substring query.
@@ -548,6 +646,15 @@
     return matches;
   }
 
+  private GroupReference parseGroup(String group) throws QueryParseException {
+    GroupReference g = GroupBackends.findBestSuggestion(args.groupBackend,
+        group);
+    if (g == null) {
+      throw error("Group " + group + " not found");
+    }
+    return g;
+  }
+
   private Account.Id self() {
     if (currentUser instanceof IdentifiedUser) {
       return ((IdentifiedUser) currentUser).getAccountId();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index e6251bc..1d6de6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// 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.
@@ -14,693 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ChangeAccess;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.query.IntPredicate;
 import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryRewriter;
-import com.google.gerrit.server.query.RewritePredicate;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Inject;
-import com.google.inject.OutOfScopeException;
-import com.google.inject.Provider;
-import com.google.inject.name.Named;
 
-import java.util.Collection;
-
-public class ChangeQueryRewriter extends QueryRewriter<ChangeData> {
-  private static final QueryRewriter.Definition<ChangeData, ChangeQueryRewriter> mydef =
-      new QueryRewriter.Definition<ChangeData, ChangeQueryRewriter>(
-          ChangeQueryRewriter.class, new ChangeQueryBuilder(
-              new ChangeQueryBuilder.Arguments( //
-                  new InvalidProvider<ReviewDb>(), //
-                  new InvalidProvider<ChangeQueryRewriter>(), //
-                  null, null, null, null, null, //
-                  null, null, null, null), null));
-
-  private final Provider<ReviewDb> dbProvider;
-
-  @Inject
-  ChangeQueryRewriter(Provider<ReviewDb> dbProvider) {
-    super(mydef);
-    this.dbProvider = dbProvider;
-  }
-
-  @Override
-  public Predicate<ChangeData> and(Collection<? extends Predicate<ChangeData>> l) {
-    return hasSource(l) ? new AndSource(dbProvider, l) : super.and(l);
-  }
-
-  @Override
-  public Predicate<ChangeData> or(Collection<? extends Predicate<ChangeData>> l) {
-    return hasSource(l) ? new OrSource(l) : super.or(l);
-  }
-
-  @Rewrite("-status:open")
-  @NoCostComputation
-  public Predicate<ChangeData> r00_notOpen() {
-    return ChangeStatusPredicate.closed(dbProvider);
-  }
-
-  @Rewrite("-status:closed")
-  @NoCostComputation
-  public Predicate<ChangeData> r00_notClosed() {
-    return ChangeStatusPredicate.open(dbProvider);
-  }
-
-  @SuppressWarnings("unchecked")
-  @NoCostComputation
-  @Rewrite("-status:merged")
-  public Predicate<ChangeData> r00_notMerged() {
-    return or(ChangeStatusPredicate.open(dbProvider),
-        new ChangeStatusPredicate(dbProvider, Change.Status.ABANDONED));
-  }
-
-  @SuppressWarnings("unchecked")
-  @NoCostComputation
-  @Rewrite("-status:abandoned")
-  public Predicate<ChangeData> r00_notAbandoned() {
-    return or(ChangeStatusPredicate.open(dbProvider),
-        new ChangeStatusPredicate(dbProvider, Change.Status.MERGED));
-  }
-
-  @SuppressWarnings("unchecked")
-  @NoCostComputation
-  @Rewrite("sortkey_before:z A=(age:*)")
-  public Predicate<ChangeData> r00_ageToSortKey(@Named("A") AgePredicate a) {
-    String cut = ChangeUtil.sortKey(a.getCut(), Integer.MAX_VALUE);
-    return and(new SortKeyPredicate.Before(dbProvider, cut), a);
-  }
-
-  @NoCostComputation
-  @Rewrite("A=(limit:*) B=(limit:*)")
-  public Predicate<ChangeData> r00_smallestLimit(
-      @Named("A") IntPredicate<ChangeData> a,
-      @Named("B") IntPredicate<ChangeData> b) {
-    return a.intValue() <= b.intValue() ? a : b;
-  }
-
-  @NoCostComputation
-  @Rewrite("A=(sortkey_before:*) B=(sortkey_before:*)")
-  public Predicate<ChangeData> r00_oldestSortKey(
-      @Named("A") SortKeyPredicate.Before a,
-      @Named("B") SortKeyPredicate.Before b) {
-    return a.getValue().compareTo(b.getValue()) <= 0 ? a : b;
-  }
-
-  @NoCostComputation
-  @Rewrite("A=(sortkey_after:*) B=(sortkey_after:*)")
-  public Predicate<ChangeData> r00_newestSortKey(
-      @Named("A") SortKeyPredicate.After a, @Named("B") SortKeyPredicate.After b) {
-    return a.getValue().compareTo(b.getValue()) >= 0 ? a : b;
-  }
-
-  @Rewrite("status:open P=(project:*) B=(branch:*)")
-  public Predicate<ChangeData> r05_byBranchOpen(
-      @Named("P") final ProjectPredicate p,
-      @Named("B") final BranchPredicate b) {
-    return new ChangeSource(500) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a)
-          throws OrmException {
-        return a.byBranchOpenAll(
-            new Branch.NameKey(p.getValueKey(), b.getValue()));
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen()
-            && p.match(cd)
-            && b.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:merged P=(project:*) B=(branch:*) S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r05_byBranchMergedPrev(
-      @Named("P") final ProjectPredicate p,
-      @Named("B") final BranchPredicate b,
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byBranchClosedPrev(Change.Status.MERGED.getCode(), //
-            new Branch.NameKey(p.getValueKey(), b.getValue()), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && p.match(cd) //
-            && b.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:merged P=(project:*) B=(branch:*) S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r05_byBranchMergedNext(
-      @Named("P") final ProjectPredicate p,
-      @Named("B") final BranchPredicate b,
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byBranchClosedNext(Change.Status.MERGED.getCode(), //
-            new Branch.NameKey(p.getValueKey(), b.getValue()), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && p.match(cd) //
-            && b.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:open P=(project:*) S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectOpenPrev(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(500, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectOpenPrev(p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() //
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:open P=(project:*) S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectOpenNext(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(500, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectOpenNext(p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() //
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:merged P=(project:*) S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectMergedPrev(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectClosedPrev(Change.Status.MERGED.getCode(), //
-            p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:merged P=(project:*) S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectMergedNext(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectClosedNext(Change.Status.MERGED.getCode(), //
-            p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:abandoned P=(project:*) S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectAbandonedPrev(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectClosedPrev(Change.Status.ABANDONED.getCode(), //
-            p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:abandoned P=(project:*) S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectAbandonedNext(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectClosedNext(Change.Status.ABANDONED.getCode(), //
-            p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:open S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byOpenPrev(
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(2000, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allOpenPrev(key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:open S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byOpenNext(
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(2000, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allOpenNext(key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:merged S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byMergedPrev(
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
-      {
-        init("r20_byMergedPrev", s, l);
-      }
-
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allClosedPrev(Change.Status.MERGED.getCode(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:merged S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byMergedNext(
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
-      {
-        init("r20_byMergedNext", s, l);
-      }
-
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allClosedNext(Change.Status.MERGED.getCode(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:abandoned S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byAbandonedPrev(
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
-      {
-        init("r20_byAbandonedPrev", s, l);
-      }
-
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allClosedPrev(Change.Status.ABANDONED.getCode(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
-            && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:abandoned S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byAbandonedNext(
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
-      {
-        init("r20_byAbandonedNext", s, l);
-      }
-
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allClosedNext(Change.Status.ABANDONED.getCode(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
-            && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byClosedPrev(
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return or(r20_byMergedPrev(s, l), r20_byAbandonedPrev(s, l));
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byClosedNext(
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return or(r20_byMergedNext(s, l), r20_byAbandonedNext(s, l));
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:open O=(owner:*)")
-  public Predicate<ChangeData> r25_byOwnerOpen(
-      @Named("O") final OwnerPredicate o) {
-    return new ChangeSource(50) {
-      {
-        init("r25_byOwnerOpen", o);
-      }
-
-      @Override
-      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-        return a.byOwnerOpen(o.getAccountId());
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() && o.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:closed O=(owner:*)")
-  public Predicate<ChangeData> r25_byOwnerClosed(
-      @Named("O") final OwnerPredicate o) {
-    return new ChangeSource(5000) {
-      {
-        init("r25_byOwnerClosed", o);
-      }
-
-      @Override
-      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-        return a.byOwnerClosedAll(o.getAccountId());
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isClosed() && o.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("O=(owner:*)")
-  public Predicate<ChangeData> r26_byOwner(@Named("O") OwnerPredicate o) {
-    return or(r25_byOwnerOpen(o), r25_byOwnerClosed(o));
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:open R=(reviewer:*)")
-  public Predicate<ChangeData> r30_byReviewerOpen(
-      @Named("R") final ReviewerPredicate r) {
-    return new Source() {
-      {
-        init("r30_byReviewerOpen", r);
-      }
-
-      @Override
-      public ResultSet<ChangeData> read() throws OrmException {
-        return ChangeDataResultSet.patchSetApproval(dbProvider.get()
-            .patchSetApprovals().openByUser(r.getAccountId()));
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        Change change = cd.change(dbProvider);
-        return change != null && change.getStatus().isOpen() && r.match(cd);
-      }
-
-      @Override
-      public int getCardinality() {
-        return 50;
-      }
-
-      @Override
-      public int getCost() {
-        return ChangeCosts.cost(ChangeCosts.APPROVALS_SCAN, getCardinality());
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:closed R=(reviewer:*)")
-  public Predicate<ChangeData> r30_byReviewerClosed(
-      @Named("R") final ReviewerPredicate r) {
-    return new Source() {
-      {
-        init("r30_byReviewerClosed", r);
-      }
-
-      @Override
-      public ResultSet<ChangeData> read() throws OrmException {
-        return ChangeDataResultSet.patchSetApproval(dbProvider.get()
-            .patchSetApprovals().closedByUserAll(r.getAccountId()));
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        Change change = cd.change(dbProvider);
-        return change != null && change.getStatus().isClosed() && r.match(cd);
-      }
-
-      @Override
-      public int getCardinality() {
-        return 5000;
-      }
-
-      @Override
-      public int getCost() {
-        return ChangeCosts.cost(ChangeCosts.APPROVALS_SCAN, getCardinality());
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("R=(reviewer:*)")
-  public Predicate<ChangeData> r31_byReviewer(
-      @Named("R") final ReviewerPredicate r) {
-    return or(r30_byReviewerOpen(r), r30_byReviewerClosed(r));
-  }
-
-  @Rewrite("status:submitted")
-  public Predicate<ChangeData> r99_allSubmitted() {
-    return new ChangeSource(50) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-        return a.allSubmitted();
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.SUBMITTED;
-      }
-    };
-  }
-
-  @Rewrite("P=(project:*)")
-  public Predicate<ChangeData> r99_byProject(
-      @Named("P") final ProjectPredicate p) {
-    return new ChangeSource(1000000) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-        return a.byProject(p.getValueKey());
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return p.match(cd);
-      }
-    };
-  }
-
-  private static boolean hasSource(Collection<? extends Predicate<ChangeData>> l) {
-    for (Predicate<ChangeData> p : l) {
-      if (p instanceof ChangeDataSource) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private abstract static class Source extends RewritePredicate<ChangeData>
-      implements ChangeDataSource {
-    @Override
-    public boolean hasChange() {
-      return false;
-    }
-  }
-
-  private abstract class ChangeSource extends Source {
-    private final int cardinality;
-
-    ChangeSource(int card) {
-      this.cardinality = card;
-    }
-
-    abstract ResultSet<Change> scan(ChangeAccess a) throws OrmException;
-
-    @Override
-    public ResultSet<ChangeData> read() throws OrmException {
-      return ChangeDataResultSet.change(scan(dbProvider.get().changes()));
-    }
-
-    @Override
-    public boolean hasChange() {
-      return true;
-    }
-
-    @Override
-    public int getCardinality() {
-      return cardinality;
-    }
-
-    @Override
-    public int getCost() {
-      return ChangeCosts.cost(ChangeCosts.CHANGES_SCAN, getCardinality());
-    }
-  }
-
-  private abstract class PaginatedSource extends ChangeSource implements
-      Paginated {
-    private final String startKey;
-    private final int limit;
-
-    PaginatedSource(int card, String start, int lim) {
-      super(card);
-      this.startKey = start;
-      this.limit = lim;
-    }
-
-    @Override
-    public int limit() {
-      return limit;
-    }
-
-    @Override
-    ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-      return scan(a, startKey, limit);
-    }
-
-    @Override
-    public ResultSet<ChangeData> restart(ChangeData last) throws OrmException {
-      return ChangeDataResultSet.change(scan(dbProvider.get().changes(), //
-          last.change(dbProvider).getSortKey(), //
-          limit));
-    }
-
-    abstract ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-        throws OrmException;
-  }
-
-  private static final class InvalidProvider<T> implements Provider<T> {
-    @Override
-    public T get() {
-      throw new OutOfScopeException("Not available at init");
-    }
-  }
+public interface ChangeQueryRewriter {
+  Predicate<ChangeData> rewrite(Predicate<ChangeData> in);
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index 6e9e79c..d5d6a92 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -14,19 +14,19 @@
 
 package com.google.gerrit.server.query.change;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableBiMap;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
 import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-
 
 /**
  * Predicate for a {@link Change.Status}.
@@ -35,21 +35,19 @@
  * status:} but may also be {@code is:} to help do-what-i-meanery for end-users
  * searching for changes. Either operator name has the same meaning.
  */
-final class ChangeStatusPredicate extends OperatorPredicate<ChangeData> {
-  private static final Map<String, Change.Status> byName;
-  private static final EnumMap<Change.Status, String> byEnum;
+public final class ChangeStatusPredicate extends IndexPredicate<ChangeData> {
+  public static final ImmutableBiMap<Change.Status, String> VALUES;
 
   static {
-    byName = new HashMap<String, Change.Status>();
-    byEnum = new EnumMap<Change.Status, String>(Change.Status.class);
-    for (final Change.Status s : Change.Status.values()) {
-      final String name = s.name().toLowerCase();
-      byName.put(name, s);
-      byEnum.put(s, name);
+    ImmutableBiMap.Builder<Change.Status, String> values =
+        ImmutableBiMap.builder();
+    for (Change.Status s : Change.Status.values()) {
+      values.put(s, s.name().toLowerCase());
     }
+    VALUES = values.build();
   }
 
-  static Predicate<ChangeData> open(Provider<ReviewDb> dbProvider) {
+  public static Predicate<ChangeData> open(Provider<ReviewDb> dbProvider) {
     List<Predicate<ChangeData>> r = new ArrayList<Predicate<ChangeData>>(4);
     for (final Change.Status e : Change.Status.values()) {
       if (e.isOpen()) {
@@ -59,7 +57,7 @@
     return r.size() == 1 ? r.get(0) : or(r);
   }
 
-  static Predicate<ChangeData> closed(Provider<ReviewDb> dbProvider) {
+  public static Predicate<ChangeData> closed(Provider<ReviewDb> dbProvider) {
     List<Predicate<ChangeData>> r = new ArrayList<Predicate<ChangeData>>(4);
     for (final Change.Status e : Change.Status.values()) {
       if (e.isClosed()) {
@@ -69,28 +67,23 @@
     return r.size() == 1 ? r.get(0) : or(r);
   }
 
-  private static Change.Status parse(final String value) {
-    final Change.Status s = byName.get(value);
-    if (s == null) {
-      throw new IllegalArgumentException();
-    }
-    return s;
-  }
-
   private final Provider<ReviewDb> dbProvider;
   private final Change.Status status;
 
   ChangeStatusPredicate(Provider<ReviewDb> dbProvider, String value) {
-    this(dbProvider, parse(value));
+    super(ChangeField.STATUS, value);
+    this.dbProvider = dbProvider;
+    status = VALUES.inverse().get(value);
+    checkArgument(status != null, "invalid change status: %s", value);
   }
 
   ChangeStatusPredicate(Provider<ReviewDb> dbProvider, Change.Status status) {
-    super(ChangeQueryBuilder.FIELD_STATUS, byEnum.get(status));
+    super(ChangeField.STATUS, VALUES.get(status));
     this.dbProvider = dbProvider;
     this.status = status;
   }
 
-  Change.Status getStatus() {
+  public Change.Status getStatus() {
     return status;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
new file mode 100644
index 0000000..05d7573
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
@@ -0,0 +1,58 @@
+// 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.query.change;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
+
+class CommentPredicate extends IndexPredicate<ChangeData> {
+  private final Provider<ReviewDb> db;
+  private final ChangeIndex index;
+
+  CommentPredicate(Provider<ReviewDb> db, ChangeIndex index, String value) {
+    super(ChangeField.COMMENT, value);
+    this.db = db;
+    this.index = index;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public boolean match(ChangeData object) throws OrmException {
+    try {
+      for (ChangeData cData : index.getSource(
+          Predicate.and(new LegacyChangeIdPredicate(db, object.getId()), this))
+          .read()) {
+        if (cData.getId().equals(object.getId())) {
+          return true;
+        }
+      }
+    } catch (QueryParseException e) {
+      throw new OrmException(e);
+    }
+
+    return false;
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
index 274b40c..082dc83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -17,7 +17,8 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.ObjectIdPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Provider;
@@ -25,13 +26,15 @@
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.ObjectId;
 
-class CommitPredicate extends ObjectIdPredicate<ChangeData> implements
+class CommitPredicate extends IndexPredicate<ChangeData> implements
     ChangeDataSource {
   private final Provider<ReviewDb> dbProvider;
+  private final AbbreviatedObjectId abbrevId;
 
   CommitPredicate(Provider<ReviewDb> dbProvider, AbbreviatedObjectId id) {
-    super(ChangeQueryBuilder.FIELD_COMMIT, id);
+    super(ChangeField.COMMIT, id.name());
     this.dbProvider = dbProvider;
+    this.abbrevId = id;
   }
 
   @Override
@@ -39,7 +42,7 @@
     for (PatchSet p : object.patches(dbProvider)) {
       if (p.getRevision() != null && p.getRevision().get() != null) {
         final ObjectId id = ObjectId.fromString(p.getRevision().get());
-        if (abbreviated().prefixCompare(id) == 0) {
+        if (abbrevId.prefixCompare(id) == 0) {
           return true;
         }
       }
@@ -49,7 +52,7 @@
 
   @Override
   public ResultSet<ChangeData> read() throws OrmException {
-    final RevId id = new RevId(abbreviated().name());
+    final RevId id = new RevId(abbrevId.name());
     if (id.isComplete()) {
       return ChangeDataResultSet.patchSet(//
           dbProvider.get().patchSets().byRevision(id));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
new file mode 100644
index 0000000..002dc99
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
@@ -0,0 +1,57 @@
+// 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.query.change;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+import java.util.List;
+
+class EqualsFilePredicate extends IndexPredicate<ChangeData> {
+  private final Provider<ReviewDb> db;
+  private final PatchListCache cache;
+  private final String value;
+
+  EqualsFilePredicate(Provider<ReviewDb> db, PatchListCache plc, String value) {
+    super(ChangeField.FILE, value);
+    this.db = db;
+    this.cache = plc;
+    this.value = value;
+  }
+
+  @Override
+  public boolean match(ChangeData object) throws OrmException {
+    List<String> files = object.currentFilePaths(db, cache);
+    if (files != null) {
+      return Collections.binarySearch(files, value) >= 0;
+    } else {
+      // The ChangeData can't do expensive lookups right now. Bypass
+      // them and include the result anyway. We might be able to do
+      // a narrow later on to a smaller set.
+      //
+      return true;
+    }
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
new file mode 100644
index 0000000..af9a07a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -0,0 +1,153 @@
+// 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.query.change;
+
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
+
+class EqualsLabelPredicate extends IndexPredicate<ChangeData> {
+  private final ProjectCache projectCache;
+  private final ChangeControl.GenericFactory ccFactory;
+  private final IdentifiedUser.GenericFactory userFactory;
+  private final Provider<ReviewDb> dbProvider;
+  private final String label;
+  private final int expVal;
+  private final Account.Id account;
+  private final AccountGroup.UUID group;
+
+  EqualsLabelPredicate(ProjectCache projectCache,
+      ChangeControl.GenericFactory ccFactory,
+      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
+      String label, int expVal, Account.Id account,
+      AccountGroup.UUID group) {
+    super(ChangeField.LABEL, ChangeField.formatLabel(label, expVal, account));
+    this.ccFactory = ccFactory;
+    this.projectCache = projectCache;
+    this.userFactory = userFactory;
+    this.dbProvider = dbProvider;
+    this.label = label;
+    this.expVal = expVal;
+    this.account = account;
+    this.group = group;
+  }
+
+  @Override
+  public boolean match(ChangeData object) throws OrmException {
+    Change c = object.change(dbProvider);
+    if (c == null) {
+      // The change has disappeared.
+      //
+      return false;
+    }
+    ProjectState project = projectCache.get(c.getDest().getParentKey());
+    if (project == null) {
+      // The project has disappeared.
+      //
+      return false;
+    }
+    LabelType labelType = type(project.getLabelTypes(), label);
+    boolean hasVote = false;
+    for (PatchSetApproval p : object.currentApprovals(dbProvider)) {
+      if (labelType.matches(p)) {
+        hasVote = true;
+        if (match(c, p.getValue(), p.getAccountId(), labelType)) {
+          return true;
+        }
+      }
+    }
+
+    if (!hasVote && expVal == 0) {
+      return true;
+    }
+
+    return false;
+  }
+
+  private static LabelType type(LabelTypes types, String toFind) {
+    if (types.byLabel(toFind) != null) {
+      return types.byLabel(toFind);
+    }
+
+    for (LabelType lt : types.getLabelTypes()) {
+      if (toFind.equalsIgnoreCase(lt.getName())) {
+        return lt;
+      }
+    }
+
+    for (LabelType lt : types.getLabelTypes()) {
+      if (toFind.equalsIgnoreCase(lt.getAbbreviation())) {
+        return lt;
+      }
+    }
+
+    return LabelType.withDefaultValues(toFind);
+  }
+
+  private boolean match(Change change, int value, Account.Id approver,
+      LabelType type) throws OrmException {
+    int psVal = value;
+    if (psVal == expVal) {
+      // Double check the value is still permitted for the user.
+      //
+      IdentifiedUser reviewer = userFactory.create(dbProvider, approver);
+      try {
+        ChangeControl cc = ccFactory.controlFor(change, reviewer);
+        if (!cc.isVisible(dbProvider.get())) {
+          // The user can't see the change anymore.
+          //
+          return false;
+        }
+        psVal = cc.getRange(Permission.forLabel(type.getName())).squash(psVal);
+      } catch (NoSuchChangeException e) {
+        // The project has disappeared.
+        //
+        return false;
+      }
+
+      if (account != null && !account.equals(approver)) {
+        return false;
+      }
+
+      if (group != null && !reviewer.getEffectiveGroups().contains(group)) {
+        return false;
+      }
+
+      if (psVal == expVal) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public int getCost() {
+    return 1 + (group == null ? 0 : 1);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
index 639be517c..6832e4e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
@@ -18,15 +18,16 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-class IsReviewedPredicate extends OperatorPredicate<ChangeData> {
+class IsReviewedPredicate extends IndexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
 
   IsReviewedPredicate(Provider<ReviewDb> dbProvider) {
-    super(ChangeQueryBuilder.FIELD_IS, "reviewed");
+    super(ChangeField.REVIEWED, "1");
     this.dbProvider = dbProvider;
   }
 
@@ -51,4 +52,9 @@
   public int getCost() {
     return 2;
   }
+
+  @Override
+  public String toString() {
+    return "is:reviewed";
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
index 3fd9feb..7f1c6f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
@@ -14,15 +14,21 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.common.collect.Lists;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Provider;
 
-class IsStarredByPredicate extends OperatorPredicate<ChangeData> implements
+import java.util.List;
+import java.util.Set;
+
+class IsStarredByPredicate extends OrPredicate<ChangeData> implements
     ChangeDataSource {
   private static String describe(CurrentUser user) {
     if (user instanceof IdentifiedUser) {
@@ -31,11 +37,21 @@
     return user.toString();
   }
 
+  private static List<Predicate<ChangeData>> predicates(
+      Provider<ReviewDb> db,
+      Set<Change.Id> ids) {
+    List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(ids.size());
+    for (Change.Id id : ids) {
+      r.add(new LegacyChangeIdPredicate(db, id));
+    }
+    return r;
+  }
+
   private final Provider<ReviewDb> db;
   private final CurrentUser user;
 
   IsStarredByPredicate(Provider<ReviewDb> db, CurrentUser user) {
-    super(ChangeQueryBuilder.FIELD_STARREDBY, describe(user));
+    super(predicates(db, user.getStarredChanges()));
     this.db = db;
     this.user = user;
   }
@@ -65,4 +81,14 @@
   public int getCost() {
     return ChangeCosts.cost(ChangeCosts.IDS_MEMORY, getCardinality());
   }
+
+  @Override
+  public String toString() {
+    String val = describe(user);
+    if (val.indexOf(' ') < 0) {
+      return ChangeQueryBuilder.FIELD_STARREDBY + ":" + val;
+    } else {
+      return ChangeQueryBuilder.FIELD_STARREDBY + ":\"" + val + "\"";
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index 25cfae7..ade1f35 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -14,22 +14,18 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.AndPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
-import com.google.gwtorm.server.OrmException;
 
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
+class IsWatchedByPredicate extends AndPredicate<ChangeData> {
   private static String describe(CurrentUser user) {
     if (user instanceof IdentifiedUser) {
       return ((IdentifiedUser) user).getAccountId().toString();
@@ -37,85 +33,83 @@
     return user.toString();
   }
 
-  private final ChangeQueryBuilder.Arguments args;
   private final CurrentUser user;
 
-  private Map<Project.NameKey, List<Predicate<ChangeData>>> rules;
-
-  IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, CurrentUser user) {
-    super(ChangeQueryBuilder.FIELD_WATCHEDBY, describe(user));
-    this.args = args;
+  IsWatchedByPredicate(ChangeQueryBuilder.Arguments args,
+      CurrentUser user,
+      boolean checkIsVisible) {
+    super(filters(args, user, checkIsVisible));
     this.user = user;
   }
 
-  @Override
-  public boolean match(final ChangeData cd) throws OrmException {
-    if (rules == null) {
-      ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
-      rules = new HashMap<Project.NameKey, List<Predicate<ChangeData>>>();
-      for (AccountProjectWatch w : user.getNotificationFilters()) {
-        List<Predicate<ChangeData>> list = rules.get(w.getProjectNameKey());
-        if (list == null) {
-          list = new ArrayList<Predicate<ChangeData>>(4);
-          rules.put(w.getProjectNameKey(), list);
-        }
-
-        Predicate<ChangeData> p = compile(builder, w);
-        if (p != null) {
-          list.add(p);
+  private static List<Predicate<ChangeData>> filters(
+      ChangeQueryBuilder.Arguments args,
+      CurrentUser user,
+      boolean checkIsVisible) {
+    List<Predicate<ChangeData>> r = Lists.newArrayList();
+    ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
+    for (AccountProjectWatch w : user.getNotificationFilters()) {
+      Predicate<ChangeData> f = null;
+      if (w.getFilter() != null) {
+        try {
+          f = builder.parse(w.getFilter());
+          if (builder.find(f, IsWatchedByPredicate.class) != null) {
+            // If the query is going to infinite loop, assume it
+            // will never match and return null. Yes this test
+            // prevents you from having a filter that matches what
+            // another user is filtering on. :-)
+           continue;
+          }
+        } catch (QueryParseException e) {
+          continue;
         }
       }
-    }
 
-    if (rules.isEmpty()) {
-      return false;
-    }
+      Predicate<ChangeData> p;
+      if (w.getProjectNameKey().equals(args.allProjectsName)) {
+        p = null;
+      } else {
+        p = builder.project(w.getProjectNameKey().get());
+      }
 
-    Change change = cd.change(args.dbProvider);
-    if (change == null) {
-      return false;
-    }
-
-    Project.NameKey project = change.getDest().getParentKey();
-    List<Predicate<ChangeData>> list = rules.get(project);
-    if (list == null) {
-      list = rules.get(args.allProjectsName);
-    }
-    if (list != null) {
-      for (Predicate<ChangeData> p : list) {
-        if (p.match(cd)) {
-          return true;
-        }
+      if (p != null && f != null) {
+        @SuppressWarnings("unchecked")
+        Predicate<ChangeData> andPredicate = and(p, f);
+        r.add(andPredicate);
+      } else if (p != null) {
+        r.add(p);
+      } else if (f != null) {
+        r.add(f);
+      } else {
+        r.add(builder.status_open());
       }
     }
-    return false;
+    if (r.isEmpty()) {
+      return none();
+    } else if (checkIsVisible) {
+      return ImmutableList.of(or(r), builder.is_visible());
+    } else {
+      return ImmutableList.of(or(r));
+    }
   }
 
-  @SuppressWarnings("unchecked")
-  private Predicate<ChangeData> compile(ChangeQueryBuilder builder,
-      AccountProjectWatch w) {
-    Predicate<ChangeData> p = builder.is_visible();
-    if (w.getFilter() != null) {
-      try {
-        p = Predicate.and(builder.parse(w.getFilter()), p);
-        if (builder.find(p, IsWatchedByPredicate.class) != null) {
-          // If the query is going to infinite loop, assume it
-          // will never match and return null. Yes this test
-          // prevents you from having a filter that matches what
-          // another user is filtering on. :-)
-          //
-          return null;
-        }
-        p = args.rewriter.get().rewrite(p);
-      } catch (QueryParseException e) {
-        return null;
-      }
-    }
-    return p;
+  private static List<Predicate<ChangeData>> none() {
+    Predicate<ChangeData> any = any();
+    return ImmutableList.of(not(any));
   }
 
   @Override
   public int getCost() {
     return 1;
   }
+
+  @Override
+  public String toString() {
+    String val = describe(user);
+    if (val.indexOf(' ') < 0) {
+      return ChangeQueryBuilder.FIELD_WATCHEDBY + ":" + val;
+    } else {
+      return ChangeQueryBuilder.FIELD_WATCHEDBY + ":\"" + val + "\"";
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index b0d02f8..b3ea6b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -14,84 +14,109 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.common.data.LabelTypes;
-import com.google.gerrit.common.data.Permission;
+import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.query.OperatorPredicate;
-import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
 import com.google.inject.Provider;
 
-import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-class LabelPredicate extends OperatorPredicate<ChangeData> {
+public class LabelPredicate extends OrPredicate<ChangeData> {
+  private static final int MAX_LABEL_VALUE = 4;
+
   private static enum Test {
-    EQ {
-      @Override
-      public boolean match(int psValue, int expValue) {
-        return psValue == expValue;
-      }
-    },
-    GT_EQ {
-      @Override
-      public boolean match(int psValue, int expValue) {
-        return psValue >= expValue;
-      }
-    },
-    LT_EQ {
-      @Override
-      public boolean match(int psValue, int expValue) {
-        return psValue <= expValue;
-      }
-    };
+    EQ, GT_EQ, LT_EQ;
 
-    abstract boolean match(int psValue, int expValue);
+    boolean isEq() {
+      return EQ.equals(this);
+    }
+
+    boolean isGtEq() {
+      return GT_EQ.equals(this);
+    }
+
+    static Test op(String op) {
+      if ("=".equals(op)) {
+        return EQ;
+
+      } else if (">=".equals(op)) {
+        return GT_EQ;
+
+      } else if ("<=".equals(op)) {
+        return LT_EQ;
+
+      } else {
+        throw new IllegalArgumentException("Unsupported operation " + op);
+      }
+    }
   }
 
-  private static LabelType type(LabelTypes types, String toFind) {
-    if (types.byLabel(toFind) != null) {
-      return types.byLabel(toFind);
-    }
+  private final String value;
 
-    for (LabelType lt : types.getLabelTypes()) {
-      if (toFind.equalsIgnoreCase(lt.getName())) {
-        return lt;
-      }
-    }
-
-    for (LabelType lt : types.getLabelTypes()) {
-      if (toFind.equalsIgnoreCase(lt.getAbbreviation())) {
-        return lt;
-      }
-    }
-
-    return LabelType.withDefaultValues(toFind);
+  LabelPredicate(ProjectCache projectCache,
+      ChangeControl.GenericFactory ccFactory,
+      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
+      String value, Set<Account.Id> accounts, AccountGroup.UUID group) {
+    super(predicates(projectCache, ccFactory, userFactory, dbProvider, value,
+        accounts, group));
+    this.value = value;
   }
 
-  private static Test op(String op) {
-    if ("=".equals(op)) {
-      return Test.EQ;
+  private static List<Predicate<ChangeData>> predicates(
+      ProjectCache projectCache, ChangeControl.GenericFactory ccFactory,
+      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
+      String value, Set<Account.Id> accounts, AccountGroup.UUID group) {
+    String label;
+    Test test;
+    int expVal;
+    Matcher m1 = Pattern.compile("(=|>=|<=)([+-]?\\d+)$").matcher(value);
+    Matcher m2 = Pattern.compile("([+-]\\d+)$").matcher(value);
+    if (m1.find()) {
+      label = value.substring(0, m1.start());
+      test = Test.op(m1.group(1));
+      expVal = value(m1.group(2));
 
-    } else if (">=".equals(op)) {
-      return Test.GT_EQ;
-
-    } else if ("<=".equals(op)) {
-      return Test.LT_EQ;
+    } else if (m2.find()) {
+      label = value.substring(0, m2.start());
+      test = Test.EQ;
+      expVal = value(m2.group(1));
 
     } else {
-      throw new IllegalArgumentException("Unsupported operation " + op);
+      label = value;
+      test = Test.EQ;
+      expVal = 1;
     }
+
+    List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
+    if (test.isEq()) {
+      if (expVal != 0) {
+        r.add(equalsLabelPredicate(projectCache, ccFactory, userFactory,
+            dbProvider, label, expVal, accounts, group));
+      } else {
+        r.add(noLabelQuery(projectCache, ccFactory, userFactory,
+            dbProvider, label, accounts, group));
+      }
+    } else {
+      for (int i = test.isGtEq() ? expVal : neg(expVal); i <= MAX_LABEL_VALUE; i++) {
+        if (i != 0) {
+          r.add(equalsLabelPredicate(projectCache, ccFactory, userFactory,
+              dbProvider, label, test.isGtEq() ? i : neg(i), accounts, group));
+        } else {
+          r.add(noLabelQuery(projectCache, ccFactory, userFactory,
+              dbProvider, label, accounts, group));
+        }
+      }
+    }
+    return r;
   }
 
   private static int value(String value) {
@@ -101,113 +126,45 @@
     return Integer.parseInt(value);
   }
 
-  private final ProjectCache projectCache;
-  private final ChangeControl.GenericFactory ccFactory;
-  private final IdentifiedUser.GenericFactory userFactory;
-  private final Provider<ReviewDb> dbProvider;
-  private final Test test;
-  private final String type;
-  private final int expVal;
+  private static int neg(int value) {
+    return -1 * value;
+  }
 
-  LabelPredicate(ProjectCache projectCache,
+  private static Predicate<ChangeData> noLabelQuery(ProjectCache projectCache,
       ChangeControl.GenericFactory ccFactory,
-      IdentifiedUser.GenericFactory userFactory,
-      Provider<ReviewDb> dbProvider,
-      String value) {
-    super(ChangeQueryBuilder.FIELD_LABEL, value);
-    this.ccFactory = ccFactory;
-    this.projectCache = projectCache;
-    this.userFactory = userFactory;
-    this.dbProvider = dbProvider;
+      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
+      String label, Set<Account.Id> accounts, AccountGroup.UUID group) {
+    List<Predicate<ChangeData>> r =
+        Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
+    for (int i = 1; i <= MAX_LABEL_VALUE; i++) {
+      r.add(not(equalsLabelPredicate(projectCache, ccFactory, userFactory,
+          dbProvider, label, i, accounts, group)));
+      r.add(not(equalsLabelPredicate(projectCache, ccFactory, userFactory,
+          dbProvider, label, neg(i), accounts, group)));
+    }
+    return and(r);
+  }
 
-    Matcher m1 = Pattern.compile("(=|>=|<=)([+-]?\\d+)$").matcher(value);
-    Matcher m2 = Pattern.compile("([+-]\\d+)$").matcher(value);
-    if (m1.find()) {
-      type = value.substring(0, m1.start());
-      test = op(m1.group(1));
-      expVal = value(m1.group(2));
-
-    } else if (m2.find()) {
-      type = value.substring(0, m2.start());
-      test = Test.EQ;
-      expVal = value(m2.group(1));
-
+  private static Predicate<ChangeData> equalsLabelPredicate(
+      ProjectCache projectCache, ChangeControl.GenericFactory ccFactory,
+      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
+      String label, int expVal, Set<Account.Id> accounts,
+      AccountGroup.UUID group) {
+    if (accounts == null || accounts.isEmpty()) {
+      return new EqualsLabelPredicate(projectCache, ccFactory, userFactory,
+          dbProvider, label, expVal, null, group);
     } else {
-      type = value;
-      test = Test.EQ;
-      expVal = 1;
+      List<Predicate<ChangeData>> r = Lists.newArrayList();
+      for (Account.Id a : accounts) {
+        r.add(new EqualsLabelPredicate(projectCache, ccFactory, userFactory,
+            dbProvider, label, expVal, a, group));
+      }
+      return or(r);
     }
   }
 
   @Override
-  public boolean match(final ChangeData object) throws OrmException {
-    final Change c = object.change(dbProvider);
-    if (c == null) {
-      // The change has disappeared.
-      //
-      return false;
-    }
-    final ProjectState project = projectCache.get(c.getDest().getParentKey());
-    if (project == null) {
-      // The project has disappeared.
-      //
-      return false;
-    }
-    final LabelType labelType = type(project.getLabelTypes(), type);
-    final Set<Account.Id> allApprovers = new HashSet<Account.Id>();
-    final Set<Account.Id> approversThatVotedInCategory = new HashSet<Account.Id>();
-    for (PatchSetApproval p : object.currentApprovals(dbProvider)) {
-      allApprovers.add(p.getAccountId());
-      if (labelType.matches(p)) {
-        approversThatVotedInCategory.add(p.getAccountId());
-        if (match(c, p.getValue(), p.getAccountId(), labelType)) {
-          return true;
-        }
-      }
-    }
-
-    final Set<Account.Id> approversThatDidNotVoteInCategory = new HashSet<Account.Id>(allApprovers);
-    approversThatDidNotVoteInCategory.removeAll(approversThatVotedInCategory);
-    for (Account.Id a : approversThatDidNotVoteInCategory) {
-      if (match(c, 0, a, labelType)) {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  private boolean match(final Change change, final int value,
-      final Account.Id approver, final LabelType type)
-      throws OrmException {
-    int psVal = value;
-    if (test.match(psVal, expVal)) {
-      // Double check the value is still permitted for the user.
-      //
-      try {
-        ChangeControl cc = ccFactory.controlFor(change, //
-            userFactory.create(dbProvider, approver));
-        if (!cc.isVisible(dbProvider.get())) {
-          // The user can't see the change anymore.
-          //
-          return false;
-        }
-        psVal = cc.getRange(Permission.forLabel(type.getName())).squash(psVal);
-      } catch (NoSuchChangeException e) {
-        // The project has disappeared.
-        //
-        return false;
-      }
-
-      if (test.match(psVal, expVal)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  @Override
-  public int getCost() {
-    return 2;
+  public String toString() {
+    return ChangeQueryBuilder.FIELD_LABEL + ":" + value;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
index 4c47e73..48ae48f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
@@ -16,7 +16,8 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
@@ -24,13 +25,13 @@
 
 import java.util.Collections;
 
-class LegacyChangeIdPredicate extends OperatorPredicate<ChangeData> implements
+class LegacyChangeIdPredicate extends IndexPredicate<ChangeData> implements
     ChangeDataSource {
   private final Provider<ReviewDb> db;
   private final Change.Id id;
 
   LegacyChangeIdPredicate(Provider<ReviewDb> db, Change.Id id) {
-    super(ChangeQueryBuilder.FIELD_CHANGE, id.toString());
+    super(ChangeField.LEGACY_ID, ChangeQueryBuilder.FIELD_CHANGE, id.toString());
     this.db = db;
     this.id = id;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyMessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyMessagePredicate.java
new file mode 100644
index 0000000..6b6d1e5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyMessagePredicate.java
@@ -0,0 +1,69 @@
+// 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.query.change;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.MessageRevFilter;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/**
+ * Predicate to match changes that contains specified text in commit messages
+ * body.
+ */
+public class LegacyMessagePredicate extends RevWalkPredicate {
+
+  private static final Logger log = LoggerFactory
+      .getLogger(LegacyMessagePredicate.class);
+
+  private final RevFilter rFilter;
+
+  public LegacyMessagePredicate(Provider<ReviewDb> db,
+      GitRepositoryManager repoManager, String text) {
+    super(db, repoManager, ChangeQueryBuilder.FIELD_MESSAGE, text);
+    this.rFilter = MessageRevFilter.create(text);
+  }
+
+  @Override
+  public boolean match(Repository repo, RevWalk rw, Arguments args) {
+    try {
+      return rFilter.include(rw, rw.parseCommit(args.objectId));
+    } catch (MissingObjectException e) {
+      log.error(args.projectName.get() + "\" commit does not exist.", e);
+    } catch (IncorrectObjectTypeException e) {
+      log.error(args.projectName.get() + "\" revision is not a commit.", e);
+    } catch (IOException e) {
+      log.error(
+          "Could not search for commit message in \"" + args.projectName.get()
+              + "\" repository.", e);
+    }
+    return false;
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
index 0ea280d..62a3876 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -15,49 +15,43 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.revwalk.filter.MessageRevFilter;
-import org.eclipse.jgit.revwalk.filter.RevFilter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
 /**
  * Predicate to match changes that contains specified text in commit messages
  * body.
  */
-public class MessagePredicate extends RevWalkPredicate {
+class MessagePredicate extends IndexPredicate<ChangeData> {
+  private final Provider<ReviewDb> db;
+  private final ChangeIndex index;
 
-  private static final Logger log =
-      LoggerFactory.getLogger(MessagePredicate.class);
-
-  private final RevFilter rFilter;
-
-  public MessagePredicate(Provider<ReviewDb> db,
-      GitRepositoryManager repoManager, String text) {
-    super(db, repoManager, ChangeQueryBuilder.FIELD_MESSAGE, text);
-    this.rFilter = MessageRevFilter.create(text);
+  MessagePredicate(Provider<ReviewDb> db, ChangeIndex index, String value) {
+    super(ChangeField.COMMIT_MESSAGE, value);
+    this.db = db;
+    this.index = index;
   }
 
+  @SuppressWarnings("unchecked")
   @Override
-  public boolean match(Repository repo, RevWalk rw, Arguments args) {
+  public boolean match(ChangeData object) throws OrmException {
     try {
-      return rFilter.include(rw, rw.parseCommit(args.objectId));
-    } catch (MissingObjectException e) {
-      log.error(args.projectName.get() + "\" commit does not exist.", e);
-    } catch (IncorrectObjectTypeException e) {
-      log.error(args.projectName.get() + "\" revision is not a commit.", e);
-    } catch (IOException e) {
-      log.error("Could not search for commit message in \"" +
-          args.projectName.get() + "\" repository.", e);
+      for (ChangeData cData : index.getSource(
+          Predicate.and(new LegacyChangeIdPredicate(db, object.getId()), this))
+          .read()) {
+        if (cData.getId().equals(object.getId())) {
+          return true;
+        }
+      }
+    } catch (QueryParseException e) {
+      throw new OrmException(e);
     }
+
     return false;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
index ec5195b..4f36777 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
@@ -25,10 +25,10 @@
 import java.util.Collection;
 import java.util.HashSet;
 
-class OrSource extends OrPredicate<ChangeData> implements ChangeDataSource {
+public class OrSource extends OrPredicate<ChangeData> implements ChangeDataSource {
   private int cardinality = -1;
 
-  OrSource(final Collection<? extends Predicate<ChangeData>> that) {
+  public OrSource(Collection<? extends Predicate<ChangeData>> that) {
     super(that);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
index 7a85ef6..10f2d35 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
@@ -17,16 +17,17 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-class OwnerPredicate extends OperatorPredicate<ChangeData> {
+class OwnerPredicate extends IndexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
   private final Account.Id id;
 
   OwnerPredicate(Provider<ReviewDb> dbProvider, Account.Id id) {
-    super(ChangeQueryBuilder.FIELD_OWNER, id.toString());
+    super(ChangeField.OWNER, id.toString());
     this.dbProvider = dbProvider;
     this.id = id;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
new file mode 100644
index 0000000..2d2f254
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
@@ -0,0 +1,70 @@
+// 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.query.change;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gerrit.server.query.QueryParseException;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is used to extract comma separated values in a predicate
+ *
+ * If tags for the values are present (e.g. "branch=jb_2.3,vote=approved") then
+ * the args are placed in a map that maps tag to value (e.g., "branch" to "jb_2.3").
+ * If no tag is present (e.g. "jb_2.3,approved") then the args are placed into a
+ * positional list.  Args may be mixed so some may appear in the map and others
+ * in the positional list (e.g. "vote=approved,jb_2.3).
+ */
+public class PredicateArgs {
+  public List<String> positional;
+  public Map<String, String> keyValue;
+
+  /**
+   * Parses query arguments into keyValue and/or positional values
+   * labels for these arguments should be kept in ChangeQueryBuilder
+   * as ARG_ID_{argument name}.
+   *
+   * @param args - arguments to be parsed
+   *
+   * @return - the static values keyValue and positional will contain
+   *           the parsed values.
+   * @throws QueryParseException
+   */
+  PredicateArgs(String args) throws QueryParseException {
+    positional = Lists.newArrayList();
+    keyValue = Maps.newHashMap();
+
+    String[] splitArgs = args.split(",");
+
+    for (String arg : splitArgs) {
+      String[] splitKeyValue = arg.split("=");
+
+      if (splitKeyValue.length == 1) {
+        positional.add(splitKeyValue[0]);
+      } else if (splitKeyValue.length == 2) {
+        if (!keyValue.containsKey(splitKeyValue[0])) {
+          keyValue.put(splitKeyValue[0], splitKeyValue[1]);
+        } else {
+          throw new QueryParseException("Duplicate key " + splitKeyValue[0]);
+        }
+      } else {
+        throw new QueryParseException("invalid arg " + arg);
+      }
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
index cce2f2a..fe8d937 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
@@ -17,15 +17,16 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-class ProjectPredicate extends OperatorPredicate<ChangeData> {
+class ProjectPredicate extends IndexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
 
   ProjectPredicate(Provider<ReviewDb> dbProvider, String id) {
-    super(ChangeQueryBuilder.FIELD_PROJECT, id);
+    super(ChangeField.PROJECT, id);
     this.dbProvider = dbProvider;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
index 4811f9a..df0150f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
@@ -16,15 +16,16 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-class RefPredicate extends OperatorPredicate<ChangeData> {
+class RefPredicate extends IndexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
 
   RefPredicate(Provider<ReviewDb> dbProvider, String ref) {
-    super(ChangeQueryBuilder.FIELD_REF, ref);
+    super(ChangeField.REF, ref);
     this.dbProvider = dbProvider;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexBranchPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexBranchPredicate.java
deleted file mode 100644
index 6704a10..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexBranchPredicate.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2010 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.query.change;
-
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
-
-import dk.brics.automaton.RegExp;
-import dk.brics.automaton.RunAutomaton;
-
-class RegexBranchPredicate extends OperatorPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
-  private final RunAutomaton pattern;
-
-  RegexBranchPredicate(Provider<ReviewDb> dbProvider, String re) {
-    super(ChangeQueryBuilder.FIELD_BRANCH, re);
-
-    if (re.startsWith("^")) {
-      re = re.substring(1);
-    }
-
-    if (re.endsWith("$") && !re.endsWith("\\$")) {
-      re = re.substring(0, re.length() - 1);
-    }
-
-    this.dbProvider = dbProvider;
-    this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
-  }
-
-  @Override
-  public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
-    if (change == null) {
-      return false;
-    }
-    return change.getDest().get().startsWith(Branch.R_HEADS)
-        && pattern.run(change.getDest().getShortName());
-  }
-
-  @Override
-  public int getCost() {
-    return 1;
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java
index 11856e4..1ce9139 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java
@@ -15,8 +15,9 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.RegexPredicate;
 import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.query.OperatorPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
@@ -24,9 +25,10 @@
 import dk.brics.automaton.RegExp;
 import dk.brics.automaton.RunAutomaton;
 
-import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
-class RegexFilePredicate extends OperatorPredicate<ChangeData> {
+class RegexFilePredicate extends RegexPredicate<ChangeData> {
   private final Provider<ReviewDb> db;
   private final PatchListCache cache;
   private final RunAutomaton pattern;
@@ -37,7 +39,7 @@
   private final boolean prefixOnly;
 
   RegexFilePredicate(Provider<ReviewDb> db, PatchListCache plc, String re) {
-    super(ChangeQueryBuilder.FIELD_FILE, re);
+    super(ChangeField.FILE, re);
     this.db = db;
     this.cache = plc;
 
@@ -67,7 +69,7 @@
 
   @Override
   public boolean match(ChangeData object) throws OrmException {
-    String[] files = object.currentFilePaths(db, cache);
+    List<String> files = object.currentFilePaths(db, cache);
     if (files != null) {
       int begin, end;
 
@@ -76,7 +78,7 @@
         end = find(files, prefixEnd);
       } else {
         begin = 0;
-        end = files.length;
+        end = files.size();
       }
 
       if (prefixOnly) {
@@ -84,7 +86,7 @@
       }
 
       while (begin < end) {
-        if (pattern.run(files[begin++])) {
+        if (pattern.run(files.get(begin++))) {
           return true;
         }
       }
@@ -100,8 +102,8 @@
     }
   }
 
-  private static int find(String[] files, String p) {
-    int r = Arrays.binarySearch(files, p);
+  private static int find(List<String> files, String p) {
+    int r = Collections.binarySearch(files, p);
     return r < 0 ? -(r + 1) : r;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
index b8911a4..b6c724d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
@@ -17,19 +17,20 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.RegexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
 import dk.brics.automaton.RegExp;
 import dk.brics.automaton.RunAutomaton;
 
-class RegexProjectPredicate extends OperatorPredicate<ChangeData> {
+class RegexProjectPredicate extends RegexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
   private final RunAutomaton pattern;
 
   RegexProjectPredicate(Provider<ReviewDb> dbProvider, String re) {
-    super(ChangeQueryBuilder.FIELD_PROJECT, re);
+    super(ChangeField.PROJECT, re);
 
     if (re.startsWith("^")) {
       re = re.substring(1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
index 1480de6..22fb49b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
@@ -16,19 +16,20 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.RegexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
 import dk.brics.automaton.RegExp;
 import dk.brics.automaton.RunAutomaton;
 
-class RegexRefPredicate extends OperatorPredicate<ChangeData> {
+class RegexRefPredicate extends RegexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
   private final RunAutomaton pattern;
 
   RegexRefPredicate(Provider<ReviewDb> dbProvider, String re) {
-    super(ChangeQueryBuilder.FIELD_REF, re);
+    super(ChangeField.REF, re);
 
     if (re.startsWith("^")) {
       re = re.substring(1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
index 03814f8..51b9c48 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
@@ -16,19 +16,20 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.RegexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
 import dk.brics.automaton.RegExp;
 import dk.brics.automaton.RunAutomaton;
 
-class RegexTopicPredicate extends OperatorPredicate<ChangeData> {
+class RegexTopicPredicate extends RegexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
   private final RunAutomaton pattern;
 
   RegexTopicPredicate(Provider<ReviewDb> dbProvider, String re) {
-    super(ChangeQueryBuilder.FIELD_TOPIC, re);
+    super(ChangeField.TOPIC, re);
 
     if (re.startsWith("^")) {
       re = re.substring(1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index 8e910df..9e9d8bf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -17,16 +17,17 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-class ReviewerPredicate extends OperatorPredicate<ChangeData> {
+class ReviewerPredicate extends IndexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
   private final Account.Id id;
 
   ReviewerPredicate(Provider<ReviewDb> dbProvider, Account.Id id) {
-    super(ChangeQueryBuilder.FIELD_REVIEWER, id.toString());
+    super(ChangeField.REVIEWER, id.toString());
     this.dbProvider = dbProvider;
     this.id = id;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
index e7668c6..1aa17cf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
@@ -16,15 +16,17 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-abstract class SortKeyPredicate extends OperatorPredicate<ChangeData> {
+public abstract class SortKeyPredicate extends IndexPredicate<ChangeData> {
   protected final Provider<ReviewDb> dbProvider;
 
   SortKeyPredicate(Provider<ReviewDb> dbProvider, String name, String value) {
-    super(name, value);
+    super(ChangeField.SORTKEY, name, value);
     this.dbProvider = dbProvider;
   }
 
@@ -33,24 +35,47 @@
     return 1;
   }
 
-  static class Before extends SortKeyPredicate {
+  public abstract long getMinValue();
+  public abstract long getMaxValue();
+
+  public static class Before extends SortKeyPredicate {
     Before(Provider<ReviewDb> dbProvider, String value) {
       super(dbProvider, "sortkey_before", value);
     }
 
     @Override
+    public long getMinValue() {
+      return 0;
+    }
+
+    @Override
+    public long getMaxValue() {
+      return ChangeUtil.parseSortKey(getValue());
+    }
+
+    @Override
     public boolean match(ChangeData cd) throws OrmException {
       Change change = cd.change(dbProvider);
       return change != null && change.getSortKey().compareTo(getValue()) < 0;
     }
   }
 
-  static class After extends SortKeyPredicate {
+  public static class After extends SortKeyPredicate {
     After(Provider<ReviewDb> dbProvider, String value) {
       super(dbProvider, "sortkey_after", value);
     }
 
     @Override
+    public long getMinValue() {
+      return ChangeUtil.parseSortKey(getValue());
+    }
+
+    @Override
+    public long getMaxValue() {
+      return Long.MAX_VALUE;
+    }
+
+    @Override
     public boolean match(ChangeData cd) throws OrmException {
       Change change = cd.change(dbProvider);
       return change != null && change.getSortKey().compareTo(getValue()) > 0;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SqlRewriterImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SqlRewriterImpl.java
new file mode 100644
index 0000000..78b3c95
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SqlRewriterImpl.java
@@ -0,0 +1,631 @@
+// Copyright (C) 2009 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.query.change;
+
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ChangeAccess;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.query.IntPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryRewriter;
+import com.google.gerrit.server.query.RewritePredicate;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+
+import java.util.Collection;
+
+public class SqlRewriterImpl extends BasicChangeRewrites
+    implements ChangeQueryRewriter {
+  private static final QueryRewriter.Definition<ChangeData, SqlRewriterImpl> mydef =
+      new QueryRewriter.Definition<ChangeData, SqlRewriterImpl>(
+          SqlRewriterImpl.class, BUILDER);
+
+  @Inject
+  SqlRewriterImpl(Provider<ReviewDb> dbProvider) {
+    super(mydef, dbProvider);
+  }
+
+  @Override
+  public Predicate<ChangeData> and(Collection<? extends Predicate<ChangeData>> l) {
+    return hasSource(l) ? new AndSource(dbProvider, l) : super.and(l);
+  }
+
+  @Override
+  public Predicate<ChangeData> or(Collection<? extends Predicate<ChangeData>> l) {
+    return hasSource(l) ? new OrSource(l) : super.or(l);
+  }
+
+  @Rewrite("status:open P=(project:*) B=(ref:*)")
+  public Predicate<ChangeData> r05_byBranchOpen(
+      @Named("P") final ProjectPredicate p,
+      @Named("B") final RefPredicate b) {
+    return new ChangeSource(500) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a)
+          throws OrmException {
+        return a.byBranchOpenAll(
+            new Branch.NameKey(p.getValueKey(), b.getValue()));
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus().isOpen()
+            && p.match(cd)
+            && b.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:merged P=(project:*) B=(ref:*) S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r05_byBranchMergedPrev(
+      @Named("P") final ProjectPredicate p,
+      @Named("B") final RefPredicate b,
+      @Named("S") final SortKeyPredicate.After s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.byBranchClosedPrev(Change.Status.MERGED.getCode(), //
+            new Branch.NameKey(p.getValueKey(), b.getValue()), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+            && p.match(cd) //
+            && b.match(cd) //
+            && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:merged P=(project:*) B=(ref:*) S=(sortkey_before:*) L=(limit:*)")
+  public Predicate<ChangeData> r05_byBranchMergedNext(
+      @Named("P") final ProjectPredicate p,
+      @Named("B") final RefPredicate b,
+      @Named("S") final SortKeyPredicate.Before s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.byBranchClosedNext(Change.Status.MERGED.getCode(), //
+            new Branch.NameKey(p.getValueKey(), b.getValue()), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+            && p.match(cd) //
+            && b.match(cd) //
+            && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:open P=(project:*) S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r10_byProjectOpenPrev(
+      @Named("P") final ProjectPredicate p,
+      @Named("S") final SortKeyPredicate.After s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(500, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.byProjectOpenPrev(p.getValueKey(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus().isOpen() //
+            && p.match(cd) //
+            && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:open P=(project:*) S=(sortkey_before:*) L=(limit:*)")
+  public Predicate<ChangeData> r10_byProjectOpenNext(
+      @Named("P") final ProjectPredicate p,
+      @Named("S") final SortKeyPredicate.Before s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(500, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.byProjectOpenNext(p.getValueKey(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus().isOpen() //
+            && p.match(cd) //
+            && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:merged P=(project:*) S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r10_byProjectMergedPrev(
+      @Named("P") final ProjectPredicate p,
+      @Named("S") final SortKeyPredicate.After s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.byProjectClosedPrev(Change.Status.MERGED.getCode(), //
+            p.getValueKey(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+            && p.match(cd) //
+            && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:merged P=(project:*) S=(sortkey_before:*) L=(limit:*)")
+  public Predicate<ChangeData> r10_byProjectMergedNext(
+      @Named("P") final ProjectPredicate p,
+      @Named("S") final SortKeyPredicate.Before s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.byProjectClosedNext(Change.Status.MERGED.getCode(), //
+            p.getValueKey(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+            && p.match(cd) //
+            && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:abandoned P=(project:*) S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r10_byProjectAbandonedPrev(
+      @Named("P") final ProjectPredicate p,
+      @Named("S") final SortKeyPredicate.After s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.byProjectClosedPrev(Change.Status.ABANDONED.getCode(), //
+            p.getValueKey(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
+            && p.match(cd) //
+            && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:abandoned P=(project:*) S=(sortkey_before:*) L=(limit:*)")
+  public Predicate<ChangeData> r10_byProjectAbandonedNext(
+      @Named("P") final ProjectPredicate p,
+      @Named("S") final SortKeyPredicate.Before s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.byProjectClosedNext(Change.Status.ABANDONED.getCode(), //
+            p.getValueKey(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
+            && p.match(cd) //
+            && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:open S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r20_byOpenPrev(
+      @Named("S") final SortKeyPredicate.After s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(2000, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.allOpenPrev(key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus().isOpen() && s.match(cd);
+      }
+    };
+  }
+
+  @Rewrite("status:open S=(sortkey_before:*) L=(limit:*)")
+  public Predicate<ChangeData> r20_byOpenNext(
+      @Named("S") final SortKeyPredicate.Before s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(2000, s.getValue(), l.intValue()) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.allOpenNext(key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus().isOpen() && s.match(cd);
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:merged S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r20_byMergedPrev(
+      @Named("S") final SortKeyPredicate.After s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+      {
+        init("r20_byMergedPrev", s, l);
+      }
+
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.allClosedPrev(Change.Status.MERGED.getCode(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+            && s.match(cd);
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:merged S=(sortkey_before:*) L=(limit:*)")
+  public Predicate<ChangeData> r20_byMergedNext(
+      @Named("S") final SortKeyPredicate.Before s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+      {
+        init("r20_byMergedNext", s, l);
+      }
+
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.allClosedNext(Change.Status.MERGED.getCode(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+            && s.match(cd);
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:abandoned S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r20_byAbandonedPrev(
+      @Named("S") final SortKeyPredicate.After s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+      {
+        init("r20_byAbandonedPrev", s, l);
+      }
+
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.allClosedPrev(Change.Status.ABANDONED.getCode(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
+            && s.match(cd);
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:abandoned S=(sortkey_before:*) L=(limit:*)")
+  public Predicate<ChangeData> r20_byAbandonedNext(
+      @Named("S") final SortKeyPredicate.Before s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+      {
+        init("r20_byAbandonedNext", s, l);
+      }
+
+      @Override
+      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+          throws OrmException {
+        return a.allClosedNext(Change.Status.ABANDONED.getCode(), key, limit);
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
+            && s.match(cd);
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r20_byClosedPrev(
+      @Named("S") final SortKeyPredicate.After s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return or(r20_byMergedPrev(s, l), r20_byAbandonedPrev(s, l));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)")
+  public Predicate<ChangeData> r20_byClosedNext(
+      @Named("S") final SortKeyPredicate.Before s,
+      @Named("L") final IntPredicate<ChangeData> l) {
+    return or(r20_byMergedNext(s, l), r20_byAbandonedNext(s, l));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:open O=(owner:*)")
+  public Predicate<ChangeData> r25_byOwnerOpen(
+      @Named("O") final OwnerPredicate o) {
+    return new ChangeSource(50) {
+      {
+        init("r25_byOwnerOpen", o);
+      }
+
+      @Override
+      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+        return a.byOwnerOpen(o.getAccountId());
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus().isOpen() && o.match(cd);
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:closed O=(owner:*)")
+  public Predicate<ChangeData> r25_byOwnerClosed(
+      @Named("O") final OwnerPredicate o) {
+    return new ChangeSource(5000) {
+      {
+        init("r25_byOwnerClosed", o);
+      }
+
+      @Override
+      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+        return a.byOwnerClosedAll(o.getAccountId());
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus().isClosed() && o.match(cd);
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("O=(owner:*)")
+  public Predicate<ChangeData> r26_byOwner(@Named("O") OwnerPredicate o) {
+    return or(r25_byOwnerOpen(o), r25_byOwnerClosed(o));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:open R=(reviewer:*)")
+  public Predicate<ChangeData> r30_byReviewerOpen(
+      @Named("R") final ReviewerPredicate r) {
+    return new Source() {
+      {
+        init("r30_byReviewerOpen", r);
+      }
+
+      @Override
+      public ResultSet<ChangeData> read() throws OrmException {
+        return ChangeDataResultSet.patchSetApproval(dbProvider.get()
+            .patchSetApprovals().openByUser(r.getAccountId()));
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        Change change = cd.change(dbProvider);
+        return change != null && change.getStatus().isOpen() && r.match(cd);
+      }
+
+      @Override
+      public int getCardinality() {
+        return 50;
+      }
+
+      @Override
+      public int getCost() {
+        return ChangeCosts.cost(ChangeCosts.APPROVALS_SCAN, getCardinality());
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("status:closed R=(reviewer:*)")
+  public Predicate<ChangeData> r30_byReviewerClosed(
+      @Named("R") final ReviewerPredicate r) {
+    return new Source() {
+      {
+        init("r30_byReviewerClosed", r);
+      }
+
+      @Override
+      public ResultSet<ChangeData> read() throws OrmException {
+        return ChangeDataResultSet.patchSetApproval(dbProvider.get()
+            .patchSetApprovals().closedByUserAll(r.getAccountId()));
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        Change change = cd.change(dbProvider);
+        return change != null && change.getStatus().isClosed() && r.match(cd);
+      }
+
+      @Override
+      public int getCardinality() {
+        return 5000;
+      }
+
+      @Override
+      public int getCost() {
+        return ChangeCosts.cost(ChangeCosts.APPROVALS_SCAN, getCardinality());
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  @Rewrite("R=(reviewer:*)")
+  public Predicate<ChangeData> r31_byReviewer(
+      @Named("R") final ReviewerPredicate r) {
+    return or(r30_byReviewerOpen(r), r30_byReviewerClosed(r));
+  }
+
+  @Rewrite("status:submitted")
+  public Predicate<ChangeData> r99_allSubmitted() {
+    return new ChangeSource(50) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+        return a.allSubmitted();
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return cd.change(dbProvider).getStatus() == Change.Status.SUBMITTED;
+      }
+    };
+  }
+
+  @Rewrite("P=(project:*)")
+  public Predicate<ChangeData> r99_byProject(
+      @Named("P") final ProjectPredicate p) {
+    return new ChangeSource(1000000) {
+      @Override
+      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+        return a.byProject(p.getValueKey());
+      }
+
+      @Override
+      public boolean match(ChangeData cd) throws OrmException {
+        return p.match(cd);
+      }
+    };
+  }
+
+  private static boolean hasSource(Collection<? extends Predicate<ChangeData>> l) {
+    for (Predicate<ChangeData> p : l) {
+      if (p instanceof ChangeDataSource) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private abstract static class Source extends RewritePredicate<ChangeData>
+      implements ChangeDataSource {
+    @Override
+    public boolean hasChange() {
+      return false;
+    }
+  }
+
+  private abstract class ChangeSource extends Source {
+    private final int cardinality;
+
+    ChangeSource(int card) {
+      this.cardinality = card;
+    }
+
+    abstract ResultSet<Change> scan(ChangeAccess a) throws OrmException;
+
+    @Override
+    public ResultSet<ChangeData> read() throws OrmException {
+      return ChangeDataResultSet.change(scan(dbProvider.get().changes()));
+    }
+
+    @Override
+    public boolean hasChange() {
+      return true;
+    }
+
+    @Override
+    public int getCardinality() {
+      return cardinality;
+    }
+
+    @Override
+    public int getCost() {
+      return ChangeCosts.cost(ChangeCosts.CHANGES_SCAN, getCardinality());
+    }
+  }
+
+  private abstract class PaginatedSource extends ChangeSource implements
+      Paginated {
+    private final String startKey;
+    private final int limit;
+
+    PaginatedSource(int card, String start, int lim) {
+      super(card);
+      this.startKey = start;
+      this.limit = lim;
+    }
+
+    @Override
+    public int limit() {
+      return limit;
+    }
+
+    @Override
+    ResultSet<Change> scan(ChangeAccess a) throws OrmException {
+      return scan(a, startKey, limit);
+    }
+
+    @Override
+    public ResultSet<ChangeData> restart(ChangeData last) throws OrmException {
+      return ChangeDataResultSet.change(scan(dbProvider.get().changes(), //
+          last.change(dbProvider).getSortKey(), //
+          limit));
+    }
+
+    abstract ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+        throws OrmException;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
index 8d58376..9393fe1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
@@ -16,15 +16,16 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
-class TopicPredicate extends OperatorPredicate<ChangeData> {
+class TopicPredicate extends IndexPredicate<ChangeData> {
   private final Provider<ReviewDb> dbProvider;
 
   TopicPredicate(Provider<ReviewDb> dbProvider, String topic) {
-    super(ChangeQueryBuilder.FIELD_TOPIC, topic);
+    super(ChangeField.TOPIC, topic);
     this.dbProvider = dbProvider;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
index e022f86..38b3d0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
@@ -17,7 +17,8 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.TrackingId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
@@ -26,12 +27,12 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 
-class TrackingIdPredicate extends OperatorPredicate<ChangeData> implements
+class TrackingIdPredicate extends IndexPredicate<ChangeData> implements
     ChangeDataSource {
   private final Provider<ReviewDb> db;
 
   TrackingIdPredicate(Provider<ReviewDb> db, String trackingId) {
-    super(ChangeQueryBuilder.FIELD_TR, trackingId);
+    super(ChangeField.TR, trackingId);
     this.db = db;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 53930ac..ac59f65 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -147,7 +147,7 @@
     }
   }
 
-  /** Execute a SQL statement. */
+  /** Execute an SQL statement. */
   protected void execute(ReviewDb db, String sql) throws SQLException {
     Statement s = ((JdbcSchema) db).getConnection().createStatement();
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
index b5aead8..31d1953 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
@@ -28,7 +28,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-/** Parses a SQL script from a resource file and later runs it. */
+/** Parses an SQL script from a resource file and later runs it. */
 class ScriptRunner {
   private final String name;
   private final List<String> commands;
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
new file mode 100644
index 0000000..d19fd22
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
@@ -0,0 +1,17 @@
+accessDatabase = Access Database
+administrateServer = Administrate Server
+createAccount = Create Account
+createGroup = Create Group
+createProject = Create Project
+emailReviewers = Email Reviewers
+flushCaches = Flush Caches
+killTask = Kill Task
+priority = Priority
+queryLimit = Query Limit
+runAs = Run As
+runGC = Run Garbage Collection
+startReplication = Start Replication
+streamEvents = Stream Events
+viewCaches = View Caches
+viewConnections = View Connections
+viewQueue = View Queue
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.vm
new file mode 100644
index 0000000..28f29fd
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.vm
@@ -0,0 +1,33 @@
+## 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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.example file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The Footer.vm template will determine the contents of the footer text
+## appended to the end of all outgoing emails after the ChangeFooter and
+## CommentFooter.
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RebasedPatchSet.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RebasedPatchSet.vm
deleted file mode 100644
index e761627..0000000
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RebasedPatchSet.vm
+++ /dev/null
@@ -1,54 +0,0 @@
-## 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.
-##
-##
-## Template Type:
-## -------------
-## This is a velocity mail template, see: http://velocity.apache.org and the
-## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
-##
-## Template File Names and extensions:
-## ----------------------------------
-## Gerrit will use templates ending in ".vm" but will ignore templates ending
-## in ".vm.example".  If a .vm template does not exist, the default internal
-## gerrit template which is the same as the .vm.example will be used.  If you
-## want to override the default template, copy the .vm.example file to a .vm
-## file and edit it appropriately.
-##
-## This Template:
-## --------------
-## The RebasedPatchSet.vm template will determine the contents of the email
-## related to a user rebasing a patchset for a change through the Gerrit UI.
-## It is a ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
-##
-#if($email.reviewerNames)
-Hello $email.joinStrings($email.reviewerNames, ', '),
-
-I'd like you to reexamine a rebased change.#if($email.changeUrl)  Please visit
-
-    $email.changeUrl
-
-to look at the new rebased patch set (#$patchSet.patchSetId).
-#end
-#else
-$fromName has created a new patch set by issuing a rebase in Gerrit (#$patchSet.patchSetId).
-#end
-
-Change subject: $change.subject
-......................................................................
-
-$email.changeDetail
-#if($email.sshHost)
-  git pull ssh://$email.sshHost/$projectName $patchSet.refName
-#end
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
index 9b122eb..117c57b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
@@ -96,11 +96,21 @@
     }
 
     @Override
+    public ProjectState checkedGet(Project.NameKey projectName) {
+      return get(projectName);
+    }
+
+    @Override
     public void evict(Project p) {
       throw new UnsupportedOperationException();
     }
 
     @Override
+    public void evict(Project.NameKey p) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void remove(Project p) {
       throw new UnsupportedOperationException();
     }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/ChangeJsonTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/ChangeJsonTest.java
deleted file mode 100644
index 14e0ebf..0000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/ChangeJsonTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-// 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 static org.easymock.EasyMock.anyBoolean;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.gerrit.common.changes.ListChangesOption;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-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.Project;
-import com.google.gerrit.reviewdb.server.ChangeAccess;
-import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
-import com.google.gerrit.reviewdb.server.PatchSetAccess;
-import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.Realm;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.change.ChangeJson.ChangeMessageInfo;
-import com.google.gerrit.server.config.AnonymousCowardName;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gwtorm.server.ListResultSet;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Binder;
-import com.google.inject.Guice;
-import com.google.inject.Module;
-
-import junit.framework.TestCase;
-
-import org.easymock.EasyMock;
-import org.easymock.IAnswer;
-import org.eclipse.jgit.lib.Config;
-
-import java.sql.Timestamp;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-public class ChangeJsonTest extends TestCase {
-
-  public void testFormatChangeMessages() throws OrmException {
-
-    // create mocks
-    final CurrentUser currentUser = createMock(CurrentUser.class);
-    final GitRepositoryManager grm = createMock(GitRepositoryManager.class);
-    final AccountByEmailCache abec = createMock(AccountByEmailCache.class);
-    final AccountCache ac = createMock(AccountCache.class);
-    final AccountInfo.Loader.Factory alf =
-        createMock(AccountInfo.Loader.Factory.class);
-    final CapabilityControl.Factory ccf =
-        createMock(CapabilityControl.Factory.class);
-    final GroupBackend gb = createMock(GroupBackend.class);
-    final Realm r = createMock(Realm.class);
-    final PatchListCache plc = createMock(PatchListCache.class);
-    final ProjectCache pc = createMock(ProjectCache.class);
-    final Config config = new Config();  // unable to mock
-    final ReviewDb rdb = createMock(ReviewDb.class);
-    final ChangeAccess ca = createMock(ChangeAccess.class);
-    final PatchSetAccess psa = createMock(PatchSetAccess.class);
-    final PatchSetApprovalAccess psaa =
-        createMock(PatchSetApprovalAccess.class);
-    final ChangeMessageAccess cma = createMock(ChangeMessageAccess.class);
-    AccountInfo.Loader accountLoader = createMock(AccountInfo.Loader.class);
-
-    // create ChangeJson instance
-    Module mod = new Module() {
-      @Override
-      public void configure(Binder binder) {
-        binder.bind(CurrentUser.class).toInstance(currentUser);
-        binder.bind(GitRepositoryManager.class).toInstance(grm);
-        binder.bind(AccountByEmailCache.class).toInstance(abec);
-        binder.bind(AccountCache.class).toInstance(ac);
-        binder.bind(AccountInfo.Loader.Factory.class).toInstance(alf);
-        binder.bind(CapabilityControl.Factory.class).toInstance(ccf);
-        binder.bind(GroupBackend.class).toInstance(gb);
-        binder.bind(Realm.class).toInstance(r);
-        binder.bind(PatchListCache.class).toInstance(plc);
-        binder.bind(ProjectCache.class).toInstance(pc);
-        binder.bind(ReviewDb.class).toInstance(rdb);
-        binder.bind(Config.class).annotatedWith(GerritServerConfig.class)
-            .toInstance(config);
-        binder.bind(String.class).annotatedWith(CanonicalWebUrl.class)
-            .toInstance("");
-        binder.bind(String.class).annotatedWith(AnonymousCowardName.class)
-            .toInstance("");
-      }
-    };
-    ChangeJson json = Guice.createInjector(mod).getInstance(ChangeJson.class);
-
-    // define mock behavior for tests
-    expect(alf.create(anyBoolean())).andReturn(accountLoader).anyTimes();
-
-    Project.NameKey proj = new Project.NameKey("ProjectNameKey");
-    Branch.NameKey forBranch = new Branch.NameKey(proj, "BranchNameKey");
-
-    Change.Key changeKey123 = new Change.Key("ChangeKey123");
-    Change.Id changeId123 = new Change.Id(123);
-    Change change123 = new Change(changeKey123, changeId123, null, forBranch);
-
-    Change.Key changeKey234 = new Change.Key("ChangeKey234");
-    Change.Id changeId234 = new Change.Id(234);
-    Change change234 = new Change(changeKey234, changeId234, null, forBranch);
-
-    expect(ca.get(Sets.newHashSet(changeId123)))
-        .andAnswer(results(Change.class, change123)).anyTimes();
-    expect(ca.get(changeId123)).andReturn(change123).anyTimes();
-    expect(ca.get(Sets.newHashSet(changeId234)))
-        .andAnswer(results(Change.class, change234));
-    expect(ca.get(changeId234)).andReturn(change234);
-    expect(rdb.changes()).andReturn(ca).anyTimes();
-
-    expect(psa.get(EasyMock.<Iterable<PatchSet.Id>>anyObject()))
-        .andAnswer(results(PatchSet.class)).anyTimes();
-    expect(rdb.patchSets()).andReturn(psa).anyTimes();
-
-    expect(psaa.byPatchSet(anyObject(PatchSet.Id.class)))
-        .andAnswer(results(PatchSetApproval.class)).anyTimes();
-    expect(rdb.patchSetApprovals()).andReturn(psaa).anyTimes();
-
-    expect(currentUser.getStarredChanges())
-        .andReturn(Collections.<Change.Id>emptySet()).anyTimes();
-
-    long timeBase = System.currentTimeMillis();
-    ChangeMessage changeMessage1 =changeMessage(
-        changeId123, "cm1", 111, timeBase, 1111, "first message");
-    ChangeMessage changeMessage2 = changeMessage(
-        changeId123, "cm2", 222, timeBase + 1000, 1111, "second message");
-    expect(cma.byChange(changeId123))
-        .andAnswer(results(ChangeMessage.class, changeMessage2, changeMessage1))
-        .anyTimes();
-    expect(cma.byChange(changeId234)).andAnswer(results(ChangeMessage.class));
-    expect(rdb.changeMessages()).andReturn(cma).anyTimes();
-
-    expect(accountLoader.get(anyObject(Account.Id.class)))
-        .andAnswer(accountForId()).anyTimes();
-    accountLoader.fill();
-    expectLastCall().anyTimes();
-
-    replay(rdb, ca, psa, psaa, alf, currentUser, cma, accountLoader);
-
-    // test 1: messages not returned by default
-    ChangeInfo ci = json.format(new ChangeData(changeId123));
-    assertNull(ci.messages);
-
-    json.addOption(ListChangesOption.MESSAGES);
-
-    // test 2: two change messages, in chronological order
-    ci = json.format(new ChangeData(changeId123));
-    assertNotNull(ci.messages);
-    assertEquals(2, ci.messages.size());
-    Iterator<ChangeMessageInfo> cmis = ci.messages.iterator();
-    assertEquals(changeMessage1, cmis.next());
-    assertEquals(changeMessage2, cmis.next());
-
-    // test 3: no change messages
-    ci = json.format(new ChangeData(changeId234));
-    assertNotNull(ci.messages);
-    assertEquals(0, ci.messages.size());
-  }
-
-  private static IAnswer<AccountInfo> accountForId() {
-    return new IAnswer<AccountInfo>() {
-      @Override
-      public AccountInfo answer() throws Throwable {
-        Account.Id id = (Account.Id) EasyMock.getCurrentArguments()[0];
-        AccountInfo ai = new AccountInfo(id);
-        return ai;
-      }};
-  }
-
-  private static <T> IAnswer<ResultSet<T>> results(Class<T> type, T... items) {
-    final List<T> list = Lists.newArrayList(items);
-    return new IAnswer<ResultSet<T>>() {
-      @Override
-      public ResultSet<T> answer() throws Throwable {
-        return new ListResultSet<T>(list);
-      }};
-  }
-
-  private static void assertEquals(ChangeMessage cm, ChangeMessageInfo cmi) {
-    assertEquals(cm.getPatchSetId().get(), (int) cmi._revisionNumber);
-    assertEquals(cm.getMessage(), cmi.message);
-    assertEquals(cm.getKey().get(), cmi.id);
-    assertEquals(cm.getWrittenOn(), cmi.date);
-    assertNotNull(cmi.author);
-    assertEquals(cm.getAuthor(), cmi.author._id);
-  }
-
-  private static ChangeMessage changeMessage(Change.Id changeId,
-      String uuid, int accountId, long time, int psId, String message) {
-    ChangeMessage.Key key = new ChangeMessage.Key(changeId, uuid);
-    Account.Id author = new Account.Id(accountId);
-    Timestamp updated = new Timestamp(time);
-    PatchSet.Id ps = new PatchSet.Id(changeId, psId);
-    ChangeMessage changeMessage = new ChangeMessage(key, author, updated, ps);
-    changeMessage.setMessage(message);
-    return changeMessage;
-  }
-}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
index 21d1ce4..6a9a93f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
@@ -22,6 +22,7 @@
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -36,7 +37,6 @@
 import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.change.CommentInfo.Side;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.AbstractModule;
@@ -216,7 +216,7 @@
     PatchLineComment plc =
         new PatchLineComment(id, line, authorId, inReplyToUuid);
     plc.setMessage(message);
-    plc.setSide(side == CommentInfo.Side.PARENT ? (short) 0 : (short) 1);
+    plc.setSide(side == Side.PARENT ? (short) 0 : (short) 1);
     plc.setStatus(Status.PUBLISHED);
     plc.setWrittenOn(new Timestamp(millis));
     return plc;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java
new file mode 100644
index 0000000..f97ca55
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java
@@ -0,0 +1,39 @@
+// 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.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.server.config.ListCapabilities.CapabilityInfo;
+
+import org.junit.Test;
+
+import java.util.Map;
+
+public class ListCapabilitiesTest {
+  @Test
+  public void testList() throws Exception {
+    Map<String, CapabilityInfo> m =
+        new ListCapabilities().apply(new ConfigResource());
+    for (String id : GlobalCapability.getAllNames()) {
+      assertTrue("contains " + id, m.containsKey(id));
+      assertEquals(id, m.get(id).id);
+      assertNotNull(id + " has name", m.get(id).name);
+    }
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
index 9a8fc00..0087df6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
@@ -21,10 +21,9 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.util.UUID;
 
 public class SitePathsTest extends TestCase {
-  public void testCreate_NotExisting() throws FileNotFoundException {
+  public void testCreate_NotExisting() throws IOException {
     final File root = random();
     final SitePaths site = new SitePaths(root);
     assertTrue(site.isNew);
@@ -32,7 +31,7 @@
     assertEquals(new File(root, "etc"), site.etc_dir);
   }
 
-  public void testCreate_Empty() throws FileNotFoundException {
+  public void testCreate_Empty() throws IOException {
     final File root = random();
     try {
       assertTrue(root.mkdir());
@@ -91,8 +90,11 @@
     assertEquals(new File(pfx + "a").getCanonicalFile(), site.resolve(pfx + "a"));
   }
 
-  private File random() {
-    final File t = new File("target");
-    return new File(t, "random-name-" + UUID.randomUUID().toString());
+  private static File random() throws IOException {
+    File tmp = File.createTempFile("gerrit_test_", "_site");
+    if (!tmp.delete()) {
+      throw new IOException("Cannot create " + tmp.getPath());
+    }
+    return tmp;
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
index a849e68..0d5207a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -159,9 +159,8 @@
         + "[contributor-agreement \"Individual\"]\n" //
         + "  description = A new description\n" //
         + "  accepted = group Staff\n" //
-        + "  agreementUrl = http://www.example.com/agree\n" //
-        + "[project]\n"//
-        + "\tstate = active\n", text(rev, "project.config"));
+        + "  agreementUrl = http://www.example.com/agree\n",
+        text(rev, "project.config"));
   }
 
   @Test
@@ -188,9 +187,7 @@
         + "  submit = group People Who Can Submit\n" //
         + "\tsubmit = group Staff\n" //
         + "  upload = group Developers\n" //
-        + "  read = group Developers\n"//
-        + "[project]\n"//
-        + "\tstate = active\n", text(rev, "project.config"));
+        + "  read = group Developers\n", text(rev, "project.config"));
   }
 
   private ProjectConfig read(RevCommit rev) throws IOException,
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
new file mode 100644
index 0000000..e91c3a0
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
@@ -0,0 +1,317 @@
+// 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.index;
+
+import static com.google.gerrit.reviewdb.client.Change.Status.ABANDONED;
+import static com.google.gerrit.reviewdb.client.Change.Status.DRAFT;
+import static com.google.gerrit.reviewdb.client.Change.Status.MERGED;
+import static com.google.gerrit.reviewdb.client.Change.Status.NEW;
+import static com.google.gerrit.reviewdb.client.Change.Status.SUBMITTED;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.AndSource;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.OrSource;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+
+import junit.framework.TestCase;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+@SuppressWarnings("unchecked")
+public class IndexRewriteTest extends TestCase {
+  private static Schema<ChangeData> V1 = new Schema<ChangeData>(
+      1, false, ImmutableList.<FieldDef<ChangeData, ?>> of(
+          ChangeField.STATUS));
+
+  private static Schema<ChangeData> V2 = new Schema<ChangeData>(
+      2, false, ImmutableList.of(
+          ChangeField.STATUS,
+          ChangeField.FILE));
+
+  private static class DummyIndex implements ChangeIndex {
+    private final Schema<ChangeData> schema;
+
+    private DummyIndex(Schema<ChangeData> schema) {
+      this.schema = schema;
+    }
+
+    @Override
+    public ListenableFuture<Void> insert(ChangeData cd) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ListenableFuture<Void> replace(ChangeData cd) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ListenableFuture<Void> delete(ChangeData cd) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void deleteAll() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ChangeDataSource getSource(Predicate<ChangeData> p)
+        throws QueryParseException {
+      return new Source(p);
+    }
+
+    @Override
+    public Schema<ChangeData> getSchema() {
+      return schema;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public void markReady(boolean ready) {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private static class Source implements ChangeDataSource {
+    private final Predicate<ChangeData> p;
+
+    Source(Predicate<ChangeData> p) {
+      this.p = p;
+    }
+
+    @Override
+    public int getCardinality() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasChange() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ResultSet<ChangeData> read() throws OrmException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String toString() {
+      return p.toString();
+    }
+  }
+
+  public class QueryBuilder extends ChangeQueryBuilder {
+    QueryBuilder() {
+      super(
+          new QueryBuilder.Definition<ChangeData, QueryBuilder>(
+            QueryBuilder.class),
+          new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
+            null, null, null, null, null, indexes),
+          null);
+    }
+
+    @Operator
+    public Predicate<ChangeData> foo(String value) {
+      return predicate("foo", value);
+    }
+
+    @Operator
+    public Predicate<ChangeData> bar(String value) {
+      return predicate("bar", value);
+    }
+
+    private Predicate<ChangeData> predicate(String name, String value) {
+      return new OperatorPredicate<ChangeData>(name, value) {
+        @Override
+        public boolean match(ChangeData object) throws OrmException {
+          return false;
+        }
+
+        @Override
+        public int getCost() {
+          return 0;
+        }
+      };
+    }
+  }
+
+  private DummyIndex index;
+  private IndexCollection indexes;
+  private ChangeQueryBuilder queryBuilder;
+  private IndexRewriteImpl rewrite;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    index = new DummyIndex(V2);
+    indexes = new IndexCollection();
+    indexes.setSearchIndex(index);
+    queryBuilder = new QueryBuilder();
+    rewrite = new IndexRewriteImpl(
+        indexes,
+        null,
+        new IndexRewriteImpl.BasicRewritesImpl(null));
+  }
+
+  public void testIndexPredicate() throws Exception {
+    Predicate<ChangeData> in = parse("file:a");
+    assertEquals(query(in), rewrite(in));
+  }
+
+  public void testNonIndexPredicate() throws Exception {
+    Predicate<ChangeData> in = parse("foo:a");
+    assertSame(in, rewrite(in));
+  }
+
+  public void testIndexPredicates() throws Exception {
+    Predicate<ChangeData> in = parse("file:a file:b");
+    assertEquals(query(in), rewrite(in));
+  }
+
+  public void testNonIndexPredicates() throws Exception {
+    Predicate<ChangeData> in = parse("foo:a OR foo:b");
+    assertEquals(in, rewrite(in));
+  }
+
+  public void testOneIndexPredicate() throws Exception {
+    Predicate<ChangeData> in = parse("foo:a file:b");
+    Predicate<ChangeData> out = rewrite(in);
+    assertSame(AndSource.class, out.getClass());
+    assertEquals(
+        ImmutableList.of(query(in.getChild(1)), in.getChild(0)),
+        out.getChildren());
+  }
+
+  public void testThreeLevelTreeWithAllIndexPredicates() throws Exception {
+    Predicate<ChangeData> in =
+        parse("-status:abandoned (status:open OR status:merged)");
+    assertEquals(
+        query(parse("status:new OR status:submitted OR status:draft OR status:merged")),
+        rewrite.rewrite(in));
+  }
+
+  public void testThreeLevelTreeWithSomeIndexPredicates() throws Exception {
+    Predicate<ChangeData> in = parse("-foo:a (file:b OR file:c)");
+    Predicate<ChangeData> out = rewrite(in);
+    assertEquals(AndSource.class, out.getClass());
+    assertEquals(
+        ImmutableList.of(query(in.getChild(1)), in.getChild(0)),
+        out.getChildren());
+  }
+
+  public void testMultipleIndexPredicates() throws Exception {
+    Predicate<ChangeData> in =
+        parse("file:a OR foo:b OR file:c OR foo:d");
+    Predicate<ChangeData> out = rewrite(in);
+    assertSame(OrSource.class, out.getClass());
+    assertEquals(ImmutableList.of(
+          query(Predicate.or(in.getChild(0), in.getChild(2))),
+          in.getChild(1), in.getChild(3)),
+        out.getChildren());
+  }
+
+  public void testIndexAndNonIndexPredicates() throws Exception {
+    Predicate<ChangeData> in = parse("status:new bar:p file:a");
+    Predicate<ChangeData> out = rewrite(in);
+    assertSame(AndSource.class, out.getClass());
+    assertEquals(ImmutableList.of(
+          query(Predicate.and(in.getChild(0), in.getChild(2))),
+          in.getChild(1)),
+        out.getChildren());
+  }
+
+  public void testDuplicateCompoundNonIndexOnlyPredicates() throws Exception {
+    Predicate<ChangeData> in =
+        parse("(status:new OR status:draft) bar:p file:a");
+    Predicate<ChangeData> out = rewrite(in);
+    assertSame(AndSource.class, out.getClass());
+    assertEquals(ImmutableList.of(
+          query(Predicate.and(in.getChild(0), in.getChild(2))),
+          in.getChild(1)),
+        out.getChildren());
+  }
+
+  public void testDuplicateCompoundIndexOnlyPredicates() throws Exception {
+    Predicate<ChangeData> in =
+        parse("(status:new OR file:a) bar:p file:b");
+    Predicate<ChangeData> out = rewrite(in);
+    assertSame(AndSource.class, out.getClass());
+    assertEquals(ImmutableList.of(
+          query(Predicate.and(in.getChild(0), in.getChild(2))),
+          in.getChild(1)),
+        out.getChildren());
+  }
+
+  public void testGetPossibleStatus() throws Exception {
+    assertEquals(EnumSet.allOf(Change.Status.class), status("file:a"));
+    assertEquals(EnumSet.of(NEW), status("is:new"));
+    assertEquals(EnumSet.of(SUBMITTED, DRAFT, MERGED, ABANDONED),
+        status("-is:new"));
+    assertEquals(EnumSet.of(NEW, MERGED), status("is:new OR is:merged"));
+
+    EnumSet<Change.Status> none = EnumSet.noneOf(Change.Status.class);
+    assertEquals(none, status("is:new is:merged"));
+    assertEquals(none, status("(is:new is:draft) (is:merged is:submitted)"));
+    assertEquals(none, status("(is:new is:draft) (is:merged is:submitted)"));
+
+    assertEquals(EnumSet.of(MERGED, SUBMITTED),
+        status("(is:new is:draft) OR (is:merged OR is:submitted)"));
+  }
+
+  public void testUnsupportedIndexOperator() throws Exception {
+    Predicate<ChangeData> in = parse("status:merged file:a");
+    assertEquals(query(in), rewrite(in));
+
+    indexes.setSearchIndex(new DummyIndex(V1));
+    Predicate<ChangeData> out = rewrite(in);
+    assertTrue(out instanceof AndPredicate);
+    assertEquals(ImmutableList.of(
+          query(in.getChild(0)),
+          in.getChild(1)),
+        out.getChildren());
+  }
+
+  private Predicate<ChangeData> parse(String query) throws QueryParseException {
+    return queryBuilder.parse(query);
+  }
+
+  private Predicate<ChangeData> rewrite(Predicate<ChangeData> in) {
+    return rewrite.rewrite(in);
+  }
+
+  private IndexedChangeQuery query(Predicate<ChangeData> p)
+      throws QueryParseException {
+    return new IndexedChangeQuery(index, p);
+  }
+
+  private Set<Change.Status> status(String query) throws QueryParseException {
+    return IndexRewriteImpl.getPossibleStatus(parse(query));
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index f1bb7de..968d661 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -420,10 +420,19 @@
       }
 
       @Override
+      public ProjectState checkedGet(Project.NameKey projectName) {
+        return get(projectName);
+      }
+
+      @Override
       public void evict(Project p) {
       }
 
       @Override
+      public void evict(Project.NameKey p) {
+      }
+
+      @Override
       public void remove(Project p) {
       }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexFilePredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexFilePredicateTest.java
index ce7b25c..1500272 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexFilePredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexFilePredicateTest.java
@@ -76,7 +76,7 @@
   private static ChangeData change(String... files) {
     Arrays.sort(files);
     ChangeData cd = new ChangeData(new Change.Id(1));
-    cd.setCurrentFilePaths(files);
+    cd.setCurrentFilePaths(Arrays.asList(files));
     return cd;
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
index b517de7..47ff6b0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
@@ -52,6 +52,8 @@
 
 import static org.junit.Assert.fail;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.io.ByteStreams;
 
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
@@ -64,10 +66,13 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.List;
+import java.util.Map;
 
 public abstract class HookTestCase extends LocalDiskRepositoryTestCase {
   protected Repository repository;
-  private File hooksh;
+  private final Map<String, File> hooks = Maps.newTreeMap();
+  private final List<File> cleanup = Lists.newArrayList();
 
   @Override
   @Before
@@ -79,15 +84,21 @@
   @Override
   @After
   public void tearDown() throws Exception {
-    if (hooksh != null) {
-      if (!hooksh.delete()) {
-        hooksh.deleteOnExit();
+    super.tearDown();
+    for (File p : cleanup) {
+      if (!p.delete()) {
+        p.deleteOnExit();
       }
-      hooksh = null;
     }
+    cleanup.clear();
   }
 
   protected File getHook(final String name) throws IOException {
+    File hook = hooks.get(name);
+    if (hook != null) {
+      return hook;
+    }
+
     final String scproot = "com/google/gerrit/server/tools/root";
     final String path = scproot + "/hooks/" + name;
     URL url = cl().getResource(path);
@@ -95,17 +106,22 @@
       fail("Cannot locate " + path + " in CLASSPATH");
     }
 
-    File hook;
     if ("file".equals(url.getProtocol())) {
       hook = new File(url.getPath());
       if (!hook.isFile()) {
         fail("Cannot locate " + path + " in CLASSPATH");
       }
+      long time = hook.lastModified();
+      hook.setExecutable(true);
+      hook.setLastModified(time);
+      hooks.put(name, hook);
+      return hook;
     } else if ("jar".equals(url.getProtocol())) {
-      hooksh = File.createTempFile("hook_", ".sh");
       InputStream in = url.openStream();
       try {
-        FileOutputStream out = new FileOutputStream(hooksh);
+        hook = File.createTempFile("hook_", ".sh");
+        cleanup.add(hook);
+        FileOutputStream out = new FileOutputStream(hook);
         try {
           ByteStreams.copy(in, out);
         } finally {
@@ -114,21 +130,13 @@
       } finally {
         in.close();
       }
-      hook = hooksh;
+      hook.setExecutable(true);
+      hooks.put(name, hook);
+      return hook;
     } else {
       fail("Cannot invoke " + url);
-      hook = null;
+      return null;
     }
-
-    // The hook was copied out of our source control system into the
-    // target area by Java tools. Its not executable in the source
-    // are, nor did the copying Java program make it executable in the
-    // destination area. So we must force it to be executable.
-    //
-    final long time = hook.lastModified();
-    hook.setExecutable(true);
-    hook.setLastModified(time);
-    return hook;
   }
 
   private ClassLoader cl() {
diff --git a/gerrit-solr/BUCK b/gerrit-solr/BUCK
new file mode 100644
index 0000000..4176561
--- /dev/null
+++ b/gerrit-solr/BUCK
@@ -0,0 +1,19 @@
+java_library(
+  name = 'solr',
+  srcs = glob(['src/main/java/**/*.java']),
+  deps = [
+    '//gerrit-antlr:query_exception',
+    '//gerrit-extension-api:api',
+    '//gerrit-lucene:query_builder',
+    '//gerrit-reviewdb:client',
+    '//gerrit-server:server',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
+    '//lib/log:api',
+    '//lib/lucene:core',
+    '//lib/solr:solrj',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java
new file mode 100644
index 0000000..7e32bac
--- /dev/null
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java
@@ -0,0 +1,80 @@
+// 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.git;
+
+package com.google.gerrit.solr;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeSchemas;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+class IndexVersionCheck implements LifecycleListener {
+  public static final Map<String, Integer> SCHEMA_VERSIONS = ImmutableMap.of(
+      SolrChangeIndex.CHANGES_OPEN, ChangeSchemas.getLatest().getVersion(),
+      SolrChangeIndex.CHANGES_CLOSED, ChangeSchemas.getLatest().getVersion());
+
+  public static File solrIndexConfig(SitePaths sitePaths) {
+    return new File(sitePaths.index_dir, "gerrit_index.config");
+  }
+
+  private final SitePaths sitePaths;
+
+  @Inject
+  IndexVersionCheck(SitePaths sitePaths) {
+    this.sitePaths = sitePaths;
+  }
+
+  @Override
+  public void start() {
+    // TODO Query schema version from a special meta-document
+    File file = solrIndexConfig(sitePaths);
+    try {
+      FileBasedConfig cfg = new FileBasedConfig(file, FS.detect());
+      cfg.load();
+      for (Map.Entry<String, Integer> e : SCHEMA_VERSIONS.entrySet()) {
+        int schemaVersion = cfg.getInt("index", e.getKey(), "schemaVersion", 0);
+        if (schemaVersion != e.getValue()) {
+          throw new ProvisionException(String.format(
+              "wrong index schema version for \"%s\": expected %d, found %d%s",
+              e.getKey(), e.getValue(), schemaVersion, upgrade()));
+        }
+      }
+    } catch (IOException e) {
+      throw new ProvisionException("unable to read " + file);
+    } catch (ConfigInvalidException e) {
+      throw new ProvisionException("invalid config file " + file);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // Do nothing.
+  }
+
+  private final String upgrade() {
+    return "\nRun reindex to rebuild the index:\n"
+        + "$ java -jar gerrit.war reindex -d "
+        + sitePaths.site_path.getAbsolutePath();
+  }
+}
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
new file mode 100644
index 0000000..b95978e
--- /dev/null
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
@@ -0,0 +1,339 @@
+// 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.git;
+
+package com.google.gerrit.solr;
+
+import static com.google.gerrit.server.index.IndexRewriteImpl.CLOSED_STATUSES;
+import static com.google.gerrit.server.index.IndexRewriteImpl.OPEN_STATUSES;
+import static com.google.gerrit.solr.IndexVersionCheck.SCHEMA_VERSIONS;
+import static com.google.gerrit.solr.IndexVersionCheck.solrIndexConfig;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lucene.QueryBuilder;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
+import com.google.gerrit.server.index.FieldType;
+import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.IndexRewriteImpl;
+import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeDataSource;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+
+import org.apache.lucene.search.Query;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrServer;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.CloudSolrServer;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrInputDocument;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Secondary index implementation using a remote Solr instance. */
+class SolrChangeIndex implements ChangeIndex, LifecycleListener {
+  public static final String CHANGES_OPEN = "changes_open";
+  public static final String CHANGES_CLOSED = "changes_closed";
+  private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
+
+  private final FillArgs fillArgs;
+  private final SitePaths sitePaths;
+  private final IndexCollection indexes;
+  private final CloudSolrServer openIndex;
+  private final CloudSolrServer closedIndex;
+  private final Schema<ChangeData> schema;
+
+  SolrChangeIndex(
+      @GerritServerConfig Config cfg,
+      FillArgs fillArgs,
+      SitePaths sitePaths,
+      IndexCollection indexes,
+      Schema<ChangeData> schema,
+      String base) throws IOException {
+    this.fillArgs = fillArgs;
+    this.sitePaths = sitePaths;
+    this.indexes = indexes;
+    this.schema = schema;
+
+    String url = cfg.getString("index", "solr", "url");
+    if (Strings.isNullOrEmpty(url)) {
+      throw new IllegalStateException("index.solr.url must be supplied");
+    }
+
+    base = Strings.nullToEmpty(base);
+    openIndex = new CloudSolrServer(url);
+    openIndex.setDefaultCollection(base + CHANGES_OPEN);
+
+    closedIndex = new CloudSolrServer(url);
+    closedIndex.setDefaultCollection(base + CHANGES_CLOSED);
+  }
+
+  @Override
+  public void start() {
+    indexes.setSearchIndex(this);
+    indexes.addWriteIndex(this);
+  }
+
+  @Override
+  public void stop() {
+    openIndex.shutdown();
+    closedIndex.shutdown();
+  }
+
+  @Override
+  public Schema<ChangeData> getSchema() {
+    return schema;
+  }
+
+  @Override
+  public void close() {
+    stop();
+  }
+
+  @Override
+  public ListenableFuture<Void> insert(ChangeData cd) throws IOException {
+    String id = cd.getId().toString();
+    SolrInputDocument doc = toDocument(cd);
+    try {
+      if (cd.getChange().getStatus().isOpen()) {
+        closedIndex.deleteById(id);
+        openIndex.add(doc);
+      } else {
+        openIndex.deleteById(id);
+        closedIndex.add(doc);
+      }
+    } catch (SolrServerException e) {
+      throw new IOException(e);
+    }
+    commit(openIndex);
+    commit(closedIndex);
+    return Futures.immediateFuture(null);
+  }
+
+  @Override
+  public ListenableFuture<Void> replace(ChangeData cd) throws IOException {
+    String id = cd.getId().toString();
+    SolrInputDocument doc = toDocument(cd);
+    try {
+      if (cd.getChange().getStatus().isOpen()) {
+        closedIndex.deleteById(id);
+        openIndex.add(doc);
+      } else {
+        openIndex.deleteById(id);
+        closedIndex.add(doc);
+      }
+    } catch (SolrServerException e) {
+      throw new IOException(e);
+    }
+    commit(openIndex);
+    commit(closedIndex);
+    return Futures.immediateFuture(null);
+  }
+
+  @Override
+  public ListenableFuture<Void> delete(ChangeData cd) throws IOException {
+    String id = cd.getId().toString();
+    try {
+      if (cd.getChange().getStatus().isOpen()) {
+        openIndex.deleteById(id);
+        commit(openIndex);
+      } else {
+        closedIndex.deleteById(id);
+        commit(closedIndex);
+      }
+      return Futures.immediateFuture(null);
+    } catch (SolrServerException e) {
+      throw new IOException(e);
+    }
+  }
+
+  @Override
+  public void deleteAll() throws IOException {
+    try {
+      openIndex.deleteByQuery("*:*");
+      closedIndex.deleteByQuery("*:*");
+    } catch (SolrServerException e) {
+      throw new IOException(e);
+    }
+    commit(openIndex);
+    commit(closedIndex);
+  }
+
+  @Override
+  public ChangeDataSource getSource(Predicate<ChangeData> p)
+      throws QueryParseException {
+    Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
+    List<SolrServer> indexes = Lists.newArrayListWithCapacity(2);
+    if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
+      indexes.add(openIndex);
+    }
+    if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
+      indexes.add(closedIndex);
+    }
+    return new QuerySource(indexes, QueryBuilder.toQuery(p));
+  }
+
+  private void commit(SolrServer server) throws IOException {
+    try {
+      server.commit();
+    } catch (SolrServerException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private class QuerySource implements ChangeDataSource {
+    private final List<SolrServer> indexes;
+    private final SolrQuery query;
+
+    public QuerySource(List<SolrServer> indexes, Query q) {
+      this.indexes = indexes;
+
+      query = new SolrQuery(q.toString());
+      query.setParam("shards.tolerant", true);
+      query.setFields(ID_FIELD);
+      query.setSort(
+          ChangeField.UPDATED.getName(),
+          SolrQuery.ORDER.desc);
+    }
+
+    @Override
+    public int getCardinality() {
+      return 10; // TODO: estimate from solr?
+    }
+
+    @Override
+    public boolean hasChange() {
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return query.getQuery();
+    }
+
+    @Override
+    public ResultSet<ChangeData> read() throws OrmException {
+      try {
+        // TODO Sort documents during merge to select only top N.
+        SolrDocumentList docs = new SolrDocumentList();
+        for (SolrServer index : indexes) {
+          docs.addAll(index.query(query).getResults());
+        }
+
+        List<ChangeData> result = Lists.newArrayListWithCapacity(docs.size());
+        for (SolrDocument doc : docs) {
+          Integer v = (Integer) doc.getFieldValue(ID_FIELD);
+          result.add(new ChangeData(new Change.Id(v.intValue())));
+        }
+
+        final List<ChangeData> r = Collections.unmodifiableList(result);
+        return new ResultSet<ChangeData>() {
+          @Override
+          public Iterator<ChangeData> iterator() {
+            return r.iterator();
+          }
+
+          @Override
+          public List<ChangeData> toList() {
+            return r;
+          }
+
+          @Override
+          public void close() {
+            // Do nothing.
+          }
+        };
+      } catch (SolrServerException e) {
+        throw new OrmException(e);
+      }
+    }
+  }
+
+  private SolrInputDocument toDocument(ChangeData cd) throws IOException {
+    try {
+      SolrInputDocument result = new SolrInputDocument();
+      for (FieldDef<ChangeData, ?> f : schema.getFields().values()) {
+        if (f.isRepeatable()) {
+          add(result, f, (Iterable<?>) f.get(cd, fillArgs));
+        } else {
+          add(result, f, Collections.singleton(f.get(cd, fillArgs)));
+        }
+      }
+      return result;
+    } catch (OrmException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private void add(SolrInputDocument doc, FieldDef<ChangeData, ?> f,
+      Iterable<?> values) throws OrmException {
+    if (f.getType() == FieldType.INTEGER) {
+      for (Object value : values) {
+        doc.addField(f.getName(), (Integer) value);
+      }
+    } else if (f.getType() == FieldType.LONG) {
+      for (Object value : values) {
+        doc.addField(f.getName(), (Long) value);
+      }
+    } else if (f.getType() == FieldType.TIMESTAMP) {
+      for (Object v : values) {
+        doc.addField(f.getName(), QueryBuilder.toIndexTime((Timestamp) v));
+      }
+    } else if (f.getType() == FieldType.EXACT
+        || f.getType() == FieldType.PREFIX
+        || f.getType() == FieldType.FULL_TEXT) {
+      for (Object value : values) {
+        doc.addField(f.getName(), (String) value);
+      }
+    } else {
+      throw QueryBuilder.badFieldType(f.getType());
+    }
+  }
+
+  @Override
+  public void markReady(boolean ready) throws IOException {
+    // TODO Move the schema version information to a special meta-document
+    FileBasedConfig cfg = new FileBasedConfig(
+        solrIndexConfig(sitePaths),
+        FS.detect());
+    for (Map.Entry<String, Integer> e : SCHEMA_VERSIONS.entrySet()) {
+      cfg.setInt("index", e.getKey(), "schemaVersion",
+          ready ? e.getValue() : -1);
+    }
+    cfg.save();
+  }
+}
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
new file mode 100644
index 0000000..8c614f7
--- /dev/null
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
@@ -0,0 +1,66 @@
+// 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.git;
+
+package com.google.gerrit.solr;
+
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.ChangeSchemas;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
+import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.IndexModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+
+public class SolrIndexModule extends LifecycleModule {
+  private final boolean checkVersion;
+  private final int threads;
+  private final String base;
+
+  public SolrIndexModule() {
+    this(true, 0, null);
+  }
+
+  public SolrIndexModule(boolean checkVersion, int threads, String base) {
+    this.checkVersion = checkVersion;
+    this.threads = threads;
+    this.base = base;
+  }
+
+  @Override
+  protected void configure() {
+    install(new IndexModule(threads));
+    bind(ChangeIndex.class).to(SolrChangeIndex.class);
+    listener().to(SolrChangeIndex.class);
+    if (checkVersion) {
+      listener().to(IndexVersionCheck.class);
+    }
+  }
+
+  @Provides
+  @Singleton
+  public SolrChangeIndex getChangeIndex(@GerritServerConfig Config cfg,
+      SitePaths sitePaths,
+      IndexCollection indexes,
+      FillArgs fillArgs) throws IOException {
+    return new SolrChangeIndex(cfg, fillArgs, sitePaths, indexes,
+        ChangeSchemas.getLatest(), base);
+  }
+}
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
new file mode 100644
index 0000000..93a3ef7
--- /dev/null
+++ b/gerrit-sshd/BUCK
@@ -0,0 +1,39 @@
+SRCS = glob(['src/main/java/**/*.java'])
+
+java_library2(
+  name = 'sshd',
+  srcs = SRCS,
+  deps = [
+    '//gerrit-extension-api:api',
+    '//gerrit-cache-h2:cache-h2',
+    '//gerrit-common:server',
+    '//gerrit-patch-jgit:server',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//gerrit-util-cli:cli',
+    '//lib:args4j',
+    '//lib:gson',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:jsch',
+    '//lib/commons:codec',
+    '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
+    '//lib/guice:guice-servlet',  # SSH should not depend on servlet
+    '//lib/log:api',
+    '//lib/log:log4j',
+    '//lib/mina:core',
+    '//lib/mina:sshd',
+    '//lib/jgit:jgit',
+  ],
+  compile_deps = [
+    '//lib/bouncycastle:bcprov',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_sources(
+  name = 'sshd-src',
+  srcs = SRCS,
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
deleted file mode 100644
index 75a43c0..0000000
--- a/gerrit-sshd/pom.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-sshd</artifactId>
-  <name>Gerrit Code Review - SSHd</name>
-
-  <description>
-    Java SSH daemon with Gerrit commands and Git support
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>log4j</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.junit</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.mina</groupId>
-      <artifactId>mina-core</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.sshd</groupId>
-      <artifactId>sshd-core</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-api</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-util-cli</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-server</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-cache-h2</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/Commands.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/Commands.java
index 929a895..1a5e62cc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/Commands.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/Commands.java
@@ -16,7 +16,6 @@
 
 import com.google.inject.Key;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.sshd.server.Command;
 
 import java.lang.annotation.Annotation;
@@ -124,7 +123,7 @@
     NestedCommandNameImpl(final CommandName parent, final String name) {
       this.parent = parent;
       this.name = name;
-      this.descr = StringUtils.EMPTY;
+      this.descr = "";
     }
 
     NestedCommandNameImpl(final CommandName parent, final String name,
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
index ccf23e1..298a099 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PeerDaemonUser;
+import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.sshd.SshScope.Context;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -45,6 +46,7 @@
   private final SshScope sshScope;
   private final DispatchCommandProvider dispatcher;
 
+  private boolean enableRunAs;
   private Provider<CurrentUser> caller;
   private Provider<SshSession> session;
   private IdentifiedUser.GenericFactory userFactory;
@@ -66,36 +68,34 @@
       @CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher,
       final Provider<CurrentUser> caller, final Provider<SshSession> session,
       final IdentifiedUser.GenericFactory userFactory,
-      final SshScope.Context callingContext) {
+      final SshScope.Context callingContext,
+      AuthConfig config) {
     this.sshScope = sshScope;
     this.dispatcher = dispatcher;
     this.caller = caller;
     this.session = session;
     this.userFactory = userFactory;
     this.callingContext = callingContext;
+    this.enableRunAs = config.isRunAsEnabled();
     atomicCmd = Atomics.newReference();
   }
 
   @Override
   public void start(Environment env) throws IOException {
     try {
-      if (caller.get() instanceof PeerDaemonUser) {
-        parseCommandLine();
+      checkCanRunAs();
+      parseCommandLine();
 
-        final Context ctx = callingContext.subContext(newSession(), join(args));
-        final Context old = sshScope.set(ctx);
-        try {
-          final BaseCommand cmd = dispatcher.get();
-          cmd.setArguments(args.toArray(new String[args.size()]));
-          provideStateTo(cmd);
-          atomicCmd.set(cmd);
-          cmd.start(env);
-        } finally {
-          sshScope.set(old);
-        }
-
-      } else {
-        throw new UnloggedFailure(1, "fatal: Not a peer daemon");
+      final Context ctx = callingContext.subContext(newSession(), join(args));
+      final Context old = sshScope.set(ctx);
+      try {
+        final BaseCommand cmd = dispatcher.get();
+        cmd.setArguments(args.toArray(new String[args.size()]));
+        provideStateTo(cmd);
+        atomicCmd.set(cmd);
+        cmd.start(env);
+      } finally {
+        sshScope.set(old);
       }
     } catch (UnloggedFailure e) {
       String msg = e.getMessage();
@@ -108,6 +108,17 @@
     }
   }
 
+  private void checkCanRunAs() throws UnloggedFailure {
+    if (caller.get() instanceof PeerDaemonUser) {
+      // OK.
+    } else if (!enableRunAs) {
+      throw new UnloggedFailure(1,
+          "fatal: suexec disabled by auth.enableRunAs = false");
+    } else if (!caller.get().getCapabilities().canRunAs()) {
+      throw new UnloggedFailure(1, "fatal: suexec not permitted");
+    }
+  }
+
   private SshSession newSession() {
     final SocketAddress peer;
     if (peerAddress == null) {
@@ -115,8 +126,12 @@
     } else {
       peer = peerAddress;
     }
+    CurrentUser self = caller.get();
+    if (self instanceof PeerDaemonUser) {
+      self = null;
+    }
     return new SshSession(session.get(), peer,
-        userFactory.create(peer, accountId));
+        userFactory.runAs(peer, accountId, self));
   }
 
   private static String join(List<String> args) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index faf2224..e83963a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -17,18 +17,23 @@
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ListChildProjects;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -73,6 +78,9 @@
   @Inject
   private AllProjectsName allProjectsName;
 
+  @Inject
+  private Provider<ListChildProjects> listChildProjects;
+
   private Project.NameKey newParentKey = null;
 
   @Override
@@ -108,17 +116,16 @@
       }
     }
 
-    final List<Project> childProjects = new ArrayList<Project>();
+    final List<Project.NameKey> childProjects = Lists.newArrayList();
     for (final ProjectControl pc : children) {
-      childProjects.add(pc.getProject());
+      childProjects.add(pc.getProject().getNameKey());
     }
     if (oldParent != null) {
       childProjects.addAll(getChildrenForReparenting(oldParent));
     }
 
-    for (final Project project : childProjects) {
-      final String name = project.getName();
-      final Project.NameKey nameKey = project.getNameKey();
+    for (final Project.NameKey nameKey : childProjects) {
+      final String name = nameKey.get();
 
       if (allProjectsName.equals(nameKey)) {
         // Don't allow the wild card project to have a parent.
@@ -159,7 +166,7 @@
         err.append("error: " + msg + "\n");
       }
 
-      projectCache.evict(project);
+      projectCache.evict(nameKey);
     }
 
     if (err.length() > 0) {
@@ -175,8 +182,8 @@
    * reparented. The returned list of child projects does not contain projects
    * that were specified to be excluded from reparenting.
    */
-  private List<Project> getChildrenForReparenting(final ProjectControl parent) {
-    final List<Project> childProjects = new ArrayList<Project>();
+  private List<Project.NameKey> getChildrenForReparenting(final ProjectControl parent) {
+    final List<Project.NameKey> childProjects = Lists.newArrayList();
     final List<Project.NameKey> excluded =
       new ArrayList<Project.NameKey>(excludedChildren.size());
     for (final ProjectControl excludedChild : excludedChildren) {
@@ -187,11 +194,12 @@
     if (newParentKey != null) {
       automaticallyExcluded.addAll(getAllParents(newParentKey));
     }
-    for (final Project child : getChildren(parent.getProject().getNameKey())) {
-      final Project.NameKey childName = child.getNameKey();
+    for (final ProjectInfo child : listChildProjects.get().apply(
+        new ProjectResource(parent))) {
+      final Project.NameKey childName = new Project.NameKey(child.name);
       if (!excluded.contains(childName)) {
         if (!automaticallyExcluded.contains(childName)) {
-          childProjects.add(child);
+          childProjects.add(childName);
         } else {
           stdout.println("Automatically excluded '" + childName + "' " +
                          "from reparenting because it is in the parent " +
@@ -213,20 +221,4 @@
         }
       }));
   }
-
-  private List<Project> getChildren(final Project.NameKey parentName) {
-    final List<Project> childProjects = new ArrayList<Project>();
-    for (final Project.NameKey projectName : projectCache.all()) {
-      final ProjectState e = projectCache.get(projectName);
-      if (e == null) {
-        // If we can't get it from the cache, pretend it's not present.
-        continue;
-      }
-
-      if (parentName.equals(e.getProject().getParent(allProjectsName))) {
-        childProjects.add(e.getProject());
-      }
-    }
-    return childProjects;
-  }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
index 500c84a..7f9e5db 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
@@ -28,10 +28,8 @@
 
   protected SortedSet<String> cacheNames() {
     SortedSet<String> names = Sets.newTreeSet();
-    for (String plugin : cacheMap.plugins()) {
-      for (String name : cacheMap.byPlugin(plugin).keySet()) {
-        names.add(cacheNameOf(plugin, name));
-      }
+    for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
+      names.add(cacheNameOf(e.getPluginName(), e.getExportName()));
     }
     return names;
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
index c209249..6c4b41c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -14,23 +14,16 @@
 
 package com.google.gerrit.sshd.commands;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.common.errors.InvalidSshKeyException;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.AccountSshKey;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gerrit.server.account.CreateAccount;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
@@ -42,8 +35,6 @@
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 
 /** Create a new user account. **/
@@ -69,94 +60,29 @@
   private String username;
 
   @Inject
-  private IdentifiedUser currentUser;
-
-  @Inject
-  private ReviewDb db;
-
-  @Inject
-  private SshKeyCache sshKeyCache;
-
-  @Inject
-  private AccountCache accountCache;
-
-  @Inject
-  private AccountByEmailCache byEmailCache;
+  private CreateAccount.Factory createAccountFactory;
 
   @Override
-  protected void run() throws OrmException, IOException,
-      InvalidSshKeyException, UnloggedFailure {
-    if (!username.matches(Account.USER_NAME_PATTERN)) {
-      throw die("Username '" + username + "'"
-          + " must contain only letters, numbers, _, - or .");
-    }
-
-    final Account.Id id = new Account.Id(db.nextAccountId());
-    final AccountSshKey key = readSshKey(id);
-
-    AccountExternalId extUser =
-        new AccountExternalId(id, new AccountExternalId.Key(
-            AccountExternalId.SCHEME_USERNAME, username));
-
-    if (httpPassword != null) {
-      extUser.setPassword(httpPassword);
-    }
-
-    if (db.accountExternalIds().get(extUser.getKey()) != null) {
-      throw die("username '" + username + "' already exists");
-    }
-    if (email != null && db.accountExternalIds().get(getEmailKey()) != null) {
-      throw die("email '" + email + "' already exists");
-    }
-
+  protected void run() throws OrmException, IOException, UnloggedFailure {
+    CreateAccount.Input input = new CreateAccount.Input();
+    input.username = username;
+    input.email = email;
+    input.name = fullName;
+    input.sshKey = readSshKey();
+    input.httpPassword = httpPassword;
+    input.groups = Lists.transform(groups, new Function<AccountGroup.Id, String>() {
+      @Override
+      public String apply(AccountGroup.Id id) {
+        return id.toString();
+      }});
     try {
-      db.accountExternalIds().insert(Collections.singleton(extUser));
-    } catch (OrmDuplicateKeyException duplicateKey) {
-      throw die("username '" + username + "' already exists");
+      createAccountFactory.create(username).apply(TopLevelResource.INSTANCE, input);
+    } catch (RestApiException e) {
+      throw die(e.getMessage());
     }
-
-    if (email != null) {
-      AccountExternalId extMailto = new AccountExternalId(id, getEmailKey());
-      extMailto.setEmailAddress(email);
-      try {
-        db.accountExternalIds().insert(Collections.singleton(extMailto));
-      } catch (OrmDuplicateKeyException duplicateKey) {
-        try {
-          db.accountExternalIds().delete(Collections.singleton(extUser));
-        } catch (OrmException cleanupError) {
-        }
-        throw die("email '" + email + "' already exists");
-      }
-    }
-
-    Account a = new Account(id);
-    a.setFullName(fullName);
-    a.setPreferredEmail(email);
-    db.accounts().insert(Collections.singleton(a));
-
-    if (key != null) {
-      db.accountSshKeys().insert(Collections.singleton(key));
-    }
-
-    for (AccountGroup.Id groupId : new HashSet<AccountGroup.Id>(groups)) {
-      AccountGroupMember m =
-          new AccountGroupMember(new AccountGroupMember.Key(id, groupId));
-      db.accountGroupMembersAudit().insert(Collections.singleton( //
-          new AccountGroupMemberAudit(m, currentUser.getAccountId())));
-      db.accountGroupMembers().insert(Collections.singleton(m));
-    }
-
-    sshKeyCache.evict(username);
-    accountCache.evictByUsername(username);
-    byEmailCache.evict(email);
   }
 
-  private AccountExternalId.Key getEmailKey() {
-    return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
-  }
-
-  private AccountSshKey readSshKey(final Account.Id id)
-      throws UnsupportedEncodingException, IOException, InvalidSshKeyException {
+  private String readSshKey() throws UnsupportedEncodingException, IOException {
     if (sshKey == null) {
       return null;
     }
@@ -169,6 +95,6 @@
         sshKey += line + "\n";
       }
     }
-    return sshKeyCache.create(new AccountSshKey.Id(id, 1), sshKey.trim());
+    return sshKey;
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index bd624ae..89bb973 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -106,6 +106,9 @@
   @Option(name = "--empty-commit", usage = "to create initial empty commit")
   private boolean createEmptyCommit;
 
+  @Option(name = "--max-object-size-limit", usage = "max Git object size for this project")
+  private String maxObjectSizeLimit;
+
   private String projectName;
 
   @Argument(index = 0, metaVar = "NAME", usage = "name of project to be created")
@@ -143,6 +146,7 @@
         args.changeIdRequired = requireChangeID;
         args.branch = branch;
         args.createEmptyCommit = createEmptyCommit;
+        args.maxObjectSizeLimit = maxObjectSizeLimit;
 
         final PerformCreateProject createProject = factory.create(args);
         createProject.createProject();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 35a4e14..521103b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -39,6 +39,7 @@
     command(gerrit, BanCommitCommand.class);
     command(gerrit, FlushCaches.class);
     command(gerrit, ListProjectsCommand.class);
+    command(gerrit, ListMembersCommand.class);
     command(gerrit, ListGroupsCommand.class);
     command(gerrit, LsUserRefs.class);
     command(gerrit, Query.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
index 13abdf4..4b78cfc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -17,17 +17,16 @@
 import com.google.common.cache.Cache;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.sshd.BaseCommand;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import org.kohsuke.args4j.Option;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.SortedSet;
 
 /** Causes the caches to purge all entries and reload. */
@@ -98,16 +97,13 @@
 
   private void doBulkFlush() {
     try {
-      for (String plugin : cacheMap.plugins()) {
-        for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
-            cacheMap.byPlugin(plugin).entrySet()) {
-          String n = cacheNameOf(plugin, entry.getKey());
-          if (flush(n)) {
-            try {
-              entry.getValue().get().invalidateAll();
-            } catch (Throwable err) {
-              stderr.println("error: cannot flush cache \"" + n + "\": " + err);
-            }
+      for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
+        String n = cacheNameOf(e.getPluginName(), e.getExportName());
+        if (flush(n)) {
+          try {
+            e.getProvider().get().invalidateAll();
+          } catch (Throwable err) {
+            stderr.println("error: cannot flush cache \"" + n + "\": " + err);
           }
         }
       }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
new file mode 100644
index 0000000..c5bda3c
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -0,0 +1,116 @@
+// 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.sshd.commands;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupDetailFactory.Factory;
+import com.google.gerrit.server.group.ListMembers;
+import com.google.gerrit.server.ioutil.ColumnFormatter;
+import com.google.gerrit.sshd.BaseCommand;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gwtorm.server.OrmException;
+
+import org.apache.sshd.server.Environment;
+import org.kohsuke.args4j.Argument;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Implements a command that allows the user to see the members of a group.
+ */
+@CommandMetaData(name = "ls-members", descr = "Lists the members of a given group")
+public class ListMembersCommand extends BaseCommand {
+  @Inject
+  ListMembersCommandImpl impl;
+
+  @Override
+  public void start(Environment env) {
+    startThread(new CommandRunnable() {
+      @Override
+      public void run() throws Exception {
+        parseCommandLine(impl);
+        final PrintWriter stdout = toPrintWriter(out);
+        try {
+          impl.display(stdout);
+        } finally {
+          stdout.flush();
+        }
+      }
+    });
+  }
+
+  private static class ListMembersCommandImpl extends ListMembers {
+    @Argument(required = true, usage = "the name of the group", metaVar = "GROUPNAME")
+    private String name;
+
+    private final GroupCache groupCache;
+
+    @Inject
+    protected ListMembersCommandImpl(GroupCache groupCache,
+        Factory groupDetailFactory,
+        AccountInfo.Loader.Factory accountLoaderFactory,
+        AccountCache accountCache) {
+      super(groupCache, groupDetailFactory, accountLoaderFactory);
+      this.groupCache = groupCache;
+    }
+
+    void display(PrintWriter writer) throws UnloggedFailure, OrmException {
+      AccountGroup group = groupCache.get(new AccountGroup.NameKey(name));
+      String errorText = "Group not found or not visible\n";
+
+      if (group == null) {
+        writer.write(errorText);
+        writer.flush();
+        return;
+      }
+
+      try {
+        List<AccountInfo> members = apply(group.getGroupUUID());
+        ColumnFormatter formatter = new ColumnFormatter(writer, '\t');
+        formatter.addColumn("id");
+        formatter.addColumn("username");
+        formatter.addColumn("full name");
+        formatter.addColumn("email");
+        formatter.nextLine();
+        for (AccountInfo member : members) {
+          if (member == null) {
+            continue;
+          }
+
+          formatter.addColumn(member._id.toString());
+          formatter.addColumn(Objects.firstNonNull(member.username, "n/a"));
+          formatter.addColumn(Objects.firstNonNull(
+              Strings.emptyToNull(member.name), "n/a"));
+          formatter.addColumn(Objects.firstNonNull(member.email, "n/a"));
+          formatter.nextLine();
+        }
+
+        formatter.finish();
+      } catch (MethodNotAllowedException e) {
+        writer.write(errorText);
+        writer.flush();
+      }
+    }
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
index 30f3c95..abe202e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
@@ -39,6 +39,7 @@
     // deprecated alias to review command
     alias(gerrit, "approve", ReviewCommand.class);
     command(gerrit, SetAccountCommand.class);
+    command(gerrit, SetMembersCommand.class);
     command(gerrit, SetProjectCommand.class);
 
     command(gerrit, "test-submit").toProvider(new DispatchCommandProvider(testSubmit));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index e0dd38f..28f20cf 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -94,7 +94,7 @@
     final ReceivePack rp = receive.getReceivePack();
     rp.setRefLogIdent(currentUser.newRefLogIdent());
     rp.setTimeout(config.getTimeout());
-    rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit());
+    rp.setMaxObjectSizeLimit(getMaxObjectSizeLimit());
     try {
       receive.advertiseHistory();
       rp.receive(in, out, err);
@@ -170,4 +170,15 @@
       }
     }
   }
+
+  private long getMaxObjectSizeLimit() {
+    long global = config.getMaxObjectSizeLimit();
+    long local = projectControl.getProjectState().getMaxObjectSizeLimit();
+    if (global > 0 && local > 0) {
+      return Math.min(global, local);
+    } else {
+      // zero means "no limit", in this case the max is more limiting
+      return Math.max(global, local);
+    }
+  }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 5769a22..b48320f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -105,10 +105,6 @@
   @Option(name = "--submit", aliases = "-s", usage = "submit the specified patch set(s)")
   private boolean submitChange;
 
-  @Option(name = "--force-message", usage = "publish the message, "
-      + "even if the label score cannot be applied due to the change being closed")
-  private boolean forceMessage = false;
-
   @Option(name = "--publish", usage = "publish the specified draft patch set(s)")
   private boolean publishPatchSet;
 
@@ -233,6 +229,7 @@
     review.labels = Maps.newTreeMap();
     review.drafts = PostReview.DraftHandling.PUBLISH;
     review.strictLabels = false;
+    review.waitForCommit = true;
     for (ApproveOption ao : optionList) {
       Short v = ao.value();
       if (v != null) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index a3e9d6e..d515aab 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -14,32 +14,40 @@
 
 package com.google.gerrit.sshd.commands;
 
-
-import com.google.gerrit.common.errors.InvalidSshKeyException;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.restapi.RawInput;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Account.FieldName;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.client.AccountSshKey;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountException;
-import com.google.gerrit.server.account.AccountManager;
-import com.google.gerrit.server.account.AuthRequest;
-import com.google.gerrit.server.account.Realm;
-import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.account.AddSshKey;
+import com.google.gerrit.server.account.CreateEmail;
+import com.google.gerrit.server.account.DeleteActive;
+import com.google.gerrit.server.account.DeleteEmail;
+import com.google.gerrit.server.account.DeleteSshKey;
+import com.google.gerrit.server.account.GetEmails;
+import com.google.gerrit.server.account.GetEmails.EmailInfo;
+import com.google.gerrit.server.account.GetSshKeys;
+import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
+import com.google.gerrit.server.account.PutActive;
+import com.google.gerrit.server.account.PutHttpPassword;
+import com.google.gerrit.server.account.PutName;
 import com.google.gerrit.sshd.BaseCommand;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.apache.sshd.server.Environment;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
@@ -81,19 +89,40 @@
   private IdentifiedUser currentUser;
 
   @Inject
-  private ReviewDb db;
+  private IdentifiedUser.GenericFactory genericUserFactory;
 
   @Inject
-  private AccountManager manager;
+  private CreateEmail.Factory createEmailFactory;
 
   @Inject
-  private SshKeyCache sshKeyCache;
+  private Provider<GetEmails> getEmailsProvider;
 
   @Inject
-  private AccountCache byIdCache;
+  private Provider<DeleteEmail> deleteEmailProvider;
 
   @Inject
-  private Realm realm;
+  private Provider<PutName> putNameProvider;
+
+  @Inject
+  private Provider<PutHttpPassword> putHttpPasswordProvider;
+
+  @Inject
+  private Provider<PutActive> putActiveProvider;
+
+  @Inject
+  private Provider<DeleteActive> deleteActiveProvider;
+
+  @Inject
+  private Provider<AddSshKey> addSshKeyProvider;
+
+  @Inject
+  private Provider<GetSshKeys> getSshKeysProvider;
+
+  @Inject
+  private Provider<DeleteSshKey> deleteSshKeyProvider;
+
+  private IdentifiedUser user;
+  private AccountResource rsrc;
 
   @Override
   public void start(final Environment env) {
@@ -131,144 +160,127 @@
   }
 
   private void setAccount() throws OrmException, IOException, UnloggedFailure {
-
-    final Account account = db.accounts().get(id);
-    boolean accountUpdated = false;
-    boolean sshKeysUpdated = false;
-
-    for (String email : addEmails) {
-      link(id, email);
-    }
-
-    for (String email : deleteEmails) {
-      deleteMail(id, email);
-    }
-
-    if (fullName != null) {
-      if (realm.allowsEdit(FieldName.FULL_NAME)) {
-        account.setFullName(fullName);
-        accountUpdated = true;
-      } else {
-        throw new UnloggedFailure(1, "The realm doesn't allow editing names");
+    user = genericUserFactory.create(id);
+    rsrc = new AccountResource(user);
+    try {
+      for (String email : addEmails) {
+        addEmail(email);
       }
-    }
 
-    if (httpPassword != null) {
-      setHttpPassword(id, httpPassword);
-    }
+      for (String email : deleteEmails) {
+        deleteEmail(email);
+      }
 
-    if (active) {
-      accountUpdated = true;
-      account.setActive(true);
-    } else if (inactive) {
-      accountUpdated = true;
-      account.setActive(false);
-    }
+      if (fullName != null) {
+        PutName.Input in = new PutName.Input();
+        in.name = fullName;
+        putNameProvider.get().apply(rsrc, in);
+      }
 
-    addSshKeys = readSshKey(addSshKeys);
-    if (!addSshKeys.isEmpty()) {
-      sshKeysUpdated = true;
-      addSshKeys(addSshKeys, account);
-    }
+      if (httpPassword != null) {
+        PutHttpPassword.Input in = new PutHttpPassword.Input();
+        in.httpPassword = httpPassword;
+        putHttpPasswordProvider.get().apply(rsrc, in);
+      }
 
-    deleteSshKeys = readSshKey(deleteSshKeys);
-    if (!deleteSshKeys.isEmpty()) {
-      sshKeysUpdated = true;
-      deleteSshKeys(deleteSshKeys, account);
-    }
+      if (active) {
+        putActiveProvider.get().apply(rsrc, null);
+      } else if (inactive) {
+        try {
+          deleteActiveProvider.get().apply(rsrc, null);
+        } catch (ResourceNotFoundException e) {
+          // user is already inactive
+        }
+      }
 
-    if (accountUpdated) {
-      db.accounts().update(Collections.singleton(account));
-      byIdCache.evict(id);
-    }
+      addSshKeys = readSshKey(addSshKeys);
+      if (!addSshKeys.isEmpty()) {
+        addSshKeys(addSshKeys);
+      }
 
-    if (sshKeysUpdated) {
-      sshKeyCache.evict(account.getUserName());
+      deleteSshKeys = readSshKey(deleteSshKeys);
+      if (!deleteSshKeys.isEmpty()) {
+        deleteSshKeys(deleteSshKeys);
+      }
+    } catch (RestApiException e) {
+      throw die(e.getMessage());
     }
   }
 
-  private void addSshKeys(final List<String> keys, final Account account)
-      throws OrmException, UnloggedFailure {
-    List<AccountSshKey> accountKeys = new ArrayList<AccountSshKey>();
-    int seq = db.accountSshKeys().byAccount(account.getId()).toList().size();
-    for (String key : keys) {
-      try {
-        seq++;
-        AccountSshKey accountSshKey = sshKeyCache.create(
-            new AccountSshKey.Id(account.getId(), seq), key.trim());
-        accountKeys.add(accountSshKey);
-      } catch (InvalidSshKeyException e) {
-        throw new UnloggedFailure(1, "fatal: invalid ssh key");
-      }
+  private void addSshKeys(List<String> sshKeys) throws RestApiException,
+      UnloggedFailure, OrmException, IOException {
+    for (final String sshKey : sshKeys) {
+      AddSshKey.Input in = new AddSshKey.Input();
+      in.raw = new RawInput() {
+        @Override
+        public InputStream getInputStream() throws IOException {
+          return new ByteArrayInputStream(sshKey.getBytes("UTF-8"));
+        }
+
+        @Override
+        public String getContentType() {
+          return "plain/text";
+        }
+
+        @Override
+        public long getContentLength() {
+          return sshKey.length();
+        }
+      };
+      addSshKeyProvider.get().apply(rsrc, in);
     }
-    db.accountSshKeys().insert(accountKeys);
   }
 
-  private void deleteSshKeys(final List<String> keys, final Account account)
-      throws OrmException {
-    ResultSet<AccountSshKey> allKeys = db.accountSshKeys().byAccount(account.getId());
-    if (keys.contains("ALL")) {
-      db.accountSshKeys().delete(allKeys);
+  private void deleteSshKeys(List<String> sshKeys) throws RestApiException,
+      OrmException {
+    List<SshKeyInfo> infos = getSshKeysProvider.get().apply(rsrc);
+    if (sshKeys.contains("ALL")) {
+      for (SshKeyInfo i : infos) {
+        deleteSshKey(i);
+      }
     } else {
-      List<AccountSshKey> accountKeys = new ArrayList<AccountSshKey>();
-      for (String key : keys) {
-        for (AccountSshKey accountSshKey : allKeys) {
-          if (key.trim().equals(accountSshKey.getSshPublicKey())
-              || accountSshKey.getComment().trim().equals(key)) {
-            accountKeys.add(accountSshKey);
+      for (String sshKey : sshKeys) {
+        for (SshKeyInfo i : infos) {
+          if (sshKey.trim().equals(i.sshPublicKey)
+              || sshKey.trim().equals(i.comment)) {
+            deleteSshKey(i);
           }
         }
       }
-      db.accountSshKeys().delete(accountKeys);
     }
   }
 
-  private void deleteMail(Account.Id id, final String mailAddress)
-      throws UnloggedFailure, OrmException {
-    if (mailAddress.equals("ALL")) {
-      ResultSet<AccountExternalId> ids = db.accountExternalIds().byAccount(id);
-      for (AccountExternalId extId : ids) {
-        if (extId.isScheme(AccountExternalId.SCHEME_MAILTO)) {
-          unlink(id, extId.getEmailAddress());
-        }
+  private void deleteSshKey(SshKeyInfo i) throws OrmException {
+    AccountSshKey sshKey = new AccountSshKey(
+        new AccountSshKey.Id(user.getAccountId(), i.seq), i.sshPublicKey);
+    deleteSshKeyProvider.get().apply(
+        new AccountResource.SshKey(user, sshKey), null);
+  }
+
+  private void addEmail(String email) throws UnloggedFailure, RestApiException,
+      OrmException {
+    CreateEmail.Input in = new CreateEmail.Input();
+    in.email = email;
+    in.noConfirmation = true;
+    try {
+      createEmailFactory.create(email).apply(rsrc, in);
+    } catch (EmailException e) {
+      throw die(e.getMessage());
+    }
+  }
+
+  private void deleteEmail(String email) throws UnloggedFailure,
+      RestApiException, OrmException {
+    if (email.equals("ALL")) {
+      List<EmailInfo> emails = getEmailsProvider.get().apply(rsrc);
+      DeleteEmail deleteEmail = deleteEmailProvider.get();
+      for (EmailInfo e : emails) {
+        deleteEmail.apply(new AccountResource.Email(user, e.email),
+            new DeleteEmail.Input());
       }
     } else {
-      AccountExternalId.Key key = new AccountExternalId.Key(
-          AccountExternalId.SCHEME_MAILTO, mailAddress);
-      AccountExternalId extId = db.accountExternalIds().get(key);
-      if (extId != null) {
-        unlink(id, mailAddress);
-      }
-    }
-  }
-
-  private void setHttpPassword(Account.Id id, final String httpPassword)
-      throws UnloggedFailure, OrmException {
-    ResultSet<AccountExternalId> ids = db.accountExternalIds().byAccount(id);
-    for (AccountExternalId extId: ids) {
-      if (extId.isScheme(AccountExternalId.SCHEME_USERNAME)) {
-        extId.setPassword(httpPassword);
-        db.accountExternalIds().update(Collections.singleton(extId));
-        byIdCache.evict(id);
-      }
-    }
-  }
-
-  private void unlink(Account.Id id, final String mailAddress)
-      throws UnloggedFailure {
-    try {
-      manager.unlink(id, AuthRequest.forEmail(mailAddress));
-    } catch (AccountException ex) {
-      throw die(ex.getMessage());
-    }
-  }
-
-  private void link(Account.Id id, final String mailAddress)
-      throws UnloggedFailure {
-    try {
-      manager.link(id, AuthRequest.forEmail(mailAddress));
-    } catch (AccountException ex) {
-      throw die(ex.getMessage());
+      deleteEmailProvider.get().apply(new AccountResource.Email(user, email),
+          new DeleteEmail.Input());
     }
   }
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
new file mode 100644
index 0000000..cd2710f
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
@@ -0,0 +1,164 @@
+// 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.sshd.commands;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.group.AddIncludedGroups;
+import com.google.gerrit.server.group.AddMembers;
+import com.google.gerrit.server.group.DeleteIncludedGroups;
+import com.google.gerrit.server.group.DeleteMembers;
+import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.group.GroupsCollection;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+@CommandMetaData(name = "set-members", descr = "Modifies members of specific group or number of groups")
+public class SetMembersCommand extends SshCommand {
+
+  @Option(name = "--add", aliases = {"-a"}, metaVar = "USER", usage = "users that should be added as group member")
+  private List<Account.Id> accountsToAdd = Lists.newArrayList();
+
+  @Option(name = "--remove", aliases = {"-r"}, metaVar = "USER", usage = "users that should be removed from the group")
+  private List<Account.Id> accountsToRemove = Lists.newArrayList();
+
+  @Option(name = "--include", aliases = {"-i"}, metaVar = "GROUP", usage = "group that should be included as group member")
+  private List<AccountGroup.UUID> groupsToInclude = Lists.newArrayList();
+
+  @Option(name = "--exclude", aliases = {"-e"}, metaVar = "GROUP", usage = "group that should be excluded from the group")
+  private List<AccountGroup.UUID> groupsToRemove = Lists.newArrayList();
+
+  @Argument(index = 0, required = true, multiValued = true, metaVar = "GROUP", usage = "groups to modify")
+  private List<AccountGroup.UUID> groups = Lists.newArrayList();
+
+  @Inject
+  private Provider<AddMembers> addMembers;
+
+  @Inject
+  private Provider<DeleteMembers> deleteMembers;
+
+  @Inject
+  private Provider<AddIncludedGroups> addIncludedGroups;
+
+  @Inject
+  private Provider<DeleteIncludedGroups> deleteIncludedGroups;
+
+  @Inject
+  private GroupsCollection groupsCollection;
+
+  @Inject
+  private GroupCache groupCache;
+
+  @Inject
+  private AccountCache accountCache;
+
+  @Override
+  protected void run() throws UnloggedFailure, Failure, Exception {
+    for (AccountGroup.UUID groupUuid : groups) {
+      GroupResource resource =
+          groupsCollection.parse(TopLevelResource.INSTANCE,
+              IdString.fromUrl(groupUuid.get()));
+      if (!accountsToRemove.isEmpty()) {
+        deleteMembers.get().apply(resource, fromMembers(accountsToRemove));
+        reportMembersAction("removed from", resource, accountsToRemove);
+      }
+      if (!groupsToRemove.isEmpty()) {
+        deleteIncludedGroups.get().apply(resource, fromGroups(groupsToRemove));
+        reportGroupsAction("excluded from", resource, groupsToRemove);
+      }
+      if (!accountsToAdd.isEmpty()) {
+        addMembers.get().apply(resource, fromMembers(accountsToAdd));
+        reportMembersAction("added to", resource, accountsToAdd);
+      }
+      if (!groupsToInclude.isEmpty()) {
+        addIncludedGroups.get().apply(resource, fromGroups(groupsToInclude));
+        reportGroupsAction("included to", resource, groupsToInclude);
+      }
+    }
+  }
+
+  private void reportMembersAction(String action, GroupResource group,
+      List<Account.Id> accountIdList) throws UnsupportedEncodingException,
+      IOException {
+    out.write(String.format(
+        "Members %s group %s: %s\n",
+        action,
+        group.getName(),
+        Joiner.on(", ").join(
+            Iterables.transform(accountIdList,
+                new Function<Account.Id, String>() {
+                  @Override
+                  public String apply(Account.Id accountId) {
+                    return Objects.firstNonNull(accountCache.get(accountId)
+                        .getAccount().getPreferredEmail(), "n/a");
+                  }
+                }))).getBytes(ENC));
+  }
+
+  private void reportGroupsAction(String action, GroupResource group,
+      List<AccountGroup.UUID> groupUuidList)
+      throws UnsupportedEncodingException, IOException {
+    out.write(String.format(
+        "Groups %s group %s: %s\n",
+        action,
+        group.getName(),
+        Joiner.on(", ").join(
+            Iterables.transform(groupUuidList,
+                new Function<AccountGroup.UUID, String>() {
+                  @Override
+                  public String apply(AccountGroup.UUID uuid) {
+                    return groupCache.get(uuid).getName();
+                  }
+                }))).getBytes(ENC));
+  }
+
+  private AddIncludedGroups.Input fromGroups(List<AccountGroup.UUID> accounts) {
+    return AddIncludedGroups.Input.fromGroups(Lists.newArrayList(Iterables
+        .transform(accounts, new Function<AccountGroup.UUID, String>() {
+          @Override
+          public String apply(AccountGroup.UUID uuid) {
+            return uuid.toString();
+          }
+        })));
+  }
+
+  private AddMembers.Input fromMembers(List<Account.Id> accounts) {
+    return AddMembers.Input.fromMembers(Lists.newArrayList(Iterables.transform(
+        accounts, new Function<Account.Id, String>() {
+          @Override
+          public String apply(Account.Id id) {
+            return id.toString();
+          }
+        })));
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index 8c06c97d..d512fd5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -108,6 +108,9 @@
   @Option(name = "--project-state", aliases = {"--ps"}, usage = "project's visibility state")
   private State state;
 
+  @Option(name = "--max-object-size-limit", usage = "max Git object size for this project")
+  private String maxObjectSizeLimit;
+
   @Inject
   private MetaDataUpdate.User metaDataUpdateFactory;
 
@@ -148,6 +151,9 @@
         if (state != null) {
           project.setState(state);
         }
+        if (maxObjectSizeLimit != null) {
+          project.setMaxObjectSizeLimit(maxObjectSizeLimit);
+        }
         md.setMessage("Project settings updated");
         config.commit(md);
       } finally {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 3366841..ae5dc56 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.server.cache.h2.H2CacheImpl;
 import com.google.gerrit.server.config.SitePath;
 import com.google.gerrit.server.git.WorkQueue;
@@ -208,13 +209,10 @@
 
   private Map<String, Cache<?, ?>> sortedPluginCaches() {
     SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
-    for (String plugin : cacheMap.plugins()) {
-      if ("gerrit".equals(plugin)) {
-        continue;
-      }
-      for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
-          cacheMap.byPlugin(plugin).entrySet()) {
-        m.put(cacheNameOf(plugin, entry.getKey()), entry.getValue().get());
+    for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
+      if (!"gerrit".equals(e.getPluginName())) {
+        m.put(cacheNameOf(e.getPluginName(), e.getExportName()),
+            e.getProvider().get());
       }
     }
     return m;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 1c3d828..f5abf4b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -27,9 +27,11 @@
 
 import org.apache.mina.core.service.IoAcceptor;
 import org.apache.mina.core.session.IoSession;
+import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.session.ServerSession;
 import org.kohsuke.args4j.Option;
 
+import java.io.IOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
@@ -47,9 +49,28 @@
   @Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
   private boolean numeric;
 
+  @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
+  private boolean wide;
+
   @Inject
   private SshDaemon daemon;
 
+  private int hostNameWidth;
+  private int columns = 80;
+
+  @Override
+  public void start(final Environment env) throws IOException {
+    String s = env.getEnv().get(Environment.ENV_COLUMNS);
+    if (s != null && !s.isEmpty()) {
+      try {
+        columns = Integer.parseInt(s);
+      } catch (NumberFormatException err) {
+        columns = 80;
+      }
+    }
+    super.start(env);
+ }
+
   @Override
   protected void run() throws Failure {
     final IoAcceptor acceptor = daemon.getIoAcceptor();
@@ -71,6 +92,8 @@
       }
     });
 
+    hostNameWidth = wide ? Integer.MAX_VALUE : columns - 9 - 9 - 10 - 32;
+
     final long now = System.currentTimeMillis();
     stdout.print(String.format("%-8s %8s %8s   %-15s %s\n", //
         "Session", "Start", "Idle", "User", "Remote Host"));
@@ -83,7 +106,7 @@
       final long start = io.getCreationTime();
       final long idle = now - io.getLastIoTime();
 
-      stdout.print(String.format("%8s %8s %8s  %-15.15s %.30s\n", //
+      stdout.print(String.format("%8s %8s %8s   %-15.15s %s\n", //
           id(sd), //
           time(now, start), //
           age(idle), //
@@ -160,6 +183,11 @@
     if (host == null) {
       host = remoteAddress.toString();
     }
+
+    if (host.length() > hostNameWidth) {
+      return host.substring(0, hostNameWidth);
+    }
+
     return host;
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index fee5275..88973fc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -42,7 +42,7 @@
 @AdminHighPriorityCommand
 @CommandMetaData(name = "show-queue", descr = "Display the background work queues, including replication")
 final class ShowQueue extends SshCommand {
-  @Option(name = "-w", usage = "display without line width truncation")
+  @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
   private boolean wide;
 
   @Inject
diff --git a/gerrit-util-cli/BUCK b/gerrit-util-cli/BUCK
new file mode 100644
index 0000000..c281e86
--- /dev/null
+++ b/gerrit-util-cli/BUCK
@@ -0,0 +1,11 @@
+java_library(
+  name = 'cli',
+  srcs = glob(['src/main/java/**/*.java']),
+  deps = [
+    '//lib:args4j',
+    '//lib:guava',
+    '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-util-cli/pom.xml b/gerrit-util-cli/pom.xml
deleted file mode 100644
index f2325a4..0000000
--- a/gerrit-util-cli/pom.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-util-cli</artifactId>
-  <name>Gerrit Code Review - Utility - CLI</name>
-
-  <description>
-    Utilities to support command line parsing
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>args4j</groupId>
-      <artifactId>args4j</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.inject</groupId>
-      <artifactId>guice</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.inject.extensions</groupId>
-      <artifactId>guice-assistedinject</artifactId>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
index 69598ce..5f6130c 100644
--- a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -34,6 +34,7 @@
 
 package com.google.gerrit.util.cli;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -153,12 +154,7 @@
         }
         out.write('=');
 
-        String var = handler.getDefaultMetaVariable();
-        if (handler instanceof EnumOptionHandler) {
-          var = var.substring(1, var.length() - 1);
-          var = var.replaceAll(" ", "");
-        }
-        out.write(var);
+        out.write(metaVar(handler, n));
         if (!n.required()) {
           out.write(']');
         }
@@ -186,6 +182,17 @@
     }
   }
 
+  private static String metaVar(OptionHandler<?> handler, NamedOptionDef n) {
+    String var = n.metaVar();
+    if (Strings.isNullOrEmpty(var)) {
+      var = handler.getDefaultMetaVariable();
+      if (handler instanceof EnumOptionHandler) {
+        var = var.substring(1, var.length() - 1).replace(" ", "");
+      }
+    }
+    return var;
+  }
+
   public boolean wasHelpRequestedByOption() {
     return parser.help.value;
   }
diff --git a/gerrit-util-ssl/BUCK b/gerrit-util-ssl/BUCK
new file mode 100644
index 0000000..068f34c
--- /dev/null
+++ b/gerrit-util-ssl/BUCK
@@ -0,0 +1,5 @@
+java_library(
+  name = 'ssl',
+  srcs = glob(['src/main/java/**/*.java']),
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-util-ssl/pom.xml b/gerrit-util-ssl/pom.xml
deleted file mode 100644
index 1e941e0..0000000
--- a/gerrit-util-ssl/pom.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-util-ssl</artifactId>
-  <name>Gerrit Code Review - Utility - SSL</name>
-
-  <description>
-    Utilities to support SSL based protocols
-  </description>
-</project>
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
new file mode 100644
index 0000000..3f6cf48
--- /dev/null
+++ b/gerrit-war/BUCK
@@ -0,0 +1,53 @@
+java_library2(
+  name = 'init',
+  srcs = glob(['src/main/java/**/*.java']),
+  deps = [
+    '//gerrit-cache-h2:cache-h2',
+    '//gerrit-extension-api:api',
+    '//gerrit-httpd:httpd',
+    '//gerrit-lucene:lucene',
+    '//gerrit-openid:openid',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:common_rules',
+    '//gerrit-server:server',
+    '//gerrit-solr:solr',
+    '//gerrit-sshd:sshd',
+    '//lib:gwtorm',
+    '//lib/guice:guice',
+    '//lib/guice:guice-servlet',
+    '//lib/log:api',
+    '//lib/jgit:jgit',
+  ],
+  compile_deps = ['//lib:servlet-api-3_0'],
+  visibility = [
+    '//:',
+    '//gerrit-gwtdebug:gwtdebug',
+    '//tools/eclipse:classpath',
+  ],
+)
+
+genrule(
+  name = 'webapp_assets',
+  cmd = 'cd $SRCDIR/src/main/webapp; zip -qr $OUT .',
+  srcs = glob(['src/main/webapp/**/*']),
+  deps = [],
+  out = 'webapp_assets.zip',
+  visibility = ['//:'],
+)
+
+genrule(
+  name = 'log4j-config__jar',
+  cmd = 'jar cf $OUT -C $(dirname $SRCS) .',
+  srcs = ['src/main/resources/log4j.properties'],
+  out = 'log4j-config.jar',
+)
+
+prebuilt_jar(
+  name = 'log4j-config',
+  binary_jar = genfile('log4j-config.jar'),
+  deps = [':log4j-config__jar'],
+  visibility = [
+    '//:',
+    '//tools/eclipse:classpath',
+  ],
+)
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
deleted file mode 100644
index 0855b7d..0000000
--- a/gerrit-war/pom.xml
+++ /dev/null
@@ -1,344 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2009 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.google.gerrit</groupId>
-    <artifactId>gerrit-parent</artifactId>
-    <version>2.7-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>gerrit-war</artifactId>
-  <name>Gerrit Code Review - WAR</name>
-  <packaging>war</packaging>
-
-  <description>
-    Gerrit packaged as a standard web application archive
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>tomcat-servlet-api</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-gwtui</artifactId>
-      <version>${project.version}</version>
-      <type>war</type>
-      <scope>runtime</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-main</artifactId>
-      <version>${project.version}</version>
-      <scope>runtime</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>bouncycastle</groupId>
-      <artifactId>bcprov-jdk15</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>bouncycastle</groupId>
-      <artifactId>bcpg-jdk15</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-log4j12</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>log4j</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-openid</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-sshd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-httpd</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gerrit</groupId>
-      <artifactId>gerrit-pgm</artifactId>
-      <version>${project.version}</version>
-      <exclusions>
-        <exclusion>
-          <groupId>org.eclipse.jetty</groupId>
-          <artifactId>jetty-servlet</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlet</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.tomcat</groupId>
-      <artifactId>servlet-api</artifactId>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-
-  <profiles>
-    <profile>
-      <id>plugins</id>
-      <activation>
-        <property>
-          <name>!gerrit.plugins.skip</name>
-        </property>
-      </activation>
-      <dependencies>
-        <!-- CORE PLUGIN LIST -->
-        <dependency>
-          <groupId>com.googlesource.gerrit.plugins.replication</groupId>
-          <artifactId>replication</artifactId>
-          <version>${project.version}</version>
-          <scope>provided</scope>
-        </dependency>
-        <dependency>
-          <groupId>com.googlesource.gerrit.plugins.reviewnotes</groupId>
-          <artifactId>reviewnotes</artifactId>
-          <version>${project.version}</version>
-          <scope>provided</scope>
-        </dependency>
-        <dependency>
-          <groupId>com.googlesource.gerrit.plugins.validators</groupId>
-          <artifactId>commit-message-length-validator</artifactId>
-          <version>${project.version}</version>
-          <scope>provided</scope>
-        </dependency>
-      </dependencies>
-    </profile>
-  </profiles>
-
-  <build>
-	<pluginManagement>
-	  <plugins>
-	    <plugin>
-	      <groupId>org.eclipse.m2e</groupId>
-	      <artifactId>lifecycle-mapping</artifactId>
-	      <version>1.0.0</version>
-	      <configuration>
-	        <lifecycleMappingMetadata>
-	          <pluginExecutions>
-	            <pluginExecution>
-	              <pluginExecutionFilter>
-	                <groupId>org.apache.maven.plugins</groupId>
-	                <artifactId>maven-dependency-plugin</artifactId>
-	                <versionRange>[2.0,)</versionRange>
-	                <goals>
-	                  <goal>copy-dependencies</goal>
-	                  <goal>unpack</goal>goal>
-	                </goals>
-	              </pluginExecutionFilter>
-	              <action>
-	                <execute />
-	              </action>
-	            </pluginExecution>
-	          </pluginExecutions>
-	        </lifecycleMappingMetadata>
-	      </configuration>
-	    </plugin>
-	  </plugins>
-	</pluginManagement>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-war-plugin</artifactId>
-        <configuration>
-          <warName>gerrit-${project.version}</warName>
-          <archiveClasses>true</archiveClasses>
-          <attachClasses>true</attachClasses>
-          <archive>
-            <addMavenDescriptor>false</addMavenDescriptor>
-            <manifestEntries>
-              <Main-Class>Main</Main-Class>
-              <Implementation-Title>Gerrit Code Review</Implementation-Title>
-              <Implementation-Version>${project.version}</Implementation-Version>
-            </manifestEntries>
-          </archive>
-          <overlays>
-            <overlay>
-              <groupId>com.google.gerrit</groupId>
-              <artifactId>gerrit-main</artifactId>
-              <type>jar</type>
-              <includes>
-                <include>Main.class</include>
-                <include>com/google/gerrit/launcher/*.class</include>
-              </includes>
-            </overlay>
-          </overlays>
-        </configuration>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>copy-servlet-api</id>
-            <configuration>
-              <includeGroupIds>org.apache.tomcat,org.eclipse.jetty</includeGroupIds>
-              <excludeArtifactIds>servlet-api</excludeArtifactIds>
-            </configuration>
-            <goals>
-              <goal>copy-dependencies</goal>
-            </goals>
-          </execution>
-          <execution>
-            <id>copy-plugins</id>
-            <configuration>
-              <!-- CORE PLUGIN LIST -->
-              <includeArtifactIds>commit-message-length-validator,replication,reviewnotes</includeArtifactIds>
-              <includeTypes>jar</includeTypes>
-              <stripVersion>true</stripVersion>
-              <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/plugins</outputDirectory>
-            </configuration>
-            <goals>
-              <goal>copy-dependencies</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-antrun-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>copy-servlet-api</id>
-            <phase>process-classes</phase>
-            <configuration>
-              <target>
-                <property name="src" location="${project.build.directory}/dependency" />
-                <property name="dst" location="${project.build.directory}/${project.build.finalName}/WEB-INF/pgm-lib" />
-
-                <mkdir dir="${dst}" />
-                <copy overwrite="true" todir="${dst}">
-                  <fileset dir="${src}">
-                    <include name="*.jar" />
-                  </fileset>
-                </copy>
-              </target>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-          <execution>
-            <id>copy-license</id>
-            <phase>process-classes</phase>
-            <configuration>
-              <target>
-                <property name="src" location="${basedir}/../Documentation" />
-                <property name="dst" location="${project.build.directory}/${project.build.finalName}" />
-
-                <copy tofile="${dst}/LICENSES.txt"
-                      file="${src}/licenses.txt"
-                      overwrite="true" />
-              </target>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-          <execution>
-            <id>include-documentation</id>
-            <phase>process-classes</phase>
-            <configuration>
-              <target unless="gerrit.documentation.skip">
-                <property name="src" location="${basedir}/../Documentation" />
-                <property name="out" location="${project.build.directory}/${project.build.finalName}" />
-                <property name="dst" location="${out}/Documentation" />
-
-                <exec dir="${src}" executable="make">
-                  <arg value="VERSION=${project.version}" />
-                  <arg value="clean" />
-                  <arg value="all" />
-                </exec>
-
-                <mkdir dir="${dst}" />
-                <copy overwrite="true" todir="${dst}">
-                  <fileset dir="${src}">
-                    <include name="*.html" />
-                  </fileset>
-                </copy>
-              </target>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-          <execution>
-            <id>include-release-notes</id>
-            <phase>process-classes</phase>
-            <configuration>
-              <target unless="gerrit.documentation.skip">
-                <property name="src" location="${basedir}/../ReleaseNotes" />
-                <property name="out" location="${project.build.directory}/${project.build.finalName}" />
-                <property name="dst" location="${out}/ReleaseNotes" />
-
-                <exec dir="${src}" executable="make">
-                  <arg value="VERSION=${project.version}" />
-                  <arg value="clean" />
-                  <arg value="all" />
-                </exec>
-
-                <mkdir dir="${dst}" />
-                <copy overwrite="true" todir="${dst}">
-                  <fileset dir="${src}">
-                    <include name="*.html" />
-                  </fileset>
-                </copy>
-              </target>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index d0b8c38..da08555 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -18,11 +18,11 @@
 import static com.google.inject.Stage.PRODUCTION;
 
 import com.google.gerrit.common.ChangeHookRunner;
-import com.google.gerrit.httpd.GerritUiOptions;
 import com.google.gerrit.httpd.auth.openid.OpenIdModule;
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.lucene.LuceneIndexModule;
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
 import com.google.gerrit.server.config.AuthConfig;
@@ -37,6 +37,8 @@
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
 import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
 import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.index.IndexModule;
+import com.google.gerrit.server.index.NoIndexModule;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.patch.IntraLineWorkerPool;
@@ -48,6 +50,7 @@
 import com.google.gerrit.server.schema.DatabaseModule;
 import com.google.gerrit.server.schema.SchemaModule;
 import com.google.gerrit.server.schema.SchemaVersionCheck;
+import com.google.gerrit.solr.SolrIndexModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
 import com.google.gerrit.sshd.commands.MasterCommandModule;
@@ -236,6 +239,18 @@
     modules.add(new SmtpEmailSender.Module());
     modules.add(new SignedTokenEmailTokenVerifier.Module());
     modules.add(new PluginModule());
+    AbstractModule changeIndexModule;
+    switch (IndexModule.getIndexType(cfgInjector)) {
+      case LUCENE:
+        changeIndexModule = new LuceneIndexModule();
+        break;
+      case SOLR:
+        changeIndexModule = new SolrIndexModule();
+        break;
+      default:
+        changeIndexModule = new NoIndexModule();
+    }
+    modules.add(changeIndexModule);
     modules.add(new CanonicalWebUrlModule() {
       @Override
       protected Class<? extends Provider<String>> provider() {
diff --git a/lib/BUCK b/lib/BUCK
new file mode 100644
index 0000000..a9d0bf6
--- /dev/null
+++ b/lib/BUCK
@@ -0,0 +1,248 @@
+include_defs('//lib/maven.defs')
+
+define_license(name = 'Apache1.1')
+define_license(name = 'Apache2.0')
+define_license(name = 'MPL1.1')
+define_license(name = 'PublicDomain')
+define_license(name = 'antlr')
+define_license(name = 'args4j')
+define_license(name = 'automaton')
+define_license(name = 'bouncycastle')
+define_license(name = 'clippy')
+define_license(name = 'codemirror')
+define_license(name = 'h2')
+define_license(name = 'jgit')
+define_license(name = 'jsch')
+define_license(name = 'jsr305')
+define_license(name = 'ow2')
+define_license(name = 'postgresql')
+define_license(name = 'prologcafe')
+define_license(name = 'slf4j')
+define_license(name = 'DO_NOT_DISTRIBUTE')
+
+maven_jar(
+  name = 'gwtorm',
+  id = 'gwtorm:gwtorm:1.6',
+  bin_sha1 = '61bcb92f438524260429149910b5261d48812419',
+  src_sha1 = '2624f9d6a750a8aa8f9a5d4b5062b70cd12d1ae7',
+  license = 'Apache2.0',
+  repository = GERRIT,
+)
+
+maven_jar(
+  name = 'gwtjsonrpc',
+  id = 'gwtjsonrpc:gwtjsonrpc:1.3',
+  bin_sha1 = '1717ba11ab0c5160798c80085220a63f864691d3',
+  src_sha1 = '9e01c5d7bd54f8e70066450b372a43c16404789e',
+  license = 'Apache2.0',
+  repository = GERRIT,
+)
+
+maven_jar(
+  name = 'gson',
+  id = 'com.google.code.gson:gson:2.1',
+  sha1 = '2e66da15851f9f5b5079228f856c2f090ba98c38',
+  license = 'Apache2.0',
+)
+
+maven_jar(
+  name = 'guava',
+  id = 'com.google.guava:guava:14.0',
+  sha1 = '67b7be4ee7ba48e4828a42d6d5069761186d4a53',
+  license = 'Apache2.0',
+)
+
+maven_jar(
+  name = 'asm3',
+  id = 'asm:asm:3.2',
+  sha1 = '9bc1511dec6adf302991ced13303e4140fdf9ab7',
+  license = 'ow2',
+  attach_source = False,
+)
+
+maven_jar(
+  name = 'ow2-asm',
+  id = 'org.ow2.asm:asm:4.0',
+  sha1 = '659add6efc75a4715d738e73f07505246edf4d66',
+  license = 'ow2',
+)
+
+maven_jar(
+  name = 'ow2-asm-analysis',
+  id = 'org.ow2.asm:asm-analysis:4.0',
+  sha1 = '1c45d52b6f6c638db13cf3ac12adeb56b254cdd7',
+  license = 'ow2',
+)
+
+maven_jar(
+  name = 'ow2-asm-tree',
+  id = 'org.ow2.asm:asm-tree:4.0',
+  sha1 = '67bd266cd17adcee486b76952ece4cc85fe248b8',
+  license = 'ow2',
+)
+
+maven_jar(
+  name = 'ow2-asm-util',
+  id = 'org.ow2.asm:asm-util:4.0',
+  sha1 = 'd7a65f54cda284f9706a750c23d64830bb740c39',
+  license = 'ow2',
+)
+
+maven_jar(
+  name = 'velocity',
+  id = 'org.apache.velocity:velocity:1.6.4',
+  sha1 = 'fcc58693dd8fc83d714fba149789be37cc19b66d',
+  license = 'Apache2.0',
+  deps = [
+    '//lib/commons:collections',
+    '//lib/commons:lang',
+    '//lib/commons:oro',
+  ],
+  exclude = ['META-INF/LICENSE', 'META-INF/NOTICE'],
+)
+
+maven_jar(
+  name = 'jsch',
+  id = 'com.jcraft:jsch:0.1.44-1',
+  sha1 = '2e9ae08de5a71bd0e0d3ba2558598181bfa71d4e',
+  license = 'jsch',
+)
+
+maven_jar(
+  name = 'servlet-api-3_0',
+  id = 'org.apache.tomcat:tomcat-servlet-api:7.0.32',
+  sha1 = 'e2f21e9868414122e6dd23ac66cf304d4290642c',
+  license = 'Apache2.0',
+  exclude = ['META-INF/NOTICE', 'META-INF/LICENSE'],
+)
+
+maven_jar(
+  name = 'jsr305',
+  id = 'com.google.code.findbugs:jsr305:1.3.9',
+  sha1 = '40719ea6961c0cb6afaeb6a921eaa1f6afd4cfdf',
+  license = 'jsr305',
+  attach_source = False,
+  exclude_java_sources = True,
+)
+
+maven_jar(
+  name = 'args4j',
+  id = 'args4j:args4j:2.0.16',
+  sha1 = '9f00fb12820743b9e05c686eba543d64dd43f2b1',
+  license = 'args4j',
+)
+
+maven_jar(
+  name = 'mime-util',
+  id = 'eu.medsea.mimeutil:mime-util:2.1.3',
+  sha1 = '0c9cfae15c74f62491d4f28def0dff1dabe52a47',
+  license = 'Apache2.0',
+  exclude = ['LICENSE.txt', 'README.txt'],
+  attach_source = False,
+)
+
+maven_jar(
+  name = 'juniversalchardet',
+  id = 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3',
+  sha1 = 'cd49678784c46aa8789c060538e0154013bb421b',
+  license = 'MPL1.1',
+)
+
+maven_jar(
+  name = 'automaton',
+  id = 'dk.brics.automaton:automaton:1.11-8',
+  sha1 = '6ebfa65eb431ff4b715a23be7a750cbc4cc96d0f',
+  license = 'automaton',
+)
+
+maven_jar(
+  name = 'pegdown',
+  id = 'org.pegdown:pegdown:1.1.0',
+  sha1 = '00bcc0c5b025b09ab85bb80a8311ce5c015d005b',
+  license = 'Apache2.0',
+  deps = [':parboiled-java'],
+  exclude = ['META-INF/LICENSE', 'META-INF/NOTICE'],
+)
+
+maven_jar(
+  name = 'parboiled-core',
+  id = 'org.parboiled:parboiled-core:1.1.3',
+  sha1 = '3fc3013adf98701efcc594a1ea99a3f841dc81bb',
+  license = 'Apache2.0',
+  attach_source = False,
+)
+
+maven_jar(
+  name = 'parboiled-java',
+  id = 'org.parboiled:parboiled-java:1.1.3',
+  sha1 = 'c2bf2935a8b3eca5f998557190cd6eb34f5536d0',
+  license = 'Apache2.0',
+  deps = [
+    ':parboiled-core',
+    ':ow2-asm-tree',
+    ':ow2-asm-analysis',
+    ':ow2-asm-util',
+  ],
+  attach_source = False,
+  visibility = [],
+)
+
+maven_jar(
+  name = 'h2',
+  id = 'com.h2database:h2:1.3.168',
+  sha1 = 'eb32936a239d95220f5b2d2973a7b17372f98b54',
+  license = 'h2',
+)
+
+maven_jar(
+  name = 'postgresql',
+  id = 'postgresql:postgresql:9.1-901-1.jdbc4',
+  sha1 = '9bfabe48876ec38f6cbaa6931bad05c64a9ea942',
+  license = 'postgresql',
+  attach_source = False,
+)
+
+maven_jar(
+  name = 'junit',
+  id = 'junit:junit:4.11',
+  sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [':hamcrest-core'],
+)
+
+maven_jar(
+  name = 'hamcrest-core',
+  id = 'org.hamcrest:hamcrest-core:1.3',
+  sha1 = '42a25dc3219429f0e5d060061f71acb49bf010a0',
+  license = 'DO_NOT_DISTRIBUTE',
+  visibility = ['//lib:junit'],
+)
+
+maven_jar(
+  name = 'easymock',
+  id = 'org.easymock:easymock:3.1',
+  sha1 = '3e127311a86fc2e8f550ef8ee4abe094bbcf7e7e',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [
+    ':cglib-2_2',
+    ':objenesis',
+  ],
+)
+
+maven_jar(
+  name = 'cglib-2_2',
+  id = 'cglib:cglib-nodep:2.2.2',
+  sha1 = '00d456bb230c70c0b95c76fb28e429d42f275941',
+  license = 'DO_NOT_DISTRIBUTE',
+  visibility = ['//lib:easymock'],
+  attach_source = False,
+)
+
+maven_jar(
+  name = 'objenesis',
+  id = 'org.objenesis:objenesis:1.2',
+  sha1 = 'bfcb0539a071a4c5a30690388903ac48c0667f2a',
+  license = 'DO_NOT_DISTRIBUTE',
+  visibility = ['//lib:easymock'],
+  attach_source = False,
+)
diff --git a/lib/LICENSE-Apache1.1 b/lib/LICENSE-Apache1.1
new file mode 100644
index 0000000..8eda4fc
--- /dev/null
+++ b/lib/LICENSE-Apache1.1
@@ -0,0 +1,51 @@
+The Apache Software License, Version 1.1
+
+Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
+reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in
+   the documentation and/or other materials provided with the
+   distribution.
+
+3. The end-user documentation included with the redistribution,
+   if any, must include the following acknowledgment:
+      "This product includes software developed by the
+       Apache Software Foundation (http://www.apache.org/)."
+   Alternately, this acknowledgment may appear in the software itself,
+   if and wherever such third-party acknowledgments normally appear.
+
+4. The names "Apache" and "Apache Software Foundation", "Jakarta-Oro"
+   must not be used to endorse or promote products derived from this
+   software without prior written permission. For written
+   permission, please contact apache@apache.org.
+
+5. Products derived from this software may not be called "Apache"
+   or "Jakarta-Oro", nor may "Apache" or "Jakarta-Oro" appear in their
+   name, without prior written permission of the Apache Software Foundation.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+====================================================================
+
+This software consists of voluntary contributions made by many
+individuals on behalf of the Apache Software Foundation.  For more
+information on the Apache Software Foundation, please see
+<http://www.apache.org/>.
diff --git a/lib/LICENSE-Apache2.0 b/lib/LICENSE-Apache2.0
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/lib/LICENSE-Apache2.0
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/lib/LICENSE-DO_NOT_DISTRIBUTE b/lib/LICENSE-DO_NOT_DISTRIBUTE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/LICENSE-DO_NOT_DISTRIBUTE
diff --git a/lib/LICENSE-MPL1.1 b/lib/LICENSE-MPL1.1
new file mode 100644
index 0000000..06f9651
--- /dev/null
+++ b/lib/LICENSE-MPL1.1
@@ -0,0 +1,469 @@
+                          MOZILLA PUBLIC LICENSE
+                                Version 1.1
+
+                              ---------------
+
+1. Definitions.
+
+     1.0.1. "Commercial Use" means distribution or otherwise making the
+     Covered Code available to a third party.
+
+     1.1. "Contributor" means each entity that creates or contributes to
+     the creation of Modifications.
+
+     1.2. "Contributor Version" means the combination of the Original
+     Code, prior Modifications used by a Contributor, and the Modifications
+     made by that particular Contributor.
+
+     1.3. "Covered Code" means the Original Code or Modifications or the
+     combination of the Original Code and Modifications, in each case
+     including portions thereof.
+
+     1.4. "Electronic Distribution Mechanism" means a mechanism generally
+     accepted in the software development community for the electronic
+     transfer of data.
+
+     1.5. "Executable" means Covered Code in any form other than Source
+     Code.
+
+     1.6. "Initial Developer" means the individual or entity identified
+     as the Initial Developer in the Source Code notice required by Exhibit
+     A.
+
+     1.7. "Larger Work" means a work which combines Covered Code or
+     portions thereof with code not governed by the terms of this License.
+
+     1.8. "License" means this document.
+
+     1.8.1. "Licensable" means having the right to grant, to the maximum
+     extent possible, whether at the time of the initial grant or
+     subsequently acquired, any and all of the rights conveyed herein.
+
+     1.9. "Modifications" means any addition to or deletion from the
+     substance or structure of either the Original Code or any previous
+     Modifications. When Covered Code is released as a series of files, a
+     Modification is:
+          A. Any addition to or deletion from the contents of a file
+          containing Original Code or previous Modifications.
+
+          B. Any new file that contains any part of the Original Code or
+          previous Modifications.
+
+     1.10. "Original Code" means Source Code of computer software code
+     which is described in the Source Code notice required by Exhibit A as
+     Original Code, and which, at the time of its release under this
+     License is not already Covered Code governed by this License.
+
+     1.10.1. "Patent Claims" means any patent claim(s), now owned or
+     hereafter acquired, including without limitation,  method, process,
+     and apparatus claims, in any patent Licensable by grantor.
+
+     1.11. "Source Code" means the preferred form of the Covered Code for
+     making modifications to it, including all modules it contains, plus
+     any associated interface definition files, scripts used to control
+     compilation and installation of an Executable, or source code
+     differential comparisons against either the Original Code or another
+     well known, available Covered Code of the Contributor's choice. The
+     Source Code can be in a compressed or archival form, provided the
+     appropriate decompression or de-archiving software is widely available
+     for no charge.
+
+     1.12. "You" (or "Your")  means an individual or a legal entity
+     exercising rights under, and complying with all of the terms of, this
+     License or a future version of this License issued under Section 6.1.
+     For legal entities, "You" includes any entity which controls, is
+     controlled by, or is under common control with You. For purposes of
+     this definition, "control" means (a) the power, direct or indirect,
+     to cause the direction or management of such entity, whether by
+     contract or otherwise, or (b) ownership of more than fifty percent
+     (50%) of the outstanding shares or beneficial ownership of such
+     entity.
+
+2. Source Code License.
+
+     2.1. The Initial Developer Grant.
+     The Initial Developer hereby grants You a world-wide, royalty-free,
+     non-exclusive license, subject to third party intellectual property
+     claims:
+          (a)  under intellectual property rights (other than patent or
+          trademark) Licensable by Initial Developer to use, reproduce,
+          modify, display, perform, sublicense and distribute the Original
+          Code (or portions thereof) with or without Modifications, and/or
+          as part of a Larger Work; and
+
+          (b) under Patents Claims infringed by the making, using or
+          selling of Original Code, to make, have made, use, practice,
+          sell, and offer for sale, and/or otherwise dispose of the
+          Original Code (or portions thereof).
+
+          (c) the licenses granted in this Section 2.1(a) and (b) are
+          effective on the date Initial Developer first distributes
+          Original Code under the terms of this License.
+
+          (d) Notwithstanding Section 2.1(b) above, no patent license is
+          granted: 1) for code that You delete from the Original Code; 2)
+          separate from the Original Code;  or 3) for infringements caused
+          by: i) the modification of the Original Code or ii) the
+          combination of the Original Code with other software or devices.
+
+     2.2. Contributor Grant.
+     Subject to third party intellectual property claims, each Contributor
+     hereby grants You a world-wide, royalty-free, non-exclusive license
+
+          (a)  under intellectual property rights (other than patent or
+          trademark) Licensable by Contributor, to use, reproduce, modify,
+          display, perform, sublicense and distribute the Modifications
+          created by such Contributor (or portions thereof) either on an
+          unmodified basis, with other Modifications, as Covered Code
+          and/or as part of a Larger Work; and
+
+          (b) under Patent Claims infringed by the making, using, or
+          selling of  Modifications made by that Contributor either alone
+          and/or in combination with its Contributor Version (or portions
+          of such combination), to make, use, sell, offer for sale, have
+          made, and/or otherwise dispose of: 1) Modifications made by that
+          Contributor (or portions thereof); and 2) the combination of
+          Modifications made by that Contributor with its Contributor
+          Version (or portions of such combination).
+
+          (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+          effective on the date Contributor first makes Commercial Use of
+          the Covered Code.
+
+          (d)    Notwithstanding Section 2.2(b) above, no patent license is
+          granted: 1) for any code that Contributor has deleted from the
+          Contributor Version; 2)  separate from the Contributor Version;
+          3)  for infringements caused by: i) third party modifications of
+          Contributor Version or ii)  the combination of Modifications made
+          by that Contributor with other software  (except as part of the
+          Contributor Version) or other devices; or 4) under Patent Claims
+          infringed by Covered Code in the absence of Modifications made by
+          that Contributor.
+
+3. Distribution Obligations.
+
+     3.1. Application of License.
+     The Modifications which You create or to which You contribute are
+     governed by the terms of this License, including without limitation
+     Section 2.2. The Source Code version of Covered Code may be
+     distributed only under the terms of this License or a future version
+     of this License released under Section 6.1, and You must include a
+     copy of this License with every copy of the Source Code You
+     distribute. You may not offer or impose any terms on any Source Code
+     version that alters or restricts the applicable version of this
+     License or the recipients' rights hereunder. However, You may include
+     an additional document offering the additional rights described in
+     Section 3.5.
+
+     3.2. Availability of Source Code.
+     Any Modification which You create or to which You contribute must be
+     made available in Source Code form under the terms of this License
+     either on the same media as an Executable version or via an accepted
+     Electronic Distribution Mechanism to anyone to whom you made an
+     Executable version available; and if made available via Electronic
+     Distribution Mechanism, must remain available for at least twelve (12)
+     months after the date it initially became available, or at least six
+     (6) months after a subsequent version of that particular Modification
+     has been made available to such recipients. You are responsible for
+     ensuring that the Source Code version remains available even if the
+     Electronic Distribution Mechanism is maintained by a third party.
+
+     3.3. Description of Modifications.
+     You must cause all Covered Code to which You contribute to contain a
+     file documenting the changes You made to create that Covered Code and
+     the date of any change. You must include a prominent statement that
+     the Modification is derived, directly or indirectly, from Original
+     Code provided by the Initial Developer and including the name of the
+     Initial Developer in (a) the Source Code, and (b) in any notice in an
+     Executable version or related documentation in which You describe the
+     origin or ownership of the Covered Code.
+
+     3.4. Intellectual Property Matters
+          (a) Third Party Claims.
+          If Contributor has knowledge that a license under a third party's
+          intellectual property rights is required to exercise the rights
+          granted by such Contributor under Sections 2.1 or 2.2,
+          Contributor must include a text file with the Source Code
+          distribution titled "LEGAL" which describes the claim and the
+          party making the claim in sufficient detail that a recipient will
+          know whom to contact. If Contributor obtains such knowledge after
+          the Modification is made available as described in Section 3.2,
+          Contributor shall promptly modify the LEGAL file in all copies
+          Contributor makes available thereafter and shall take other steps
+          (such as notifying appropriate mailing lists or newsgroups)
+          reasonably calculated to inform those who received the Covered
+          Code that new knowledge has been obtained.
+
+          (b) Contributor APIs.
+          If Contributor's Modifications include an application programming
+          interface and Contributor has knowledge of patent licenses which
+          are reasonably necessary to implement that API, Contributor must
+          also include this information in the LEGAL file.
+
+               (c)    Representations.
+          Contributor represents that, except as disclosed pursuant to
+          Section 3.4(a) above, Contributor believes that Contributor's
+          Modifications are Contributor's original creation(s) and/or
+          Contributor has sufficient rights to grant the rights conveyed by
+          this License.
+
+     3.5. Required Notices.
+     You must duplicate the notice in Exhibit A in each file of the Source
+     Code.  If it is not possible to put such notice in a particular Source
+     Code file due to its structure, then You must include such notice in a
+     location (such as a relevant directory) where a user would be likely
+     to look for such a notice.  If You created one or more Modification(s)
+     You may add your name as a Contributor to the notice described in
+     Exhibit A.  You must also duplicate this License in any documentation
+     for the Source Code where You describe recipients' rights or ownership
+     rights relating to Covered Code.  You may choose to offer, and to
+     charge a fee for, warranty, support, indemnity or liability
+     obligations to one or more recipients of Covered Code. However, You
+     may do so only on Your own behalf, and not on behalf of the Initial
+     Developer or any Contributor. You must make it absolutely clear than
+     any such warranty, support, indemnity or liability obligation is
+     offered by You alone, and You hereby agree to indemnify the Initial
+     Developer and every Contributor for any liability incurred by the
+     Initial Developer or such Contributor as a result of warranty,
+     support, indemnity or liability terms You offer.
+
+     3.6. Distribution of Executable Versions.
+     You may distribute Covered Code in Executable form only if the
+     requirements of Section 3.1-3.5 have been met for that Covered Code,
+     and if You include a notice stating that the Source Code version of
+     the Covered Code is available under the terms of this License,
+     including a description of how and where You have fulfilled the
+     obligations of Section 3.2. The notice must be conspicuously included
+     in any notice in an Executable version, related documentation or
+     collateral in which You describe recipients' rights relating to the
+     Covered Code. You may distribute the Executable version of Covered
+     Code or ownership rights under a license of Your choice, which may
+     contain terms different from this License, provided that You are in
+     compliance with the terms of this License and that the license for the
+     Executable version does not attempt to limit or alter the recipient's
+     rights in the Source Code version from the rights set forth in this
+     License. If You distribute the Executable version under a different
+     license You must make it absolutely clear that any terms which differ
+     from this License are offered by You alone, not by the Initial
+     Developer or any Contributor. You hereby agree to indemnify the
+     Initial Developer and every Contributor for any liability incurred by
+     the Initial Developer or such Contributor as a result of any such
+     terms You offer.
+
+     3.7. Larger Works.
+     You may create a Larger Work by combining Covered Code with other code
+     not governed by the terms of this License and distribute the Larger
+     Work as a single product. In such a case, You must make sure the
+     requirements of this License are fulfilled for the Covered Code.
+
+4. Inability to Comply Due to Statute or Regulation.
+
+     If it is impossible for You to comply with any of the terms of this
+     License with respect to some or all of the Covered Code due to
+     statute, judicial order, or regulation then You must: (a) comply with
+     the terms of this License to the maximum extent possible; and (b)
+     describe the limitations and the code they affect. Such description
+     must be included in the LEGAL file described in Section 3.4 and must
+     be included with all distributions of the Source Code. Except to the
+     extent prohibited by statute or regulation, such description must be
+     sufficiently detailed for a recipient of ordinary skill to be able to
+     understand it.
+
+5. Application of this License.
+
+     This License applies to code to which the Initial Developer has
+     attached the notice in Exhibit A and to related Covered Code.
+
+6. Versions of the License.
+
+     6.1. New Versions.
+     Netscape Communications Corporation ("Netscape") may publish revised
+     and/or new versions of the License from time to time. Each version
+     will be given a distinguishing version number.
+
+     6.2. Effect of New Versions.
+     Once Covered Code has been published under a particular version of the
+     License, You may always continue to use it under the terms of that
+     version. You may also choose to use such Covered Code under the terms
+     of any subsequent version of the License published by Netscape. No one
+     other than Netscape has the right to modify the terms applicable to
+     Covered Code created under this License.
+
+     6.3. Derivative Works.
+     If You create or use a modified version of this License (which you may
+     only do in order to apply it to code which is not already Covered Code
+     governed by this License), You must (a) rename Your license so that
+     the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+     "MPL", "NPL" or any confusingly similar phrase do not appear in your
+     license (except to note that your license differs from this License)
+     and (b) otherwise make it clear that Your version of the license
+     contains terms which differ from the Mozilla Public License and
+     Netscape Public License. (Filling in the name of the Initial
+     Developer, Original Code or Contributor in the notice described in
+     Exhibit A shall not of themselves be deemed to be modifications of
+     this License.)
+
+7. DISCLAIMER OF WARRANTY.
+
+     COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+     WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+     WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+     DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+     THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+     IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+     YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+     COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+     OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+     ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION.
+
+     8.1.  This License and the rights granted hereunder will terminate
+     automatically if You fail to comply with terms herein and fail to cure
+     such breach within 30 days of becoming aware of the breach. All
+     sublicenses to the Covered Code which are properly granted shall
+     survive any termination of this License. Provisions which, by their
+     nature, must remain in effect beyond the termination of this License
+     shall survive.
+
+     8.2.  If You initiate litigation by asserting a patent infringement
+     claim (excluding declatory judgment actions) against Initial Developer
+     or a Contributor (the Initial Developer or Contributor against whom
+     You file such action is referred to as "Participant")  alleging that:
+
+     (a)  such Participant's Contributor Version directly or indirectly
+     infringes any patent, then any and all rights granted by such
+     Participant to You under Sections 2.1 and/or 2.2 of this License
+     shall, upon 60 days notice from Participant terminate prospectively,
+     unless if within 60 days after receipt of notice You either: (i)
+     agree in writing to pay Participant a mutually agreeable reasonable
+     royalty for Your past and future use of Modifications made by such
+     Participant, or (ii) withdraw Your litigation claim with respect to
+     the Contributor Version against such Participant.  If within 60 days
+     of notice, a reasonable royalty and payment arrangement are not
+     mutually agreed upon in writing by the parties or the litigation claim
+     is not withdrawn, the rights granted by Participant to You under
+     Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+     the 60 day notice period specified above.
+
+     (b)  any software, hardware, or device, other than such Participant's
+     Contributor Version, directly or indirectly infringes any patent, then
+     any rights granted to You by such Participant under Sections 2.1(b)
+     and 2.2(b) are revoked effective as of the date You first made, used,
+     sold, distributed, or had made, Modifications made by that
+     Participant.
+
+     8.3.  If You assert a patent infringement claim against Participant
+     alleging that such Participant's Contributor Version directly or
+     indirectly infringes any patent where such claim is resolved (such as
+     by license or settlement) prior to the initiation of patent
+     infringement litigation, then the reasonable value of the licenses
+     granted by such Participant under Sections 2.1 or 2.2 shall be taken
+     into account in determining the amount or value of any payment or
+     license.
+
+     8.4.  In the event of termination under Sections 8.1 or 8.2 above,
+     all end user license agreements (excluding distributors and resellers)
+     which have been validly granted by You or any distributor hereunder
+     prior to termination shall survive termination.
+
+9. LIMITATION OF LIABILITY.
+
+     UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+     (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+     DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+     OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+     ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+     CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+     WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+     COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+     INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+     LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+     RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+     PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+     EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+     THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10. U.S. GOVERNMENT END USERS.
+
+     The Covered Code is a "commercial item," as that term is defined in
+     48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+     software" and "commercial computer software documentation," as such
+     terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+     C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+     all U.S. Government End Users acquire Covered Code with only those
+     rights set forth herein.
+
+11. MISCELLANEOUS.
+
+     This License represents the complete agreement concerning subject
+     matter hereof. If any provision of this License is held to be
+     unenforceable, such provision shall be reformed only to the extent
+     necessary to make it enforceable. This License shall be governed by
+     California law provisions (except to the extent applicable law, if
+     any, provides otherwise), excluding its conflict-of-law provisions.
+     With respect to disputes in which at least one party is a citizen of,
+     or an entity chartered or registered to do business in the United
+     States of America, any litigation relating to this License shall be
+     subject to the jurisdiction of the Federal Courts of the Northern
+     District of California, with venue lying in Santa Clara County,
+     California, with the losing party responsible for costs, including
+     without limitation, court costs and reasonable attorneys' fees and
+     expenses. The application of the United Nations Convention on
+     Contracts for the International Sale of Goods is expressly excluded.
+     Any law or regulation which provides that the language of a contract
+     shall be construed against the drafter shall not apply to this
+     License.
+
+12. RESPONSIBILITY FOR CLAIMS.
+
+     As between Initial Developer and the Contributors, each party is
+     responsible for claims and damages arising, directly or indirectly,
+     out of its utilization of rights under this License and You agree to
+     work with Initial Developer and Contributors to distribute such
+     responsibility on an equitable basis. Nothing herein is intended or
+     shall be deemed to constitute any admission of liability.
+
+13. MULTIPLE-LICENSED CODE.
+
+     Initial Developer may designate portions of the Covered Code as
+     "Multiple-Licensed".  "Multiple-Licensed" means that the Initial
+     Developer permits you to utilize portions of the Covered Code under
+     Your choice of the NPL or the alternative licenses, if any, specified
+     by the Initial Developer in the file described in Exhibit A.
+
+EXHIBIT A -Mozilla Public License.
+
+     ``The contents of this file are subject to the Mozilla Public License
+     Version 1.1 (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.mozilla.org/MPL/
+
+     Software distributed under the License is distributed on an "AS IS"
+     basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+     License for the specific language governing rights and limitations
+     under the License.
+
+     The Original Code is ______________________________________.
+
+     The Initial Developer of the Original Code is ________________________.
+     Portions created by ______________________ are Copyright (C) ______
+     _______________________. All Rights Reserved.
+
+     Contributor(s): ______________________________________.
+
+     Alternatively, the contents of this file may be used under the terms
+     of the _____ license (the  "[___] License"), in which case the
+     provisions of [______] License are applicable instead of those
+     above.  If you wish to allow use of your version of this file only
+     under the terms of the [____] License and not to allow others to use
+     your version of this file under the MPL, indicate your decision by
+     deleting  the provisions above and replace  them with the notice and
+     other provisions required by the [___] License.  If you do not delete
+     the provisions above, a recipient may use your version of this file
+     under either the MPL or the [___] License."
+
+     [NOTE: The text of this Exhibit A may differ slightly from the text of
+     the notices in the Source Code files of the Original Code. You should
+     use the text of this Exhibit A rather than the text found in the
+     Original Code Source Code for Your Modifications.]
diff --git a/lib/LICENSE-PublicDomain b/lib/LICENSE-PublicDomain
new file mode 100644
index 0000000..8a71ce0
--- /dev/null
+++ b/lib/LICENSE-PublicDomain
@@ -0,0 +1 @@
+This software has been placed in the public domain by its author(s).
diff --git a/lib/LICENSE-antlr b/lib/LICENSE-antlr
new file mode 100644
index 0000000..6041290
--- /dev/null
+++ b/lib/LICENSE-antlr
@@ -0,0 +1,29 @@
+Copyright (c) 2003-2008, Terence Parr
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of the author nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-args4j b/lib/LICENSE-args4j
new file mode 100644
index 0000000..36cd75f
--- /dev/null
+++ b/lib/LICENSE-args4j
@@ -0,0 +1,32 @@
+Copyright (c) 2003, Kohsuke Kawaguchi
+All rights reserved.
+
+Redistribution and use in source and binary forms,
+with or without modification, are permitted provided
+that the following conditions are met:
+
+    * Redistributions of source code must retain
+      the above copyright notice, this list of
+      conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce
+      the above copyright notice, this list of
+      conditions and the following disclaimer in
+      the documentation and/or other materials
+      provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-automaton b/lib/LICENSE-automaton
new file mode 100644
index 0000000..72dcb1c
--- /dev/null
+++ b/lib/LICENSE-automaton
@@ -0,0 +1,28 @@
+Copyright (c) 2007-2009, dk.brics.automaton
+All rights reserved.
+
+http://www.opensource.org/licenses/bsd-license.php
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of the JSR305 expert group nor the names of its
+      contributors may be used to endorse or promote products derived from
+      this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-bouncycastle b/lib/LICENSE-bouncycastle
new file mode 100644
index 0000000..d17a4bc
--- /dev/null
+++ b/lib/LICENSE-bouncycastle
@@ -0,0 +1,21 @@
+Copyright (c) 2000 - 2012 The Legion Of The Bouncy Castle
+(http://www.bouncycastle.org)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/LICENSE-clippy b/lib/LICENSE-clippy
new file mode 100644
index 0000000..b0feeae
--- /dev/null
+++ b/lib/LICENSE-clippy
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Tom Preston-Werner
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/LICENSE-codemirror b/lib/LICENSE-codemirror
new file mode 100644
index 0000000..7df9fec
--- /dev/null
+++ b/lib/LICENSE-codemirror
@@ -0,0 +1,44 @@
+Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+----
+
+codemirror Python mode:
+----
+The MIT License
+
+Copyright (c) 2010 Timothy Farrell
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/lib/LICENSE-h2 b/lib/LICENSE-h2
new file mode 100644
index 0000000..1be4fba8
--- /dev/null
+++ b/lib/LICENSE-h2
@@ -0,0 +1,710 @@
+H2 is dual licensed and available under a modified version of the
+MPL 1.1 (Mozilla Public License) or under the (unmodified) EPL 1.0.
+----
+
+link:http://www.h2database.com/html/license.html[H2 License]
+
+----
+H2 License - Version 1.0
+1. Definitions
+
+1.0.1. "Commercial Use" means distribution or otherwise making the
+       Covered Code available to a third party.
+
+1.1. "Contributor" means each entity that creates or contributes
+     to the creation of Modifications.
+
+1.2. "Contributor Version" means the combination of the Original
+     Code, prior Modifications used by a Contributor, and the
+     Modifications made by that particular Contributor.
+
+1.3. "Covered Code" means the Original Code or Modifications or
+     the combination of the Original Code and Modifications, in each
+     case including portions thereof.
+
+1.4. "Electronic Distribution Mechanism" means a mechanism generally
+     accepted in the software development community for the electronic
+     transfer of data.
+
+1.5. "Executable" means Covered Code in any form other than Source Code.
+
+1.6. "Initial Developer" means the individual or entity identified
+     as the Initial Developer in the Source Code notice required
+     by Exhibit A.
+
+1.7. "Larger Work" means a work which combines Covered Code or
+     portions thereof with code not governed by the terms of this
+     License.
+
+1.8. "License" means this document.
+
+1.8.1. "Licensable" means having the right to grant, to the maximum
+       extent possible, whether at the time of the initial grant
+       or subsequently acquired, any and all of the rights conveyed
+       herein.
+
+1.9. "Modifications" means any addition to or deletion from the
+     substance or structure of either the Original Code or any
+     previous Modifications. When Covered Code is released as a
+     series of files, a Modification is:
+
+1.9.a. Any addition to or deletion from the contents of a file
+       containing Original Code or previous Modifications.
+
+1.9.b. Any new file that contains any part of the Original Code or
+       previous Modifications.
+
+1.10. "Original Code" means Source Code of computer software
+      code which is described in the Source Code notice required
+      by Exhibit A as Original Code, and which, at the time of
+      its release under this License is not already Covered Code
+      governed by this License.
+
+1.10.1. "Patent Claims" means any patent claim(s), now owned or
+        hereafter acquired, including without limitation, method,
+        process, and apparatus claims, in any patent Licensable
+        by grantor.
+
+1.11. "Source Code" means the preferred form of the Covered Code
+      for making modifications to it, including all modules it
+      contains, plus any associated interface definition files,
+      scripts used to control compilation and installation of an
+      Executable, or source code differential comparisons against
+      either the Original Code or another well known, available
+      Covered Code of the Contributor's choice. The Source Code can
+      be in a compressed or archival form, provided the appropriate
+      decompression or de-archiving software is widely available
+      for no charge.
+
+1.12. "You" (or "Your") means an individual or a legal entity
+      exercising rights under, and complying with all of the terms
+      of, this License or a future version of this License issued
+      under Section 6.1. For legal entities, "You" includes any
+      entity which controls, is controlled by, or is under common
+      control with You. For purposes of this definition, "control"
+      means (a) the power, direct or indirect, to cause the direction
+      or management of such entity, whether by contract or otherwise,
+      or (b) ownership of more than fifty percent (50%) of the
+      outstanding shares or beneficial ownership of such entity.
+
+2. Source Code License
+
+2.1. The Initial Developer Grant
+
+The Initial Developer hereby grants You a world-wide, royalty-free,
+non-exclusive license, subject to third party intellectual property
+claims:
+
+2.1.a. under intellectual property rights (other than patent
+       or trademark) Licensable by Initial Developer to use,
+       reproduce, modify, display, perform, sublicense and distribute
+       the Original Code (or portions thereof) with or without
+       Modifications, and/or as part of a Larger Work; and
+
+2.1.b. under Patents Claims infringed by the making, using or selling
+       of Original Code, to make, have made, use, practice, sell,
+       and offer for sale, and/or otherwise dispose of the Original
+       Code (or portions thereof).
+
+2.1.c. the licenses granted in this Section 2.1 (a) and (b) are
+       effective on the date Initial Developer first distributes
+       Original Code under the terms of this License.
+
+2.1.d. Notwithstanding Section 2.1 (b) above, no patent license is
+       granted: 1) for code that You delete from the Original Code;
+       2) separate from the Original Code; or 3) for infringements
+       caused by: i) the modification of the Original Code or ii)
+       the combination of the Original Code with other software
+       or devices.
+
+2.2. Contributor Grant
+
+Subject to third party intellectual property claims, each Contributor
+hereby grants You a world-wide, royalty-free, non-exclusive license
+
+2.2.a. under intellectual property rights (other than patent or
+       trademark) Licensable by Contributor, to use, reproduce,
+       modify, display, perform, sublicense and distribute the
+       Modifications created by such Contributor (or portions
+       thereof) either on an unmodified basis, with other
+       Modifications, as Covered Code and/or as part of a Larger
+       Work; and
+
+2.2.b. under Patent Claims infringed by the making, using, or selling
+       of Modifications made by that Contributor either alone and/or
+       in combination with its Contributor Version (or portions
+       of such combination), to make, use, sell, offer for sale,
+       have made, and/or otherwise dispose of: 1) Modifications
+       made by that Contributor (or portions thereof); and 2) the
+       combination of Modifications made by that Contributor with
+       its Contributor Version (or portions of such combination).
+
+2.2.c. the licenses granted in Sections 2.2 (a) and 2.2 (b) are
+       effective on the date Contributor first makes Commercial
+       Use of the Covered Code.
+
+2.2.c. Notwithstanding Section 2.2 (b) above, no patent license is
+       granted: 1) for any code that Contributor has deleted from
+       the Contributor Version; 2) separate from the Contributor
+       Version; 3) for infringements caused by: i) third party
+       modifications of Contributor Version or ii) the combination
+       of Modifications made by that Contributor with other software
+       (except as part of the Contributor Version) or other devices;
+       or 4) under Patent Claims infringed by Covered Code in the
+       absence of Modifications made by that Contributor.
+
+3. Distribution Obligations
+
+3.1. Application of License
+
+The Modifications which You create or to which You contribute
+are governed by the terms of this License, including without
+limitation Section 2.2. The Source Code version of Covered Code may
+be distributed only under the terms of this License or a future
+version of this License released under Section 6.1, and You must
+include a copy of this License with every copy of the Source Code
+You distribute. You may not offer or impose any terms on any Source
+Code version that alters or restricts the applicable version of
+this License or the recipients' rights hereunder. However, You
+may include an additional document offering the additional rights
+described in Section 3.5.
+
+3.2. Availability of Source Code
+
+Any Modification which You create or to which You contribute must
+be made available in Source Code form under the terms of this
+License either on the same media as an Executable version or via
+an accepted Electronic Distribution Mechanism to anyone to whom
+you made an Executable version available; and if made available
+via Electronic Distribution Mechanism, must remain available for
+at least twelve (12) months after the date it initially became
+available, or at least six (6) months after a subsequent version
+of that particular Modification has been made available to such
+recipients. You are responsible for ensuring that the Source Code
+version remains available even if the Electronic Distribution
+Mechanism is maintained by a third party.
+
+3.3. Description of Modifications
+
+You must cause all Covered Code to which You contribute to contain
+a file documenting the changes You made to create that Covered
+Code and the date of any change. You must include a prominent
+statement that the Modification is derived, directly or indirectly,
+from Original Code provided by the Initial Developer and including
+the name of the Initial Developer in (a) the Source Code, and (b)
+in any notice in an Executable version or related documentation in
+which You describe the origin or ownership of the Covered Code.
+
+3.4. Intellectual Property Matters
+
+3.4.a. Third Party Claims: If Contributor has knowledge that
+       a license under a third party's intellectual property
+       rights is required to exercise the rights granted by such
+       Contributor under Sections 2.1 or 2.2, Contributor must
+       include a text file with the Source Code distribution titled
+       "LEGAL" which describes the claim and the party making the
+       claim in sufficient detail that a recipient will know whom
+       to contact. If Contributor obtains such knowledge after the
+       Modification is made available as described in Section 3.2,
+       Contributor shall promptly modify the LEGAL file in all
+       copies Contributor makes available thereafter and shall take
+       other steps (such as notifying appropriate mailing lists or
+       newsgroups) reasonably calculated to inform those who received
+       the Covered Code that new knowledge has been obtained.
+
+3.4.b. Contributor APIs: If Contributor's Modifications include
+       an application programming interface and Contributor has
+       knowledge of patent licenses which are reasonably necessary
+       to implement that API, Contributor must also include this
+       information in the legal file.
+
+3.4.c. Representations: Contributor represents that, except as
+       disclosed pursuant to Section 3.4 (a) above, Contributor
+       believes that Contributor's Modifications are Contributor's
+       original creation(s) and/or Contributor has sufficient rights
+       to grant the rights conveyed by this License.
+
+3.5. Required Notices
+
+You must duplicate the notice in Exhibit A in each file of
+the Source Code. If it is not possible to put such notice in a
+particular Source Code file due to its structure, then You must
+include such notice in a location (such as a relevant directory)
+where a user would be likely to look for such a notice. If You
+created one or more Modification(s) You may add your name as a
+Contributor to the notice described in Exhibit A. You must also
+duplicate this License in any documentation for the Source Code
+where You describe recipients' rights or ownership rights relating
+to Covered Code. You may choose to offer, and to charge a fee for,
+warranty, support, indemnity or liability obligations to one or
+more recipients of Covered Code. However, You may do so only on
+Your own behalf, and not on behalf of the Initial Developer or
+any Contributor. You must make it absolutely clear than any such
+warranty, support, indemnity or liability obligation is offered by
+You alone, and You hereby agree to indemnify the Initial Developer
+and every Contributor for any liability incurred by the Initial
+Developer or such Contributor as a result of warranty, support,
+indemnity or liability terms You offer.
+
+3.6. Distribution of Executable Versions
+
+You may distribute Covered Code in Executable form only if the
+requirements of Sections 3.1, 3.2, 3.3, 3.4 and 3.5 have been met
+for that Covered Code, and if You include a notice stating that
+the Source Code version of the Covered Code is available under the
+terms of this License, including a description of how and where
+You have fulfilled the obligations of Section 3.2. The notice
+must be conspicuously included in any notice in an Executable
+version, related documentation or collateral in which You describe
+recipients' rights relating to the Covered Code. You may distribute
+the Executable version of Covered Code or ownership rights under
+a license of Your choice, which may contain terms different from
+this License, provided that You are in compliance with the terms
+of this License and that the license for the Executable version
+does not attempt to limit or alter the recipient's rights in the
+Source Code version from the rights set forth in this License. If
+You distribute the Executable version under a different license You
+must make it absolutely clear that any terms which differ from this
+License are offered by You alone, not by the Initial Developer or any
+Contributor. You hereby agree to indemnify the Initial Developer and
+every Contributor for any liability incurred by the Initial Developer
+or such Contributor as a result of any such terms You offer.
+
+3.7. Larger Works
+
+You may create a Larger Work by combining Covered Code with other
+code not governed by the terms of this License and distribute the
+Larger Work as a single product. In such a case, You must make sure
+the requirements of this License are fulfilled for the Covered Code.
+
+4. Inability to Comply Due to Statute or Regulation.
+
+If it is impossible for You to comply with any of the terms of
+this License with respect to some or all of the Covered Code due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description
+must be included in the legal file described in Section 3.4 and
+must be included with all distributions of the Source Code. Except
+to the extent prohibited by statute or regulation, such description
+must be sufficiently detailed for a recipient of ordinary skill to
+be able to understand it.
+
+5. Application of this License.
+
+This License applies to code to which the Initial Developer has
+attached the notice in Exhibit A and to related Covered Code.
+
+6. Versions of the License.
+
+6.1. New Versions
+
+The H2 Group may publish revised and/or new versions of the License
+from time to time. Each version will be given a distinguishing
+version number.
+
+6.2. Effect of New Versions
+
+Once Covered Code has been published under a particular version of
+the License, You may always continue to use it under the terms of
+that version. You may also choose to use such Covered Code under the
+terms of any subsequent version of the License published by the H2
+Group. No one other than the H2 Group has the right to modify the
+terms applicable to Covered Code created under this License.
+
+6.3. Derivative Works
+
+If You create or use a modified version of this License (which you
+may only do in order to apply it to code which is not already Covered
+Code governed by this License), You must (a) rename Your license so
+that the phrases "H2 Group", "H2" or any confusingly similar phrase
+do not appear in your license (except to note that your license
+differs from this License) and (b) otherwise make it clear that
+Your version of the license contains terms which differ from the
+H2 License. (Filling in the name of the Initial Developer, Original
+Code or Contributor in the notice described in Exhibit A shall not
+of themselves be deemed to be modifications of this License.)
+
+7. Disclaimer of Warranty
+
+Covered code is provided under this license on an "as is" basis,
+without warranty of any kind, either expressed or implied,
+including, without limitation, warranties that the covered code
+is free of defects, merchantable, fit for a particular purpose or
+non-infringing. The entire risk as to the quality and performance
+of the covered code is with you. Should any covered code prove
+defective in any respect, you (not the initial developer or any
+other contributor) assume the cost of any necessary servicing,
+repair or correction. This disclaimer of warranty constitutes
+an essential part of this license. No use of any covered code is
+authorized hereunder except under this disclaimer.
+
+8. Termination
+
+8.1. This License and the rights granted hereunder will terminate
+     automatically if You fail to comply with terms herein and
+     fail to cure such breach within 30 days of becoming aware
+     of the breach. All sublicenses to the Covered Code which
+     are properly granted shall survive any termination of this
+     License. Provisions which, by their nature, must remain in
+     effect beyond the termination of this License shall survive.
+
+8.2. If You initiate litigation by asserting a patent infringement
+     claim (excluding declaratory judgment actions) against
+     Initial Developer or a Contributor (the Initial Developer or
+     Contributor against whom You file such action is referred to as
+     "Participant") alleging that:
+
+8.2.a. such Participant's Contributor Version directly or indirectly
+       infringes any patent, then any and all rights granted by
+       such Participant to You under Sections 2.1 and/or 2.2 of this
+       License shall, upon 60 days notice from Participant terminate
+       prospectively, unless if within 60 days after receipt of
+       notice You either: (i) agree in writing to pay Participant
+       a mutually agreeable reasonable royalty for Your past and
+       future use of Modifications made by such Participant, or (ii)
+       withdraw Your litigation claim with respect to the Contributor
+       Version against such Participant. If within 60 days of notice,
+       a reasonable royalty and payment arrangement are not mutually
+       agreed upon in writing by the parties or the litigation claim
+       is not withdrawn, the rights granted by Participant to You
+       under Sections 2.1 and/or 2.2 automatically terminate at
+       the expiration of the 60 day notice period specified above.
+
+8.2.b. any software, hardware, or device, other than such
+       Participant's Contributor Version, directly or indirectly
+       infringes any patent, then any rights granted to You by
+       such Participant under Sections 2.1(b) and 2.2(b) are
+       revoked effective as of the date You first made, used,
+       sold, distributed, or had made, Modifications made by that
+       Participant.
+
+8.3. If You assert a patent infringement claim against Participant
+     alleging that such Participant's Contributor Version directly
+     or indirectly infringes any patent where such claim is resolved
+     (such as by license or settlement) prior to the initiation of
+     patent infringement litigation, then the reasonable value of
+     the licenses granted by such Participant under Sections 2.1
+     or 2.2 shall be taken into account in determining the amount
+     or value of any payment or license.
+
+8.4. In the event of termination under Sections 8.1 or 8.2 above,
+     all end user license agreements (excluding distributors and
+     resellers) which have been validly granted by You or any
+     distributor hereunder prior to termination shall survive
+     termination.
+
+9. Limitation of Liability
+
+Under no circumstances and under no legal theory, whether tort
+(including negligence), contract, or otherwise, shall you, the
+initial developer, any other contributor, or any distributor of
+covered code, or any supplier of any of such parties, be liable to
+any person for any indirect, special, incidental, or consequential
+damages of any character including, without limitation, damages for
+loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses, even if such party
+shall have been informed of the possibility of such damages. This
+limitation of liability shall not apply to liability for death or
+personal injury resulting from such party's negligence to the extent
+applicable law prohibits such limitation. Some jurisdictions do not
+allow the exclusion or limitation of incidental or consequential
+damages, so this exclusion and limitation may not apply to you.
+
+10. United States Government End Users
+
+The Covered Code is a "commercial item", as that term is defined in
+48 C.F.R. 2.101 (October 1995), consisting of "commercial computer
+software" and "commercial computer software documentation", as such
+terms are used in 48 C.F.R. 12.212 (September 1995). Consistent
+with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4
+(June 1995), all U.S. Government End Users acquire Covered Code
+with only those rights set forth herein.
+
+11. Miscellaneous
+
+This License represents the complete agreement concerning subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. This License shall be governed
+by California law provisions (except to the extent applicable
+law, if any, provides otherwise), excluding its conflict-of-law
+provisions. With respect to disputes in which at least one party is
+a citizen of, or an entity chartered or registered to do business in
+United States of America, any litigation relating to this License
+shall be subject to the jurisdiction of the Federal Courts of the
+Northern District of California, with venue lying in Santa Clara
+County, California, with the losing party responsible for costs,
+including without limitation, court costs and reasonable attorneys'
+fees and expenses. The application of the United Nations Convention
+on Contracts for the International Sale of Goods is expressly
+excluded. Any law or regulation which provides that the language of
+a contract shall be construed against the drafter shall not apply
+to this License.
+
+12. Responsibility for Claims
+
+As between Initial Developer and the Contributors, each party is
+responsible for claims and damages arising, directly or indirectly,
+out of its utilization of rights under this License and You agree
+to work with Initial Developer and Contributors to distribute such
+responsibility on an equitable basis. Nothing herein is intended
+or shall be deemed to constitute any admission of liability.
+
+13. Multiple-Licensed Code
+
+Initial Developer may designate portions of the Covered Code as
+"Multiple-Licensed". "Multiple-Licensed" means that the Initial
+Developer permits you to utilize portions of the Covered Code under
+Your choice of this or the alternative licenses, if any, specified
+by the Initial Developer in the file described in Exhibit A.
+
+Exhibit A
+
+Multiple-Licensed under the H2 License, Version 1.0,
+and under the Eclipse Public License, Version 1.0
+(http://h2database.com/html/license.html).
+Initial Developer: H2 Group
+----
+
+----
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and
+   documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from
+and are distributed by that particular Contributor. A Contribution
+'originates' from a Contributor if it was added to the Program
+by such Contributor itself or anyone acting on such Contributor's
+behalf. Contributions do not include additions to the Program which:
+(i) are separate modules of software distributed in conjunction
+with the Program under their own license agreement, and (ii) are
+not derivative works of the Program.
+
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents " mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its
+Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with
+this Agreement.
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby
+   grants Recipient a non-exclusive, worldwide, royalty-free copyright
+   license to reproduce, prepare derivative works of, publicly display,
+   publicly perform, distribute and sublicense the Contribution of such
+   Contributor, if any, and such derivative works, in source code and
+   object code form.
+
+b) Subject to the terms of this Agreement, each Contributor hereby
+   grants Recipient a non-exclusive, worldwide, royalty-free patent
+   license under Licensed Patents to make, use, sell, offer to sell,
+   import and otherwise transfer the Contribution of such Contributor,
+   if any, in source code and object code form. This patent license
+   shall apply to the combination of the Contribution and the Program
+   if, at the time the Contribution is added by the Contributor, such
+   addition of the Contribution causes such combination to be covered
+   by the Licensed Patents. The patent license shall not apply to any
+   other combinations which include the Contribution. No hardware per
+   se is licensed hereunder.
+
+c) Recipient understands that although each Contributor grants the
+   licenses to its Contributions set forth herein, no assurances are
+   provided by any Contributor that the Program does not infringe
+   the patent or other intellectual property rights of any other
+   entity. Each Contributor disclaims any liability to Recipient
+   for claims brought by any other entity based on infringement
+   of intellectual property rights or otherwise. As a condition to
+   exercising the rights and licenses granted hereunder, each Recipient
+   hereby assumes sole responsibility to secure any other intellectual
+   property rights needed, if any. For example, if a third party patent
+   license is required to allow Recipient to distribute the Program,
+   it is Recipient's responsibility to acquire that license before
+   distributing the Program.
+
+d) Each Contributor represents that to its knowledge it has
+   sufficient copyright rights in its Contribution, if any, to grant
+   the copyright license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code
+  form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors all warranties
+   and conditions, express and implied, including warranties or
+   conditions of title and non-infringement, and implied warranties or
+   conditions of merchantability and fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors all liability
+    for damages, including direct, indirect, special, incidental and
+    consequential damages, such as lost profits;
+
+iii) states that any provisions which differ from this Agreement
+     are offered by that Contributor alone and not by any other
+     party; and
+
+iv) states that source code for the Program is available from such
+    Contributor, and informs licensees how to obtain it in a reasonable
+    manner on or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained
+within the Program.
+
+Each Contributor must identify itself as the originator of its
+Contribution, if any, in a manner that reasonably allows subsequent
+Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial
+use of the Program, the Contributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for other Contributors. Therefore, if a
+Contributor includes the Program in a commercial product offering,
+such Contributor ("Commercial Contributor") hereby agrees to defend
+and indemnify every other Contributor ("Indemnified Contributor")
+against any losses, damages and costs (collectively "Losses") arising
+from claims, lawsuits and other legal actions brought by a third
+party against the Indemnified Contributor to the extent caused by
+the acts or omissions of such Commercial Contributor in connection
+with its distribution of the Program in a commercial product
+offering. The obligations in this section do not apply to any claims
+or Losses relating to any actual or alleged intellectual property
+infringement. In order to qualify, an Indemnified Contributor must:
+a) promptly notify the Commercial Contributor in writing of such
+claim, and b) allow the Commercial Contributor to control, and
+cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may
+participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a
+commercial product offering, Product X. That Contributor is then a
+Commercial Contributor. If that Commercial Contributor then makes
+performance claims, or offers warranties related to Product X, those
+performance claims and warranties are such Commercial Contributor's
+responsibility alone. Under this section, the Commercial Contributor
+would have to defend claims against the other Contributors related
+to those performance claims and warranties, and if a court requires
+any other Contributor to pay any damages as a result, the Commercial
+Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with
+its exercise of rights under this Agreement , including but not
+limited to the risks and costs of program errors, compliance with
+applicable laws, damage to or loss of data, programs or equipment,
+and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY
+RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed
+to the minimum extent necessary to make such provision valid and
+enforceable.
+
+If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging
+that the Program itself (excluding combinations of the Program with
+other software or hardware) infringes such Recipient's patent(s),
+then such Recipient's rights granted under Section 2(b) shall
+terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if
+it fails to comply with any of the material terms or conditions
+of this Agreement and does not cure such failure in a reasonable
+period of time after becoming aware of such noncompliance. If all
+Recipient's rights under this Agreement terminate, Recipient agrees
+to cease use and distribution of the Program as soon as reasonably
+practicable. However, Recipient's obligations under this Agreement
+and any licenses granted by Recipient relating to the Program shall
+continue and survive.
+
+Everyone is permitted to copy and distribute copies of this
+Agreement, but in order to avoid inconsistency the Agreement is
+copyrighted and may only be modified in the following manner. The
+Agreement Steward reserves the right to publish new versions
+(including revisions) of this Agreement from time to time. No
+one other than the Agreement Steward has the right to modify
+this Agreement. The Eclipse Foundation is the initial Agreement
+Steward. The Eclipse Foundation may assign the responsibility to
+serve as the Agreement Steward to a suitable separate entity. Each
+new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+distributed subject to the version of the Agreement under which it
+was received. In addition, after a new version of the Agreement is
+published, Contributor may elect to distribute the Program (including
+its Contributions) under the new version. Except as expressly stated
+in Sections 2(a) and 2(b) above, Recipient receives no rights or
+licenses to the intellectual property of any Contributor under
+this Agreement, whether expressly, by implication, estoppel or
+otherwise. All rights in the Program not expressly granted under
+this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this
+Agreement more than one year after the cause of action arose. Each
+party waives its rights to a jury trial in any resulting litigation.
+----
+
+----
+Export Control Classification Number (ECCN)
+
+As far as we know, the U.S. Export Control Classification Number
+(ECCN) for this software is 5D002. However, for legal reasons, we
+can make no warranty that this information is correct. For details,
+see also the Apache Software Foundation Export Classifications page.
diff --git a/lib/LICENSE-jgit b/lib/LICENSE-jgit
new file mode 100644
index 0000000..1b85c64
--- /dev/null
+++ b/lib/LICENSE-jgit
@@ -0,0 +1,37 @@
+This program and the accompanying materials are made available
+under the terms of the Eclipse Distribution License v1.0 which
+accompanies this distribution, is reproduced below, and is
+available at http://www.eclipse.org/org/documents/edl-v10.php
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the following
+conditions are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the following
+  disclaimer in the documentation and/or other materials provided
+  with the distribution.
+
+- Neither the name of the Eclipse Foundation, Inc. nor the
+  names of its contributors may be used to endorse or promote
+  products derived from this software without specific prior
+  written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-jsch b/lib/LICENSE-jsch
new file mode 100644
index 0000000..2cb0ddd
--- /dev/null
+++ b/lib/LICENSE-jsch
@@ -0,0 +1,26 @@
+Copyright (c) 2002-2012 Atsuhiko Yamanaka, JCraft,Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  1. Redistributions of source code must retain the above copyright notice,
+     this list of conditions and the following disclaimer.
+
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in
+     the documentation and/or other materials provided with the distribution.
+
+  3. The names of the authors may not be used to endorse or promote products
+     derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-jsr305 b/lib/LICENSE-jsr305
new file mode 100644
index 0000000..cf30ea2
--- /dev/null
+++ b/lib/LICENSE-jsr305
@@ -0,0 +1,28 @@
+Copyright (c) 2007-2009, JSR305 expert group
+All rights reserved.
+
+http://www.opensource.org/licenses/bsd-license.php
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of the JSR305 expert group nor the names of its
+      contributors may be used to endorse or promote products derived from
+      this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-ow2 b/lib/LICENSE-ow2
new file mode 100644
index 0000000..c5aba7b
--- /dev/null
+++ b/lib/LICENSE-ow2
@@ -0,0 +1,29 @@
+Copyright (c) 2000-2011 INRIA, France Telecom
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holders nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-postgresql b/lib/LICENSE-postgresql
new file mode 100644
index 0000000..fd416d2
--- /dev/null
+++ b/lib/LICENSE-postgresql
@@ -0,0 +1,26 @@
+Copyright (c) 1997-2011, PostgreSQL Global Development Group
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+3. Neither the name of the PostgreSQL Global Development Group nor the names
+   of its contributors may be used to endorse or promote products derived
+   from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/LICENSE-prologcafe b/lib/LICENSE-prologcafe
new file mode 100644
index 0000000..7183d37
--- /dev/null
+++ b/lib/LICENSE-prologcafe
@@ -0,0 +1,593 @@
+Prolog Cafe (A Prolog to Java Translator System)
+Copyright (C) 1997-2009 by Mutsunori Banbara and Naoyuki Tamura
+
+Prolog Cafe is free software; you can redistribute it and/or modify
+it under the terms of either:
+
+  * the GNU General Public License as published by the Free Software
+    Foundation; either version 2 of the License, or (at your option)
+    any later version, or
+
+  * the Eclipse Public License
+----
+
+In the context of Gerrit Code Review, Prolog Cafe is consumed under
+the <<prologcafe_EPL,EPL>>. Gerrit Code Review uses a fork derived
+from the 1.2.5 release and offers the corresponding source code at
+link:https://gerrit.googlesource.com/prolog-cafe[].
+
+----
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+----
+
+[[prologcafe_EPL]]
+----
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and
+   documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from
+and are distributed by that particular Contributor. A Contribution
+'originates' from a Contributor if it was added to the Program
+by such Contributor itself or anyone acting on such Contributor's
+behalf. Contributions do not include additions to the Program which:
+(i) are separate modules of software distributed in conjunction
+with the Program under their own license agreement, and (ii) are
+not derivative works of the Program.
+
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents " mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its
+Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with
+this Agreement.
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby
+   grants Recipient a non-exclusive, worldwide, royalty-free copyright
+   license to reproduce, prepare derivative works of, publicly display,
+   publicly perform, distribute and sublicense the Contribution of such
+   Contributor, if any, and such derivative works, in source code and
+   object code form.
+
+b) Subject to the terms of this Agreement, each Contributor hereby
+   grants Recipient a non-exclusive, worldwide, royalty-free patent
+   license under Licensed Patents to make, use, sell, offer to sell,
+   import and otherwise transfer the Contribution of such Contributor,
+   if any, in source code and object code form. This patent license
+   shall apply to the combination of the Contribution and the Program
+   if, at the time the Contribution is added by the Contributor, such
+   addition of the Contribution causes such combination to be covered
+   by the Licensed Patents. The patent license shall not apply to any
+   other combinations which include the Contribution. No hardware per
+   se is licensed hereunder.
+
+c) Recipient understands that although each Contributor grants the
+   licenses to its Contributions set forth herein, no assurances are
+   provided by any Contributor that the Program does not infringe
+   the patent or other intellectual property rights of any other
+   entity. Each Contributor disclaims any liability to Recipient
+   for claims brought by any other entity based on infringement
+   of intellectual property rights or otherwise. As a condition to
+   exercising the rights and licenses granted hereunder, each Recipient
+   hereby assumes sole responsibility to secure any other intellectual
+   property rights needed, if any. For example, if a third party patent
+   license is required to allow Recipient to distribute the Program,
+   it is Recipient's responsibility to acquire that license before
+   distributing the Program.
+
+d) Each Contributor represents that to its knowledge it has
+   sufficient copyright rights in its Contribution, if any, to grant
+   the copyright license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code
+  form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors all warranties
+   and conditions, express and implied, including warranties or
+   conditions of title and non-infringement, and implied warranties or
+   conditions of merchantability and fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors all liability
+    for damages, including direct, indirect, special, incidental and
+    consequential damages, such as lost profits;
+
+iii) states that any provisions which differ from this Agreement
+     are offered by that Contributor alone and not by any other
+     party; and
+
+iv) states that source code for the Program is available from such
+    Contributor, and informs licensees how to obtain it in a reasonable
+    manner on or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained
+within the Program.
+
+Each Contributor must identify itself as the originator of its
+Contribution, if any, in a manner that reasonably allows subsequent
+Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial
+use of the Program, the Contributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for other Contributors. Therefore, if a
+Contributor includes the Program in a commercial product offering,
+such Contributor ("Commercial Contributor") hereby agrees to defend
+and indemnify every other Contributor ("Indemnified Contributor")
+against any losses, damages and costs (collectively "Losses") arising
+from claims, lawsuits and other legal actions brought by a third
+party against the Indemnified Contributor to the extent caused by
+the acts or omissions of such Commercial Contributor in connection
+with its distribution of the Program in a commercial product
+offering. The obligations in this section do not apply to any claims
+or Losses relating to any actual or alleged intellectual property
+infringement. In order to qualify, an Indemnified Contributor must:
+a) promptly notify the Commercial Contributor in writing of such
+claim, and b) allow the Commercial Contributor to control, and
+cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may
+participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a
+commercial product offering, Product X. That Contributor is then a
+Commercial Contributor. If that Commercial Contributor then makes
+performance claims, or offers warranties related to Product X, those
+performance claims and warranties are such Commercial Contributor's
+responsibility alone. Under this section, the Commercial Contributor
+would have to defend claims against the other Contributors related
+to those performance claims and warranties, and if a court requires
+any other Contributor to pay any damages as a result, the Commercial
+Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with
+its exercise of rights under this Agreement , including but not
+limited to the risks and costs of program errors, compliance with
+applicable laws, damage to or loss of data, programs or equipment,
+and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY
+RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed
+to the minimum extent necessary to make such provision valid and
+enforceable.
+
+If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging
+that the Program itself (excluding combinations of the Program with
+other software or hardware) infringes such Recipient's patent(s),
+then such Recipient's rights granted under Section 2(b) shall
+terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if
+it fails to comply with any of the material terms or conditions
+of this Agreement and does not cure such failure in a reasonable
+period of time after becoming aware of such noncompliance. If all
+Recipient's rights under this Agreement terminate, Recipient agrees
+to cease use and distribution of the Program as soon as reasonably
+practicable. However, Recipient's obligations under this Agreement
+and any licenses granted by Recipient relating to the Program shall
+continue and survive.
+
+Everyone is permitted to copy and distribute copies of this
+Agreement, but in order to avoid inconsistency the Agreement is
+copyrighted and may only be modified in the following manner. The
+Agreement Steward reserves the right to publish new versions
+(including revisions) of this Agreement from time to time. No
+one other than the Agreement Steward has the right to modify
+this Agreement. The Eclipse Foundation is the initial Agreement
+Steward. The Eclipse Foundation may assign the responsibility to
+serve as the Agreement Steward to a suitable separate entity. Each
+new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+distributed subject to the version of the Agreement under which it
+was received. In addition, after a new version of the Agreement is
+published, Contributor may elect to distribute the Program (including
+its Contributions) under the new version. Except as expressly stated
+in Sections 2(a) and 2(b) above, Recipient receives no rights or
+licenses to the intellectual property of any Contributor under
+this Agreement, whether expressly, by implication, estoppel or
+otherwise. All rights in the Program not expressly granted under
+this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this
+Agreement more than one year after the cause of action arose. Each
+party waives its rights to a jury trial in any resulting litigation.
diff --git a/lib/LICENSE-slf4j b/lib/LICENSE-slf4j
new file mode 100644
index 0000000..f5ecafa
--- /dev/null
+++ b/lib/LICENSE-slf4j
@@ -0,0 +1,21 @@
+Copyright (c) 2004-2008 QOS.ch
+All rights reserved.
+
+Permission is hereby granted, free  of charge, to any person obtaining
+a  copy  of this  software  and  associated  documentation files  (the
+"Software"), to  deal in  the Software without  restriction, including
+without limitation  the rights to  use, copy, modify,  merge, publish,
+distribute,  sublicense, and/or sell  copies of  the Software,  and to
+permit persons to whom the Software  is furnished to do so, subject to
+the following conditions:
+
+The  above  copyright  notice  and  this permission  notice  shall  be
+included in all copies or substantial portions of the Software.
+
+THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/antlr/BUCK b/lib/antlr/BUCK
new file mode 100644
index 0000000..732b459
--- /dev/null
+++ b/lib/antlr/BUCK
@@ -0,0 +1,48 @@
+include_defs('//lib/maven.defs')
+
+VERSION = '3.2'
+
+maven_jar(
+  name = 'java_runtime',
+  id = 'org.antlr:antlr-runtime:' + VERSION,
+  sha1 = '31c746001016c6226bd7356c9f87a6a084ce3715',
+  license = 'antlr',
+)
+
+java_binary(
+  name = 'antlr-tool',
+  main_class = 'org.antlr.Tool',
+  deps = [':tool'],
+  visibility = ['PUBLIC'],
+)
+
+maven_jar(
+  name = 'stringtemplate',
+  id = 'org.antlr:stringtemplate:' + VERSION,
+  sha1 = '6fe2e3bb57daebd1555494818909f9664376dd6c',
+  license = 'antlr',
+  attach_source = False,
+  visibility = [],
+)
+
+maven_jar(
+  name = 'tool',
+  id = 'org.antlr:antlr:' + VERSION,
+  sha1 = '6b0acabea7bb3da058200a77178057e47e25cb69',
+  license = 'antlr',
+  deps = [
+    ':java_runtime',
+    ':stringtemplate',
+    ':antlr27',
+  ],
+  visibility = [],
+)
+
+maven_jar(
+  name = 'antlr27',
+  id = 'antlr:antlr:2.7.7',
+  sha1 = '83cd2cd674a217ade95a4bb83a8a14f351f48bd0',
+  license = 'antlr',
+  attach_source = False,
+  visibility = [],
+)
diff --git a/lib/bouncycastle/BUCK b/lib/bouncycastle/BUCK
new file mode 100644
index 0000000..3d76ea6
--- /dev/null
+++ b/lib/bouncycastle/BUCK
@@ -0,0 +1,20 @@
+include_defs('//lib/maven.defs')
+
+# This version must match the version that also appears in
+# gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
+VERSION = '1.44'
+
+maven_jar(
+  name = 'bcprov',
+  id = 'org.bouncycastle:bcprov-jdk16:' + VERSION,
+  sha1 = '6327a5f7a3dc45e0fd735adb5d08c5a74c05c20c',
+  license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
+)
+
+maven_jar(
+  name = 'bcpg',
+  id = 'org.bouncycastle:bcpg-jdk16:' + VERSION,
+  sha1 = 'ee14f5a29cb3cf9c1edec034ab16e1bbd26e9647',
+  license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
+  deps = [':bcprov'],
+)
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
new file mode 100644
index 0000000..47623b5
--- /dev/null
+++ b/lib/codemirror/BUCK
@@ -0,0 +1,43 @@
+include_defs('//lib/maven.defs')
+
+VERSION = 'b05b96e030'
+SHA1 = '42c541dfdeb877ad726d265e7b1e7da0d429af84'
+URL = GERRIT + 'net/codemirror/codemirror-%s.zip' % VERSION
+
+prebuilt_jar(
+  name = 'codemirror',
+  binary_jar = genfile('codemirror.jar'),
+  deps = [
+    ':jar',
+    '//lib:LICENSE-codemirror',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+# TODO(sop) Repackage by license boundaries.
+# TODO(sop) Minify with Closure JS compiler.
+genrule(
+  name = 'jar',
+  cmd = ';'.join([
+    'cd $TMP',
+    'mkdir net META-INF',
+    'unzip -d net $SRCS',
+    'mv net/codemirror-%s net/codemirror' % VERSION,
+    'echo "Manifest-Version: 1.0" >META-INF/MANIFEST.MF',
+    'zip -r $OUT *'
+  ]),
+  srcs = [genfile('codemirror-' + VERSION + '.zip')],
+  deps = [':download'],
+  out = 'codemirror.jar',
+)
+
+genrule(
+  name = 'download',
+  cmd = '${//tools:download_file}' +
+    ' -o $OUT' +
+    ' -u ' + URL +
+    ' -v ' + SHA1,
+  srcs = [],
+  deps = ['//tools:download_file'],
+  out = 'codemirror-' + VERSION + '.zip',
+)
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
new file mode 100644
index 0000000..6f412e4
--- /dev/null
+++ b/lib/commons/BUCK
@@ -0,0 +1,105 @@
+include_defs('//lib/maven.defs')
+
+maven_jar(
+  name = 'codec',
+  id = 'commons-codec:commons-codec:1.4',
+  sha1 = '4216af16d38465bbab0f3dff8efa14204f7a399a',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
+)
+
+maven_jar(
+  name = 'collections',
+  id = 'commons-collections:commons-collections:3.2.1',
+  sha1 = '761ea405b9b37ced573d2df0d1e3a4e0f9edc668',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
+  attach_source = False,
+  visibility = [
+    '//lib:velocity',
+    '//lib/solr:zookeeper',
+  ],
+)
+
+maven_jar(
+  name = 'dbcp',
+  id = 'commons-dbcp:commons-dbcp:1.4',
+  sha1 = '30be73c965cc990b153a100aaaaafcf239f82d39',
+  license = 'Apache2.0',
+  deps = [':pool'],
+  exclude = [
+    'META-INF/LICENSE.txt',
+    'META-INF/NOTICE.txt',
+    'testpool.jocl'
+  ],
+)
+
+maven_jar(
+  name = 'lang',
+  id = 'commons-lang:commons-lang:2.5',
+  sha1 = 'b0236b252e86419eef20c31a44579d2aee2f0a69',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
+)
+
+maven_jar(
+  name = 'net',
+  id = 'commons-net:commons-net:2.2',
+  sha1 = '07993c12f63c78378f8c90de4bc2ee62daa7ca3a',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
+)
+
+maven_jar(
+  name = 'pool',
+  id = 'commons-pool:commons-pool:1.5.5',
+  sha1 = '7d8ffbdc47aa0c5a8afe5dc2aaf512f369f1d19b',
+  license = 'Apache2.0',
+  attach_source = False,
+  exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
+)
+
+maven_jar(
+  name = 'oro',
+  id = 'oro:oro:2.0.8',
+  sha1 = '5592374f834645c4ae250f4c9fbb314c9369d698',
+  license = 'Apache1.1',
+  attach_source = False,
+  exclude = ['META-INF/LICENSE'],
+)
+
+maven_jar(
+  name = 'io',
+  id = 'commons-io:commons-io:1.4',
+  sha1 = 'a8762d07e76cfde2395257a5da47ba7c1dbd3dce',
+  license = 'Apache2.0',
+)
+
+maven_jar(
+  name = 'httpclient',
+  id = 'org.apache.httpcomponents:httpclient:4.2.5',
+  bin_sha1 = '666e26e76f2e87d84e4f16acb546481ae1b8e9a6',
+  src_sha1 = '55d345272944d7e8dace47925336a3764ee0e24b',
+  license = 'Apache2.0',
+  deps = [
+    ':codec',
+    ':httpcore',
+    '//lib/log:jcl-over-slf4j',
+  ],
+)
+
+maven_jar(
+  name = 'httpcore',
+  id = 'org.apache.httpcomponents:httpcore:4.2.4',
+  bin_sha1 = '3b7f38df6de5dd8b500e602ae8c2dd5ee446f883',
+  src_sha1 = 'c3ffe3a73348645042fb0b06303b6a3de194494e',
+  license = 'Apache2.0',
+)
+
+maven_jar(
+  name = 'httpmime',
+  id = 'org.apache.httpcomponents:httpmime:4.2.5',
+  bin_sha1 = '412b9914d0adec6d5716df1ada8acbc4f6f2dd37',
+  src_sha1 = 'c07ce7f6b153284a9ebaf58532c2442200cf3aa2',
+  license = 'Apache2.0',
+)
diff --git a/lib/guice/BUCK b/lib/guice/BUCK
new file mode 100644
index 0000000..48c3be3
--- /dev/null
+++ b/lib/guice/BUCK
@@ -0,0 +1,62 @@
+include_defs('//lib/maven.defs')
+
+VERSION = '3.0'
+EXCLUDE = [
+  'META-INF/DEPENDENCIES',
+  'META-INF/LICENSE',
+  'META-INF/NOTICE',
+]
+
+java_library(
+  name = 'guice',
+  deps = [
+    ':guice_library',
+    ':javax-inject',
+  ],
+  export_deps = True,
+  visibility = ['PUBLIC'],
+)
+
+maven_jar(
+  name = 'guice_library',
+  id = 'com.google.inject:guice:' + VERSION,
+  sha1 = '9d84f15fe35e2c716a02979fb62f50a29f38aefa',
+  license = 'Apache2.0',
+  deps = [':aopalliance'],
+  exclude = EXCLUDE,
+  visibility = [],
+)
+
+maven_jar(
+  name = 'guice-assistedinject',
+  id = 'com.google.inject.extensions:guice-assistedinject:' + VERSION,
+  sha1 = '544449ddb19f088dcde44f055d30a08835a954a7',
+  license = 'Apache2.0',
+  deps = [':guice'],
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'guice-servlet',
+  id = 'com.google.inject.extensions:guice-servlet:' + VERSION,
+  sha1 = '610cde0e8da5a8b7d8efb8f0b8987466ffebaaf9',
+  license = 'Apache2.0',
+  deps = [':guice'],
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'aopalliance',
+  id = 'aopalliance:aopalliance:1.0',
+  sha1 = '0235ba8b489512805ac13a8f9ea77a1ca5ebe3e8',
+  license = 'PublicDomain',
+  visibility = ['//lib/guice:guice'],
+)
+
+maven_jar(
+  name = 'javax-inject',
+  id = 'javax.inject:javax.inject:1',
+  sha1 = '6975da39a7040257bd51d21a231b76c915872d38',
+  license = 'Apache2.0',
+  visibility = ['//lib/guice:guice'],
+)
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
new file mode 100644
index 0000000..9f37c9d
--- /dev/null
+++ b/lib/gwt/BUCK
@@ -0,0 +1,60 @@
+include_defs('//lib/maven.defs')
+
+VERSION = '2.5.0'
+
+maven_jar(
+  name = 'user',
+  id = 'com.google.gwt:gwt-user:' + VERSION,
+  sha1 = 'bbec0cc2ba81a2601aac55e3783ad0d0ff7d45ac',
+  license = 'Apache2.0',
+  attach_source = False,
+)
+
+maven_jar(
+  name = 'dev',
+  id = 'com.google.gwt:gwt-dev:' + VERSION,
+  sha1 = '3c227524e0978036e76e73e125ac8535a9e8fd7c',
+  license = 'Apache2.0',
+  deps = [
+    ':javax-validation',
+    ':javax-validation_src',
+  ],
+  attach_source = False,
+)
+
+maven_jar(
+  name = 'javax-validation',
+  id = 'javax.validation:validation-api:1.0.0.GA',
+  bin_sha1 = 'b6bd7f9d78f6fdaa3c37dae18a4bd298915f328e',
+  src_sha1 = '7a561191db2203550fbfa40d534d4997624cd369',
+  license = 'Apache2.0',
+  visibility = [],
+)
+
+python_binary(
+  name = 'compiler',
+  main = 'compiler.py',
+  visibility = ['PUBLIC'],
+)
+
+maven_jar(
+  name = 'gwt-test-utils',
+  id = 'com.googlecode.gwt-test-utils:gwt-test-utils:0.45-SNAPSHOT-20130612.054327-7',
+  sha1 = 'ad53b8a05df35fbe394db80d1d7d2913a646bfb3',
+  license = 'Apache2.0',
+  repository = 'http://oss.sonatype.org/content/repositories/snapshots',
+  deps = [
+    ':javassist',
+    '//lib/log:api',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+maven_jar(
+  name = 'javassist',
+  id = 'org.javassist:javassist:3.16.1-GA',
+  sha1 = '315891b371395271977af518d4db5cee1a0bc9bf',
+  license = 'Apache2.0',
+  visibility = [],
+)
+
diff --git a/lib/gwt/compiler.py b/lib/gwt/compiler.py
new file mode 100755
index 0000000..4318aac
--- /dev/null
+++ b/lib/gwt/compiler.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+# 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.
+
+from __future__ import print_function
+
+from multiprocessing import cpu_count
+from os import environ, makedirs, mkdir, path
+from subprocess import Popen, PIPE
+from sys import argv, stderr
+
+cp, opt, end, TMP = [], [], False, environ['TMP']
+module, outzip = argv[1], argv[2]
+
+for a in argv[3:]:
+  if end:
+    if a.endswith('.jar'):
+      cp.append(a)
+  elif a == '--':
+    end = True
+  else:
+    opt.append(a)
+
+if not outzip.endswith('.zip'):
+  print("%s must end with .zip" % outzip, file=stderr)
+  exit(1)
+
+rebuild = outzip[:-4] + '.rebuild'
+for d in ['deploy', 'unit_cache', 'work']:
+  mkdir(path.join(TMP, d))
+if not path.exists(path.dirname(outzip)):
+  makedirs(path.dirname(outzip))
+
+cmd = [
+  'java', '-Xmx512m',
+  '-Djava.io.tmpdir=' + TMP,
+  '-Dgwt.normalizeTimestamps=true',
+  '-Dgwt.persistentunitcachedir=' + path.join(TMP, 'unit_cache'),
+  '-classpath', ':'.join(cp),
+  'com.google.gwt.dev.Compiler',
+  '-deploy', path.join(TMP, 'deploy'),
+  '-workDir', path.join(TMP, 'work'),
+  '-war', outzip,
+  '-localWorkers', str(cpu_count()),
+] + opt + [module]
+
+gwt = Popen(cmd, stdout = PIPE, stderr = PIPE)
+out, err = gwt.communicate()
+if gwt.returncode != 0:
+  print(out + err, file=stderr)
+  exit(gwt.returncode)
diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK
new file mode 100644
index 0000000..6eac1a9
--- /dev/null
+++ b/lib/jetty/BUCK
@@ -0,0 +1,75 @@
+include_defs('//lib/maven.defs')
+
+VERSION = '8.1.7.v20120910'
+EXCLUDE = ['about.html']
+
+maven_jar(
+  name = 'servlet',
+  id = 'org.eclipse.jetty:jetty-servlet:' + VERSION,
+  sha1 = '93da01e3ea26e70449e9a1a0affa5c31436be5a0',
+  license = 'Apache2.0',
+  deps = [
+    ':security',
+    '//lib:servlet-api-3_0',
+  ],
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'security',
+  id = 'org.eclipse.jetty:jetty-security:' + VERSION,
+  sha1 = '8d78beb7a07f4cccee05a3f16a264f1025946258',
+  license = 'Apache2.0',
+  deps = [':server'],
+  exclude = EXCLUDE,
+  visibility = [],
+)
+
+maven_jar(
+  name = 'server',
+  id = 'org.eclipse.jetty:jetty-server:' + VERSION,
+  sha1 = '6c81f733f28713919e99c2f8952e6ca5178033cd',
+  license = 'Apache2.0',
+  deps = [
+    ':continuation',
+    ':http',
+  ],
+  export_deps = True,
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'continuation',
+  id = 'org.eclipse.jetty:jetty-continuation:' + VERSION,
+  sha1 = 'f60cfe6267038000b459508529c88737601081e4',
+  license = 'Apache2.0',
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'http',
+  id = 'org.eclipse.jetty:jetty-http:' + VERSION,
+  sha1 = '10126433876cd74534695f7f99c4362596555493',
+  license = 'Apache2.0',
+  deps = [':io'],
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'io',
+  id = 'org.eclipse.jetty:jetty-io:' + VERSION,
+  sha1 = 'a81f746ae1b10c37e1bb0a01d1374c202c0bd549',
+  license = 'Apache2.0',
+  deps = [':util'],
+  exclude = EXCLUDE,
+  visibility = [],
+)
+
+maven_jar(
+  name = 'util',
+  id = 'org.eclipse.jetty:jetty-util:' + VERSION,
+  sha1 = '7eb2004ab2c22fd3b00095bd9ba0f32a9e88f6a5',
+  license = 'Apache2.0',
+  exclude = EXCLUDE,
+  visibility = [],
+)
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
new file mode 100644
index 0000000..b8ff017
--- /dev/null
+++ b/lib/jgit/BUCK
@@ -0,0 +1,65 @@
+include_defs('//lib/maven.defs')
+
+REPO = GERRIT
+VERS = '3.0.0.201306101825-r.41-g84d2738'
+
+maven_jar(
+  name = 'jgit',
+  id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
+  bin_sha1 = 'fec8584e9e60ab3f43c3dd136f72662818e07766',
+  src_sha1 = '398e49984b281dbcd913b3894b8b9582c8b2cf9a',
+  license = 'jgit',
+  repository = REPO,
+  deps = [':ewah'],
+  exclude = [
+    'META-INF/eclipse.inf',
+    'about.html',
+    'plugin.properties',
+  ],
+)
+
+maven_jar(
+  name = 'jgit-servlet',
+  id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
+  sha1 = 'b20c36982aa90fc08180ef4a570bf28de5e1a0ab',
+  license = 'jgit',
+  repository = REPO,
+  deps = [':jgit'],
+  exclude = [
+    'about.html',
+    'plugin.properties',
+  ],
+)
+
+maven_jar(
+  name = 'junit',
+  id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
+  sha1 = 'ee899f2d96b51e400ae84cd9405330a7e8ce1cb9',
+  license = 'DO_NOT_DISTRIBUTE',
+  repository = REPO,
+  deps = [':jgit'],
+)
+
+maven_jar(
+  name = 'ewah',
+  id = 'com.googlecode.javaewah:JavaEWAH:0.5.6',
+  sha1 = '1207c0fc8552d4f5f574b50f29321d923521128e',
+  license = 'Apache2.0',
+)
+
+prebuilt_jar(
+  name = 'Edit',
+  binary_jar = genfile('edit-src.jar'),
+  deps = [':jgit_edit_src'],
+  visibility = ['PUBLIC'],
+)
+
+genrule(
+  name = 'jgit_edit_src',
+  cmd = 'unzip -qd $TMP $SRCS org/eclipse/jgit/diff/Edit.java;' +
+    'cd $TMP;' +
+    'zip -Dq $OUT org/eclipse/jgit/diff/Edit.java',
+  srcs = [genfile('jgit/org.eclipse.jgit-%s-src.jar' % VERS)],
+  out = 'edit-src.jar',
+  deps = [':jgit_src']
+)
diff --git a/lib/log/BUCK b/lib/log/BUCK
new file mode 100644
index 0000000..2659fcd
--- /dev/null
+++ b/lib/log/BUCK
@@ -0,0 +1,31 @@
+include_defs('//lib/maven.defs')
+
+maven_jar(
+  name = 'api',
+  id = 'org.slf4j:slf4j-api:1.6.1',
+  sha1 = '6f3b8a24bf970f17289b234284c94f43eb42f0e4',
+  license = 'slf4j',
+)
+
+maven_jar(
+  name = 'impl_log4j',
+  id = 'org.slf4j:slf4j-log4j12:1.6.1',
+  sha1 = 'bd245d6746cdd4e6203e976e21d597a46f115802',
+  license = 'slf4j',
+  deps = [':log4j'],
+)
+
+maven_jar(
+  name = 'log4j',
+  id = 'log4j:log4j:1.2.16',
+  sha1 = '7999a63bfccbc7c247a9aea10d83d4272bd492c6',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE', 'META-INF/NOTICE'],
+)
+
+maven_jar(
+  name = 'jcl-over-slf4j',
+  id = 'org.slf4j:jcl-over-slf4j:1.6.1',
+  sha1 = '99c61095a14dfc9e47a086068033c286bf236475',
+  license = 'slf4j',
+)
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK
new file mode 100644
index 0000000..e0972d9
--- /dev/null
+++ b/lib/lucene/BUCK
@@ -0,0 +1,49 @@
+include_defs('//lib/maven.defs')
+
+maven_jar(
+  name = 'core',
+  id = 'org.apache.lucene:lucene-core:4.3.0',
+  bin_sha1 = 'd4e40fe5661b8de5d8c66db3d63a47b6b3ecf7f3',
+  src_sha1 = '86c29288b1930e33ba7ffea1b866af9a52d3d24a',
+  license = 'Apache2.0',
+  exclude = [
+    'META-INF/LICENSE.txt',
+    'META-INF/NOTICE.txt',
+  ],
+)
+
+maven_jar(
+  name = 'analyzers-common',
+  id = 'org.apache.lucene:lucene-analyzers-common:4.3.0',
+  bin_sha1 = 'e7c3976156d292f696016e138b67ab5e6bfc1a56',
+  src_sha1 = '3606622b3c1f09b4b7cf34070cbf60d414af9b6b',
+  license = 'Apache2.0',
+  exclude = [
+    'META-INF/LICENSE.txt',
+    'META-INF/NOTICE.txt',
+  ],
+)
+
+maven_jar(
+  name = 'highlighter',
+  id = 'org.apache.lucene:lucene-highlighter:4.3.0',
+  bin_sha1 = '9e6d60921e16a0d6b2e609c6a02a8b08cd7f643c',
+  src_sha1 = '0ff70cae1a8fb7af29bf254d90e9885961deed5e',
+  license = 'Apache2.0',
+)
+
+maven_jar(
+  name = 'queries',
+  id = 'org.apache.lucene:lucene-queries:4.3.0',
+  bin_sha1 = '68e01022bdf4f869b95362c9af964846e5d3cf2d',
+  src_sha1 = '3e3541c1b9f44c532ce88ab6a12216566c3399df',
+  license = 'Apache2.0',
+)
+
+maven_jar(
+  name = 'spellchecker',
+  id = 'org.apache.lucene:lucene-spellchecker:3.6.2',
+  bin_sha1 = '15db0c0cfee44e275f15ad046e46b9a05910ad24',
+  src_sha1 = 'bbecb3fb725ae594101c165a72c102296007c203',
+  license = 'Apache2.0',
+)
diff --git a/lib/maven.defs b/lib/maven.defs
new file mode 100644
index 0000000..551c951
--- /dev/null
+++ b/lib/maven.defs
@@ -0,0 +1,131 @@
+# 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.
+
+GERRIT = 'GERRIT:'
+MAVEN_CENTRAL = 'MAVEN_CENTRAL:'
+
+def define_license(name):
+  n = 'LICENSE-' + name
+  genrule(
+    name = n,
+    cmd = 'ln -s $SRCS $OUT',
+    srcs = [n],
+    out = n,
+    visibility = ['PUBLIC'],
+  )
+
+def maven_jar(
+    name,
+    id,
+    license,
+    exclude = [],
+    exclude_java_sources = False,
+    deps = [],
+    sha1 = '', bin_sha1 = '', src_sha1 = '',
+    repository = MAVEN_CENTRAL,
+    attach_source = True,
+    export_deps = False,
+    visibility = ['PUBLIC']):
+  from os import path
+
+  parts = id.split(':')
+  if len(parts) != 3:
+    raise NameError('expected id="groupId:artifactId:version"')
+  group, artifact, version = parts
+
+  if 'SNAPSHOT' in version:
+    file_version = version.replace('-SNAPSHOT', '')
+    version = version.split('-')[0] + '-SNAPSHOT'
+  else:
+    file_version = version
+
+  jar = path.join(name, artifact.lower() + '-' + file_version)
+  url = '/'.join([
+    repository,
+    group.replace('.', '/'), artifact, version,
+    artifact + '-' + file_version])
+
+  binjar = jar + '.jar'
+  binurl = url + '.jar'
+
+  srcjar = jar + '-src.jar'
+  srcurl = url + '-sources.jar'
+
+  cmd = ['${//tools:download_file}', '-o', '$OUT', '-u', binurl]
+  if sha1:
+    cmd.extend(['-v', sha1])
+  elif bin_sha1:
+    cmd.extend(['-v', bin_sha1])
+  for x in exclude:
+    cmd.extend(['-x', x])
+  if exclude_java_sources:
+    cmd.append('--exclude_java_sources')
+
+  genrule(
+    name = name + '__download_bin',
+    cmd = ' '.join(cmd),
+    srcs = [],
+    deps = ['//tools:download_file'],
+    out = binjar,
+  )
+  license = ['//lib:LICENSE-' + license]
+
+  if src_sha1 or attach_source:
+    cmd = ['${//tools:download_file}', '-o', '$OUT', '-u', srcurl]
+    if src_sha1:
+      cmd.extend(['-v', src_sha1])
+    genrule(
+      name = name + '__download_src',
+      cmd = ' '.join(cmd),
+      srcs = [],
+      deps = ['//tools:download_file'],
+      out = srcjar,
+    )
+    if src_sha1:
+      prebuilt_jar(
+        name = name + '_src',
+        binary_jar = genfile(srcjar),
+        deps = license + [':' + name + '__download_src'],
+        visibility = visibility,
+      )
+  else:
+    srcjar = None
+    genrule(
+      name = name + '__download_src',
+      cmd = ':>$OUT',
+      srcs = [],
+      out = '__' + name + '__no_src',
+    )
+
+  if export_deps:
+    prebuilt_jar(
+      name = name + '__jar',
+      deps = deps + license + [':' + name + '__download_bin'],
+      binary_jar = genfile(binjar),
+      source_jar = genfile(srcjar) if srcjar else None,
+    )
+    java_library(
+      name = name,
+      deps = [':' + name + '__jar'],
+      export_deps = True,
+      visibility = visibility,
+    )
+  else:
+    prebuilt_jar(
+      name = name,
+      deps = deps + license + [':' + name + '__download_bin'],
+      binary_jar = genfile(binjar),
+      source_jar = genfile(srcjar) if srcjar else None,
+      visibility = visibility,
+    )
diff --git a/lib/mina/BUCK b/lib/mina/BUCK
new file mode 100644
index 0000000..3e9558a
--- /dev/null
+++ b/lib/mina/BUCK
@@ -0,0 +1,24 @@
+include_defs('//lib/maven.defs')
+
+EXCLUDE = [
+  'META-INF/DEPENDENCIES',
+  'META-INF/LICENSE',
+  'META-INF/NOTICE',
+]
+
+maven_jar(
+  name = 'core',
+  id = 'org.apache.mina:mina-core:2.0.5',
+  sha1 = '0e134a3761833a3c28c79331e806f64f985a9eec',
+  license = 'Apache2.0',
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'sshd',
+  id = 'org.apache.sshd:sshd-core:0.6.0',
+  sha1 = '2b9a119dd77a1decec78b0c511ba400c8655e96e',
+  license = 'Apache2.0',
+  deps = [':core'],
+  exclude = EXCLUDE,
+)
diff --git a/lib/openid/BUCK b/lib/openid/BUCK
new file mode 100644
index 0000000..c6c8baf
--- /dev/null
+++ b/lib/openid/BUCK
@@ -0,0 +1,35 @@
+include_defs('//lib/maven.defs')
+
+maven_jar(
+  name = 'consumer',
+  id = 'org.openid4java:openid4java:0.9.8',
+  sha1 = 'de4f1b33d3b0f0b2ab1d32834ec1190b39db4160',
+  license = 'Apache2.0',
+  deps = [
+    ':nekohtml',
+    ':xerces',
+    '//lib/commons:httpclient',
+    '//lib/log:jcl-over-slf4j',
+    '//lib/guice:guice',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+maven_jar(
+  name = 'nekohtml',
+  id = 'net.sourceforge.nekohtml:nekohtml:1.9.10',
+  sha1 = '14052461031a7054aa094f5573792feb6686d3de',
+  license = 'Apache2.0',
+  deps = [':xerces'],
+  attach_source = False,
+  visibility = [],
+)
+
+maven_jar(
+  name = 'xerces',
+  id = 'xerces:xercesImpl:2.8.1',
+  sha1 = '25101e37ec0c907db6f0612cbf106ee519c1aef1',
+  license = 'Apache2.0',
+  attach_source = False,
+  visibility = [],
+)
diff --git a/lib/prolog/BUCK b/lib/prolog/BUCK
new file mode 100644
index 0000000..1f3e425
--- /dev/null
+++ b/lib/prolog/BUCK
@@ -0,0 +1,23 @@
+include_defs('//lib/maven.defs')
+
+maven_jar(
+  name = 'prolog-cafe',
+  id = 'com.googlecode.prolog-cafe:PrologCafe:1.3',
+  sha1 = '5e0fbf18e8c98c4113f9acc978306884a1152870',
+  license = 'prologcafe',
+  repository = GERRIT,
+)
+
+java_binary(
+  name = 'compiler',
+  main_class = 'BuckPrologCompiler',
+  deps = [':compiler_lib'],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'compiler_lib',
+  srcs = ['java/BuckPrologCompiler.java'],
+  deps = [':prolog-cafe'],
+  visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/lib/prolog/DEFS b/lib/prolog/DEFS
new file mode 100644
index 0000000..38d7fe5
--- /dev/null
+++ b/lib/prolog/DEFS
@@ -0,0 +1,38 @@
+# 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.
+
+def prolog_cafe_library(
+    name,
+    srcs,
+    deps = [],
+    visibility = []):
+  genrule(
+    name = name + '_prolog2java',
+    cmd = '${//lib/prolog:compiler} $SRCS $DEPS $OUT',
+    srcs = srcs,
+    deps = [
+      '//lib/prolog:compiler',
+      '//lib/prolog:prolog-cafe',
+    ] + deps,
+    out = name + '.jar',
+  )
+  prebuilt_jar(
+    name = name,
+    binary_jar = genfile(name + '.jar'),
+    deps = [
+      ':' + name + '_prolog2java',
+      '//lib/prolog:prolog-cafe',
+    ] + deps,
+    visibility = visibility,
+  )
diff --git a/lib/prolog/java/BuckPrologCompiler.java b/lib/prolog/java/BuckPrologCompiler.java
new file mode 100644
index 0000000..b731ea7
--- /dev/null
+++ b/lib/prolog/java/BuckPrologCompiler.java
@@ -0,0 +1,169 @@
+// 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.
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.compiler.Compiler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+public class BuckPrologCompiler {
+  public static void main(String[] argv) throws IOException, CompileException {
+    List<File> srcs = new ArrayList<File>();
+    List<File> jars = new ArrayList<File>();
+    for (int i = 0; i < argv.length - 1; i++) {
+      String s = argv[i];
+      if (s.endsWith(".pl")) {
+        srcs.add(new File(s));
+      } else if (s.endsWith(".jar")) {
+        jars.add(new File(s));
+      }
+    }
+
+    File out = new File(argv[argv.length - 1]);
+    File java = tmpdir("java");
+    File classes = tmpdir("classes");
+    for (File src : srcs) {
+      new Compiler().prologToJavaSource(src.getPath(), java.getPath());
+    }
+    javac(jars, java, classes);
+    jar(out, classes);
+  }
+
+  private static File tmpdir(String name) throws IOException {
+    File d = File.createTempFile(name + "_", "");
+    if (!d.delete() || !d.mkdir()) {
+      throw new IOException("Cannot mkdir " + d);
+    }
+    return d;
+  }
+
+  private static void javac(List<File> cp, File java, File classes)
+      throws IOException, CompileException {
+    JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
+    if (javac == null) {
+      throw new CompileException("JDK required (running inside of JRE)");
+    }
+
+    DiagnosticCollector<JavaFileObject> d =
+        new DiagnosticCollector<JavaFileObject>();
+    StandardJavaFileManager fm = javac.getStandardFileManager(d, null, null);
+    try {
+      StringBuilder classpath = new StringBuilder();
+      for (File jar : cp) {
+        if (classpath.length() > 0) {
+          classpath.append(File.pathSeparatorChar);
+        }
+        classpath.append(jar.getPath());
+      }
+      ArrayList<String> args = new ArrayList<String>();
+      args.addAll(Arrays.asList(new String[]{
+          "-source", "6",
+          "-target", "6",
+          "-g:none",
+          "-nowarn",
+          "-d", classes.getPath()}));
+      if (classpath.length() > 0) {
+        args.add("-classpath");
+        args.add(classpath.toString());
+      }
+      if (!javac.getTask(null, fm, d, args, null,
+          fm.getJavaFileObjectsFromFiles(find(java, ".java"))).call()) {
+        StringBuilder msg = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> err : d.getDiagnostics()) {
+          msg.append('\n').append(err.getKind()).append(": ");
+          if (err.getSource() != null) {
+            msg.append(err.getSource().getName());
+          }
+          msg.append(':').append(err.getLineNumber()).append(": ");
+          msg.append(err.getMessage(Locale.getDefault()));
+        }
+        throw new CompileException(msg.toString());
+      }
+    } finally {
+      fm.close();
+    }
+  }
+
+  private static void jar(File jar, File classes) throws IOException {
+    File tmp = File.createTempFile("prolog", ".jar", jar.getParentFile());
+    try {
+      JarOutputStream out = new JarOutputStream(new FileOutputStream(tmp));
+      try {
+        out.setLevel(9);
+        add(out, classes, "");
+      } finally {
+        out.close();
+      }
+      if (!tmp.renameTo(jar)) {
+        throw new IOException("Cannot create " + jar);
+      }
+    } finally {
+      tmp.delete();
+    }
+  }
+
+  private static void add(JarOutputStream out, File classes, String prefix)
+      throws IOException {
+    for (String name : classes.list()) {
+      File f = new File(classes, name);
+      if (f.isDirectory()) {
+        add(out, f, prefix + name + "/");
+        continue;
+      }
+
+      JarEntry e = new JarEntry(prefix + name);
+      FileInputStream in = new FileInputStream(f);
+      try {
+        e.setTime(f.lastModified());
+        out.putNextEntry(e);
+        byte[] buf = new byte[16 << 10];
+        int n;
+        while (0 < (n = in.read(buf))) {
+          out.write(buf, 0, n);
+        }
+      } finally {
+        in.close();
+        out.closeEntry();
+      }
+    }
+  }
+
+  private static List<File> find(File dir, String extension) {
+    ArrayList<File> list = new ArrayList<File>();
+    for (File f : dir.listFiles()) {
+      if (f.getName().endsWith(extension)) {
+        list.add(f);
+      } else if (f.isDirectory()) {
+        list.addAll(find(f, extension));
+      }
+    }
+    return list;
+  }
+}
diff --git a/lib/solr/BUCK b/lib/solr/BUCK
new file mode 100644
index 0000000..afaa948
--- /dev/null
+++ b/lib/solr/BUCK
@@ -0,0 +1,33 @@
+include_defs('//lib/maven.defs')
+
+# Java client library to use Solr over the network.
+maven_jar(
+  name = 'solrj',
+  id = 'org.apache.solr:solr-solrj:4.3.1',
+  sha1 = '433fe37796e67eaeb4452f69eb1fae2de27cb7a8',
+  license = 'Apache2.0',
+  deps = [
+    ':noggit',
+    ':zookeeper',
+    '//lib/commons:httpclient',
+    '//lib/commons:httpmime',
+    '//lib/commons:io',
+  ],
+)
+
+maven_jar(
+  name = 'noggit',
+  id = 'org.noggit:noggit:0.5',
+  sha1 = '8e6e65624d2e09a30190c6434abe23b7d4e5413c',
+  license = 'Apache2.0',
+  visibility = [],
+)
+
+maven_jar(
+  name = 'zookeeper',
+  id = 'org.apache.zookeeper:zookeeper:3.4.5',
+  sha1 = 'c0f69fb36526552a8f0bc548a6c33c49cf08e562',
+  license = 'Apache2.0',
+  deps = ['//lib/log:api'],
+  visibility = [],
+)
diff --git a/plugins/BUCK b/plugins/BUCK
new file mode 100644
index 0000000..ded6ba2
--- /dev/null
+++ b/plugins/BUCK
@@ -0,0 +1,36 @@
+BASE = get_base_path()
+CORE = [
+  'commit-message-length-validator',
+  'replication',
+  'reviewnotes',
+]
+
+# buck audit parses and resolves all deps even if not reachable
+# from the root(s) passed to audit. Filter dependencies to only
+# the ones that currently exist to allow buck to parse cleanly.
+# TODO(sop): buck should more lazily resolve deps
+def filter(names):
+  from os import path
+  h, n = [], []
+  for p in names:
+    if path.exists(path.join(BASE, p, 'BUCK')):
+      h.append(p)
+    else:
+      n.append(p)
+  return h, n
+HAVE, NEED = filter(CORE)
+
+genrule(
+  name = 'core',
+  cmd = '' +
+    ';'.join(['echo >&2 plugins/'+n+' is required.' for n in NEED]) +
+    (';echo >&2;exit 1;' if NEED else '') +
+    'mkdir -p $TMP/WEB-INF/plugins;' +
+    'for s in $SRCS;do ln -s $s $TMP/WEB-INF/plugins;done;' +
+    'cd $TMP;' +
+    'zip -qr $OUT .',
+  srcs = [genfile('%s/%s.jar' % (n, n)) for n in CORE],
+  deps = ['//%s/%s:%s' % (BASE, n, n) for n in HAVE],
+  out = 'core.zip',
+  visibility = ['//:release'],
+)
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index 74df0dc..9f988c6 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit 74df0dc1e7704224645718c130548fecc7c55494
+Subproject commit 9f988c6e5b1e07f66479f0d4d08537281fa4694f
diff --git a/plugins/helloworld b/plugins/helloworld
new file mode 160000
index 0000000..6e3c3c8
--- /dev/null
+++ b/plugins/helloworld
@@ -0,0 +1 @@
+Subproject commit 6e3c3c8a54e9e0e2c5b2fb9205bbbe3112ea55bb
diff --git a/plugins/replication b/plugins/replication
index 26b0185..353417f 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 26b0185a6e70c5ccf8b3444fea0014168067f0e1
+Subproject commit 353417f94b0dbbb82e343bc114113ea8f4a7cf9d
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 1490f20..36d87e5 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 1490f20e92303da4998335a0198c75c2e5d38724
+Subproject commit 36d87e549a4294c1697d68a0160383153b8f7891
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index f7798bd7..0000000
--- a/pom.xml
+++ /dev/null
@@ -1,902 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2008 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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <groupId>com.google.gerrit</groupId>
-  <artifactId>gerrit-parent</artifactId>
-  <packaging>pom</packaging>
-  <version>2.7-SNAPSHOT</version>
-
-  <name>Gerrit Code Review - Parent</name>
-  <url>http://code.google.com/p/gerrit/</url>
-
-  <description>
-    Gerrit - Web Based Code Review
-  </description>
-
-  <mailingLists>
-    <mailingList>
-      <name>repo-discuss mailing list</name>
-      <post>repo-discuss@googlegroups.com</post>
-      <archive>http://groups.google.com/group/repo-discuss</archive>
-      <subscribe>http://groups.google.com/group/repo-discuss/subscribe</subscribe>
-    </mailingList>
-  </mailingLists>
-
-  <issueManagement>
-    <url>http://code.google.com/p/gerrit/issues/list</url>
-    <system>Google Code</system>
-  </issueManagement>
-
-  <properties>
-    <jgitVersion>2.3.1.201302201838-r.209-g18030f9</jgitVersion>
-    <gwtormVersion>1.6</gwtormVersion>
-    <gwtjsonrpcVersion>1.3</gwtjsonrpcVersion>
-    <gwtVersion>2.5.0</gwtVersion>
-    <bouncyCastleVersion>140</bouncyCastleVersion>
-    <slf4jVersion>1.6.1</slf4jVersion>
-    <guiceVersion>3.0</guiceVersion>
-    <jettyVersion>8.1.7.v20120910</jettyVersion>
-
-    <gwt.compileReport>false</gwt.compileReport>
-
-    <project.build.sourceEncoding>
-      UTF-8
-    </project.build.sourceEncoding>
-    <project.reporting.outputEncoding>
-      UTF-8
-    </project.reporting.outputEncoding>
-  </properties>
-
-  <modules>
-    <module>gerrit-patch-commonsnet</module>
-    <module>gerrit-patch-jgit</module>
-
-    <module>gerrit-util-cli</module>
-    <module>gerrit-util-ssl</module>
-
-    <module>gerrit-antlr</module>
-    <module>gerrit-common</module>
-    <module>gerrit-cache-h2</module>
-    <module>gerrit-httpd</module>
-    <module>gerrit-gwtexpui</module>
-    <module>gerrit-gwtui</module>
-    <module>gerrit-launcher</module>
-    <module>gerrit-main</module>
-    <module>gerrit-openid</module>
-    <module>gerrit-pgm</module>
-    <module>gerrit-prettify</module>
-    <module>gerrit-reviewdb</module>
-    <module>gerrit-server</module>
-    <module>gerrit-sshd</module>
-    <module>gerrit-gwtdebug</module>
-    <module>gerrit-war</module>
-
-    <module>gerrit-acceptance-tests</module>
-    <module>gerrit-extension-api</module>
-    <module>gerrit-plugin-api</module>
-    <module>gerrit-plugin-archetype</module>
-    <module>gerrit-plugin-gwtui</module>
-    <module>gerrit-plugin-js-archetype</module>
-    <module>gerrit-plugin-gwt-archetype</module>
-  </modules>
-
-  <profiles>
-    <profile>
-      <id>plugins</id>
-      <activation>
-        <property>
-          <name>!gerrit.plugins.skip</name>
-        </property>
-      </activation>
-      <modules>
-        <!-- CORE PLUGIN LIST -->
-        <module>plugins/commit-message-length-validator</module>
-        <module>plugins/replication</module>
-        <module>plugins/reviewnotes</module>
-      </modules>
-    </profile>
-  </profiles>
-
-  <licenses>
-    <license>
-      <name>Apache License, 2.0</name>
-      <comments>
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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.
-      </comments>
-    </license>
-  </licenses>
-
-  <build>
-    <pluginManagement>
-      <plugins>
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-jar-plugin</artifactId>
-          <configuration>
-            <archive>
-              <manifestEntries>
-                <Implementation-Title>Gerrit Code Review - ${project.artifactId}</Implementation-Title>
-                <Implementation-Version>${project.version}</Implementation-Version>
-                <Implementation-Vendor>Gerrit Code Review</Implementation-Vendor>
-                <Implementation-Vendor-Id>com.google.gerrit</Implementation-Vendor-Id>
-                <Implementation-Vendor-URL>http://code.google.com/p/gerrit/</Implementation-Vendor-URL>
-              </manifestEntries>
-            </archive>
-          </configuration>
-        </plugin>
-
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-compiler-plugin</artifactId>
-          <version>2.3.2</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-source-plugin</artifactId>
-          <version>2.1.2</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-shade-plugin</artifactId>
-          <version>1.6</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-antrun-plugin</artifactId>
-          <version>1.6</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-war-plugin</artifactId>
-          <version>2.1.1</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-dependency-plugin</artifactId>
-          <version>2.5.1</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.13</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.antlr</groupId>
-          <artifactId>antlr3-maven-plugin</artifactId>
-          <version>3.2</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.codehaus.mojo</groupId>
-          <artifactId>gwt-maven-plugin</artifactId>
-          <version>2.5.0</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.codehaus.mojo</groupId>
-          <artifactId>build-helper-maven-plugin</artifactId>
-          <version>1.5</version>
-        </plugin>
-
-        <!--This plugin's configuration is used to store Eclipse 
-            m2e settings only. It has no influence on the Maven build itself. -->
-        <plugin>
-          <groupId>org.eclipse.m2e</groupId>
-          <artifactId>lifecycle-mapping</artifactId>
-          <version>1.0.0</version>
-          <configuration>
-            <lifecycleMappingMetadata>
-              <pluginExecutions>
-                <pluginExecution>
-                  <pluginExecutionFilter>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-antrun-plugin</artifactId>
-                    <versionRange>[1.0,)</versionRange>
-                    <goals>
-                      <goal>run</goal>
-                    </goals>
-                  </pluginExecutionFilter>
-                  <action>
-                     <execute>
-                      <runOnIncremental>false</runOnIncremental>
-                      <runOnConfiguration>true</runOnConfiguration>
-                    </execute>
-                  </action>
-                </pluginExecution>
-                <pluginExecution>
-                  <pluginExecutionFilter>
-                    <groupId>org.codehaus.mojo</groupId>
-                    <artifactId>build-helper-maven-plugin</artifactId>
-                    <versionRange>[1.0,)</versionRange>
-                    <goals>
-                      <goal>add-source</goal>
-                    </goals>
-                  </pluginExecutionFilter>
-                  <action>
-                    <execute>
-                      <runOnIncremental>false</runOnIncremental>
-                      <runOnConfiguration>true</runOnConfiguration>
-                   </execute>
-                  </action>
-                </pluginExecution>
-              </pluginExecutions>
-            </lifecycleMappingMetadata>
-          </configuration>
-        </plugin>
-      </plugins>
-    </pluginManagement>
-
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.6</source>
-          <target>1.6</target>
-          <encoding>UTF-8</encoding>
-        </configuration>
-      </plugin>
-    </plugins>
-
-    <extensions>
-      <extension>
-        <groupId>com.googlesource.gerrit</groupId>
-        <artifactId>gs-maven-wagon</artifactId>
-        <version>3.3</version>
-      </extension>
-    </extensions>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>org.easymock</groupId>
-      <artifactId>easymock</artifactId>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-
-  <dependencyManagement>
-    <dependencies>
-      <dependency>
-        <groupId>com.google.code.gson</groupId>
-        <artifactId>gson</artifactId>
-        <version>2.1</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.google.guava</groupId>
-        <artifactId>guava</artifactId>
-        <version>14.0</version>
-      </dependency>
-
-      <dependency>
-        <groupId>gwtorm</groupId>
-        <artifactId>gwtorm</artifactId>
-        <version>${gwtormVersion}</version>
-      </dependency>
-      <dependency>
-        <groupId>gwtorm</groupId>
-        <artifactId>gwtorm</artifactId>
-        <version>${gwtormVersion}</version>
-        <classifier>sources</classifier>
-      </dependency>
-
-      <dependency>
-        <groupId>gwtjsonrpc</groupId>
-        <artifactId>gwtjsonrpc</artifactId>
-        <version>${gwtjsonrpcVersion}</version>
-      </dependency>
-      <dependency>
-        <groupId>gwtjsonrpc</groupId>
-        <artifactId>gwtjsonrpc</artifactId>
-        <version>${gwtjsonrpcVersion}</version>
-        <classifier>sources</classifier>
-      </dependency>
-
-      <dependency>
-        <groupId>org.openid4java</groupId>
-        <artifactId>openid4java</artifactId>
-        <version>0.9.8</version>
-        <exclusions>
-          <exclusion>
-            <!-- conflicts with our use of guice 3.0 -->
-            <groupId>com.google.code.guice</groupId>
-            <artifactId>guice</artifactId>
-          </exclusion>
-          <exclusion>
-            <!-- jug-1.1 is LGPL, and the source has been lost -->
-            <groupId>jug</groupId>
-            <artifactId>jug</artifactId>
-          </exclusion>
-          <exclusion>
-            <!-- not required on Java 5 or later -->
-            <groupId>xml-apis</groupId>
-            <artifactId>xml-apis</artifactId>
-          </exclusion>
-
-          <!-- optional, we aren't bothering with XRI support -->
-          <exclusion>
-            <groupId>org.openxri</groupId>
-            <artifactId>openxri</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>xml-security</groupId>
-            <artifactId>xmlsec</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>xalan</groupId>
-            <artifactId>xalan</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-
-      <dependency>
-        <groupId>org.apache.mina</groupId>
-        <artifactId>mina-core</artifactId>
-        <version>2.0.5</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.apache.sshd</groupId>
-        <artifactId>sshd-core</artifactId>
-        <version>0.6.0</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.jcraft</groupId>
-        <artifactId>jsch</artifactId>
-        <version>0.1.44-1</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.apache.velocity</groupId>
-        <artifactId>velocity</artifactId>
-        <version>1.6.4</version>
-      </dependency>
-
-      <dependency>
-        <groupId>args4j</groupId>
-        <artifactId>args4j</artifactId>
-        <version>2.0.16</version>
-      </dependency>
-
-      <dependency>
-        <groupId>javax.validation</groupId>
-        <artifactId>validation-api</artifactId>
-        <version>1.0.0.GA</version>
-        <scope>provided</scope>
-      </dependency>
-      <dependency>
-        <groupId>javax.validation</groupId>
-        <artifactId>validation-api</artifactId>
-        <version>1.0.0.GA</version>
-        <classifier>sources</classifier>
-        <scope>provided</scope>
-      </dependency>
-
-      <dependency>
-        <groupId>com.google.inject</groupId>
-        <artifactId>guice</artifactId>
-        <version>${guiceVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.google.inject.extensions</groupId>
-        <artifactId>guice-servlet</artifactId>
-        <version>${guiceVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.google.inject.extensions</groupId>
-        <artifactId>guice-assistedinject</artifactId>
-        <version>${guiceVersion}</version>
-      </dependency>
-
-      <dependency>
-        <!-- required by Guice (whose POM is fake and lacks it) -->
-        <groupId>aopalliance</groupId>
-        <artifactId>aopalliance</artifactId>
-        <version>1.0</version>
-      </dependency>
-
-      <dependency>
-        <groupId>commons-net</groupId>
-        <artifactId>commons-net</artifactId>
-        <version>2.2</version>
-      </dependency>
-
-      <dependency>
-        <groupId>commons-codec</groupId>
-        <artifactId>commons-codec</artifactId>
-        <version>1.4</version>
-      </dependency>
-
-      <dependency>
-        <groupId>commons-dbcp</groupId>
-        <artifactId>commons-dbcp</artifactId>
-        <version>1.4</version>
-      </dependency>
-
-      <dependency>
-        <groupId>commons-pool</groupId>
-        <artifactId>commons-pool</artifactId>
-        <version>1.5.5</version>
-      </dependency>
-
-      <dependency>
-        <groupId>commons-lang</groupId>
-        <artifactId>commons-lang</artifactId>
-        <version>2.5</version>
-      </dependency>
-
-      <dependency>
-        <groupId>eu.medsea.mimeutil</groupId>
-        <artifactId>mime-util</artifactId>
-        <version>2.1.3</version>
-        <exclusions>
-          <exclusion>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-log4j12</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>log4j</groupId>
-            <artifactId>log4j</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-
-      <dependency>
-        <groupId>org.antlr</groupId>
-        <artifactId>antlr</artifactId>
-        <version>3.2</version>
-        <exclusions>
-          <exclusion>
-            <groupId>org.antlr</groupId>
-            <artifactId>stringtemplate</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>antlr</groupId>
-            <artifactId>antlr</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-
-      <dependency>
-        <groupId>bouncycastle</groupId>
-        <artifactId>bcpg-jdk15</artifactId>
-        <version>${bouncyCastleVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>bouncycastle</groupId>
-        <artifactId>bcprov-jdk15</artifactId>
-        <version>${bouncyCastleVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.slf4j</groupId>
-        <artifactId>slf4j-api</artifactId>
-        <version>${slf4jVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.slf4j</groupId>
-        <artifactId>slf4j-log4j12</artifactId>
-        <version>${slf4jVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>log4j</groupId>
-        <artifactId>log4j</artifactId>
-        <version>1.2.16</version>
-        <exclusions>
-          <exclusion>
-            <groupId>javax.mail</groupId>
-            <artifactId>mail</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>javax.jms</groupId>
-            <artifactId>jms</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>com.sun.jdmk</groupId>
-            <artifactId>jmxtools</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>com.sun.jmx</groupId>
-            <artifactId>jmxri</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-
-      <dependency>
-        <groupId>org.eclipse.jgit</groupId>
-        <artifactId>org.eclipse.jgit</artifactId>
-        <version>${jgitVersion}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.eclipse.jgit</groupId>
-        <artifactId>org.eclipse.jgit</artifactId>
-        <version>${jgitVersion}</version>
-        <classifier>sources</classifier>
-      </dependency>
-
-      <dependency>
-        <groupId>org.eclipse.jgit</groupId>
-        <artifactId>org.eclipse.jgit.junit</artifactId>
-        <version>${jgitVersion}</version>
-        <scope>test</scope>
-        <exclusions>
-          <exclusion>
-            <groupId>org.eclipse.jgit</groupId>
-            <artifactId>org.eclipse.jgit</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-
-      <dependency>
-        <groupId>org.eclipse.jgit</groupId>
-        <artifactId>org.eclipse.jgit.http.server</artifactId>
-        <version>${jgitVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>junit</groupId>
-        <artifactId>junit</artifactId>
-        <version>4.11</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.h2database</groupId>
-        <artifactId>h2</artifactId>
-        <version>1.3.168</version>
-      </dependency>
-
-      <dependency>
-        <groupId>postgresql</groupId>
-        <artifactId>postgresql</artifactId>
-        <version>9.1-901-1.jdbc4</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.eclipse.jetty</groupId>
-        <artifactId>jetty-servlet</artifactId>
-        <version>${jettyVersion}</version>
-        <exclusions>
-          <exclusion>
-            <!-- use Apache javax.servlet not CDDL -->
-            <groupId>org.eclipse.jetty.orbit</groupId>
-            <artifactId>javax.servlet</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-
-      <dependency>
-        <groupId>org.easymock</groupId>
-        <artifactId>easymock</artifactId>
-        <version>3.0</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.apache.tomcat</groupId>
-        <artifactId>servlet-api</artifactId>
-        <version>6.0.29</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.apache.tomcat</groupId>
-        <artifactId>tomcat-servlet-api</artifactId>
-        <version>7.0.32</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.google.gwt</groupId>
-        <artifactId>gwt-servlet</artifactId>
-        <version>${gwtVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.google.gwt</groupId>
-        <artifactId>gwt-user</artifactId>
-        <version>${gwtVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.google.gwt</groupId>
-        <artifactId>gwt-dev</artifactId>
-        <version>${gwtVersion}</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.google.code.findbugs</groupId>
-        <artifactId>jsr305</artifactId>
-        <version>1.3.9</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.googlecode.juniversalchardet</groupId>
-        <artifactId>juniversalchardet</artifactId>
-        <version>1.0.3</version>
-      </dependency>
-
-      <dependency>
-        <groupId>dk.brics.automaton</groupId>
-        <artifactId>automaton</artifactId>
-        <version>1.11.8</version>
-      </dependency>
-
-      <dependency>
-        <groupId>com.googlecode.prolog-cafe</groupId>
-        <artifactId>PrologCafe</artifactId>
-        <version>1.3</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.pegdown</groupId>
-        <artifactId>pegdown</artifactId>
-        <version>1.1.0</version>
-      </dependency>
-
-      <dependency>
-        <groupId>org.parboiled</groupId>
-        <artifactId>parboiled-core</artifactId>
-        <version>1.1.3</version>
-      </dependency>
-      <dependency>
-        <groupId>org.parboiled</groupId>
-        <artifactId>parboiled-java</artifactId>
-        <version>1.1.3</version>
-      </dependency>
-    </dependencies>
-  </dependencyManagement>
-
-  <pluginRepositories>
-    <pluginRepository>
-      <id>gerrit-maven</id>
-      <url>https://gerrit-maven.commondatastorage.googleapis.com</url>
-    </pluginRepository>
-  </pluginRepositories>
-
-  <repositories>
-    <repository>
-      <id>gerrit-maven</id>
-      <url>https://gerrit-maven.commondatastorage.googleapis.com</url>
-    </repository>
-
-    <repository>
-      <id>jgit-repository</id>
-      <url>http://download.eclipse.org/jgit/maven</url>
-    </repository>
-  </repositories>
-</project>
diff --git a/tools/BUCK b/tools/BUCK
new file mode 100644
index 0000000..405d60d
--- /dev/null
+++ b/tools/BUCK
@@ -0,0 +1,48 @@
+python_binary(
+  name = 'download_all',
+  main = 'download_all.py',
+  visibility = ['PUBLIC'],
+)
+
+python_binary(
+  name = 'download_file',
+  main = 'download_file.py',
+  visibility = ['PUBLIC'],
+)
+
+python_binary(
+  name = 'pack_war',
+  main = 'pack_war.py',
+  deps = [':util'],
+  visibility = ['PUBLIC'],
+)
+
+python_binary(
+  name = 'maven_deploy',
+  main = 'maven_deploy.py',
+  deps = [':util'],
+  visibility = ['PUBLIC'],
+)
+
+python_library(
+  name = 'util',
+  srcs = ['util.py'],
+  visibility = ['PUBLIC'],
+)
+
+def shquote(s):
+  return s.replace("'", "'\\''")
+
+def os_path():
+  from os import environ
+  return environ.get('PATH')
+
+genrule(
+  name = 'buck.properties',
+  cmd = 'echo buck=`which buck`>$OUT;' +
+    ("echo PATH=\''%s'\' >>$OUT;" % shquote(os_path())),
+  srcs = [],
+  deps = [],
+  out = 'buck.properties',
+  visibility = ['PUBLIC'],
+)
diff --git a/tools/DEFS b/tools/DEFS
new file mode 100644
index 0000000..0cf8c50
--- /dev/null
+++ b/tools/DEFS
@@ -0,0 +1,186 @@
+# 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.
+
+def genantlr(
+    name,
+    srcs,
+    outs):
+  tmp = name + '.srcjar'
+  genrule(
+    name = name,
+    srcs = srcs,
+    cmd = '${//lib/antlr:antlr-tool} -o $TMP $SRCS;' +
+      'cd $TMP;' +
+      'zip -qr $OUT .',
+    deps = ['//lib/antlr:antlr-tool'],
+    out = tmp,
+  )
+  for o in outs:
+    genrule(
+      name = o,
+      cmd = 'unzip -qp $SRCS %s >$OUT' % o,
+      srcs = [genfile(tmp)],
+      deps = [':' + name],
+      out = o,
+    )
+
+
+def gwt_module(
+    name,
+    srcs,
+    gwtxml = None,
+    resources = [],
+    deps = [],
+    visibility = []):
+  if gwtxml:
+    resources = resources + [gwtxml]
+  resources = resources + srcs
+  java_library(
+    name = name,
+    srcs = srcs,
+    deps = deps,
+    resources = resources,
+    visibility = visibility,
+  )
+
+def gwt_application(
+    name,
+    module_target,
+    compiler_opts = [],
+    compiler_jvm_flags = [],
+    deps = [],
+    visibility = []):
+  cmd = ['${//lib/gwt:compiler}', module_target, '$OUT']
+  cmd += compiler_opts + ['--', '$DEPS']
+  genrule(
+    name = name,
+    srcs = [],
+    cmd = ' '.join(cmd),
+    deps = [
+      '//lib/gwt:compiler',
+      '//lib/gwt:dev',
+    ] + deps,
+    out = '%s.zip' % name,
+    visibility = visibility,
+  )
+
+# Compiles a Java library with additional compile-time dependencies
+# that do not show up as transitive dependencies to java_library()
+# or java_binary() rule that depends on this library.
+def java_library2(
+    name,
+    srcs = [],
+    resources = [],
+    deps = [],
+    compile_deps = [],
+    visibility = []):
+  c = name + '__compile'
+  t = name + '__link'
+  j = 'lib__%s__output/%s.jar' % (c, c)
+  o = 'lib__%s__output/%s.jar' % (name, name)
+  java_library(
+    name = c,
+    srcs = srcs,
+    resources = resources,
+    deps = deps + compile_deps,
+    visibility = ['//tools/eclipse:classpath'],
+  )
+  # Break the dependency chain by passing the newly built
+  # JAR to consumers through a prebuilt_jar().
+  genrule(
+    name = t,
+    cmd = 'mkdir -p $(dirname $OUT);ln -s $SRCS $OUT',
+    srcs = [genfile(j)],
+    deps = [':' + c],
+    out = o,
+  )
+  prebuilt_jar(
+    name = name,
+    binary_jar = genfile(o),
+    deps = deps + [':' + t],
+    visibility = visibility,
+  )
+
+def gerrit_extension(
+    name,
+    deps = [],
+    srcs = [],
+    resources = [],
+    manifest_file = None,
+    manifest_entries = [],
+    visibility = ['PUBLIC']):
+  gerrit_plugin(
+    name = name,
+    deps = deps,
+    srcs = srcs,
+    resources = resources,
+    manifest_file = manifest_file,
+    manifest_entries = manifest_entries,
+    type = 'extension',
+    visibility = visibility,
+  )
+
+def gerrit_plugin(
+    name,
+    deps = [],
+    srcs = [],
+    resources = [],
+    manifest_file = None,
+    manifest_entries = [],
+    type = 'plugin',
+    visibility = ['PUBLIC']):
+  mf_cmd = 'v=$(git describe HEAD);'
+  if manifest_file:
+    mf_src = [manifest_file]
+    mf_cmd += 'sed "s:@VERSION@:$v:g" $SRCS >$OUT'
+  else:
+    mf_src = []
+    mf_cmd += 'echo "Manifest-Version: 1.0" >$OUT;'
+    mf_cmd += 'echo "Gerrit-ApiType: %s" >>$OUT;' % type
+    mf_cmd += 'echo "Implementation-Version: $v" >>$OUT'
+    for line in manifest_entries:
+      mf_cmd += ';echo "%s" >> $OUT' % line
+  genrule(
+    name = name + '__manifest',
+    cmd = mf_cmd,
+    srcs = mf_src,
+    out = 'MANIFEST.MF',
+  )
+  java_library2(
+    name = name + '__plugin',
+    srcs = srcs,
+    resources = resources,
+    deps = deps,
+    compile_deps = ['//:%s-lib' % type],
+  )
+  java_binary(
+    name = name,
+    manifest_file = genfile('MANIFEST.MF'),
+    deps = [
+      ':%s__plugin' % name,
+      ':%s__manifest' % name,
+    ],
+    visibility = visibility,
+  )
+
+def java_sources(
+    name,
+    srcs,
+    visibility = []
+  ):
+  java_library(
+    name = name,
+    resources = srcs,
+    visibility = visibility,
+  )
diff --git a/tools/build.defs b/tools/build.defs
new file mode 100644
index 0000000..3ff82d1
--- /dev/null
+++ b/tools/build.defs
@@ -0,0 +1,110 @@
+# 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.
+include_defs('//VERSION')
+
+DOCS = ['//Documentation:html.zip']
+LIBS = [
+  '//gerrit-common:version',
+  '//gerrit-war:init',
+  '//gerrit-war:log4j-config',
+  '//lib:postgresql',
+  '//lib/log:impl_log4j',
+]
+PGMLIBS = ['//gerrit-pgm:pgm']
+
+def scan_plugins():
+  import os
+  deps = []
+  for n in os.listdir('plugins'):
+    if os.path.exists(os.path.join('plugins', n, 'BUCK')):
+      deps.append('//plugins/%s:%s__plugin__compile' % (n, n))
+  return deps
+
+def war(
+    name,
+    libs = [],
+    pgmlibs = [],
+    context = [],
+    visibility = []
+    ):
+  cmd = ['${//tools:pack_war}', '-o', '$OUT']
+  for l in libs:
+    cmd.extend(['--lib', l])
+  for l in pgmlibs:
+    cmd.extend(['--pgmlib', l])
+
+  src = []
+  dep = []
+  if context:
+    root = get_base_path()
+    if root:
+      root = '/'.join(['..' for _ in root.split('/')]) + '/'
+    for r in context:
+      dep.append(r[:r.rindex('.')])
+      if r.startswith('//'):
+        r = root + r[2:]
+      r = r.replace(':', '/')
+      src.append(genfile(r))
+    cmd.append('$SRCS')
+
+  genrule(
+    name = name,
+    cmd = ' '.join(cmd),
+    srcs = src,
+    deps = libs + pgmlibs + dep + ['//tools:pack_war'],
+    out = name + '.war',
+    visibility = visibility,
+  )
+
+def gerrit_war(name, ui = 'ui_optdbg', context = []):
+  war(
+    name = name,
+    libs = LIBS,
+    pgmlibs = PGMLIBS,
+    context = [
+      '//gerrit-main:main_bin.jar',
+      '//gerrit-war:webapp_assets.zip',
+      '//gerrit-gwtui:' + ui + '.zip',
+    ] + context,
+  )
+
+def maven_deploy(
+    deps
+    ):
+  _maven_util('deploy', deps)
+
+def maven_install(
+    deps
+    ):
+  _maven_util(
+    name = 'install',
+    deps = deps)
+
+def _maven_util(
+    name,
+    deps
+    ):
+  cmd = [
+    '${//tools:maven_deploy}',
+    '-a', name,
+    '-v', GERRIT_VER,
+    '-d', '"$DEPS"'
+  ]
+  genrule(
+    name = 'api_%s' % name,
+    cmd = ' '.join(cmd),
+    srcs = [],
+    deps = deps + ['//tools:maven_deploy'],
+    out = '__fake.api_%s__' % name
+  )
diff --git a/tools/download_all.py b/tools/download_all.py
new file mode 100755
index 0000000..241d20b
--- /dev/null
+++ b/tools/download_all.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+# 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.
+
+from optparse import OptionParser
+import re
+from subprocess import check_call, CalledProcessError, Popen, PIPE
+
+MAIN = ['//tools/eclipse:classpath']
+PAT = re.compile(r'"(//.*?)" -> "//tools:download_file"')
+
+opts = OptionParser()
+opts.add_option('--src', action='store_true')
+args, _ = opts.parse_args()
+
+targets = set()
+
+p = Popen(['buck', 'audit', 'classpath', '--dot'] + MAIN, stdout = PIPE)
+for line in p.stdout:
+  m = PAT.search(line)
+  if m:
+    n = m.group(1)
+    if args.src and n.endswith('__download_bin'):
+      n = n[:-4] + '_src'
+    targets.add(n)
+r = p.wait()
+if r != 0:
+  exit(r)
+
+try:
+  check_call(['buck', 'build'] + sorted(targets))
+except CalledProcessError as err:
+  exit(1)
diff --git a/tools/download_file.py b/tools/download_file.py
new file mode 100755
index 0000000..87d5c2d
--- /dev/null
+++ b/tools/download_file.py
@@ -0,0 +1,181 @@
+#!/usr/bin/python
+# 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.
+
+from __future__ import print_function
+
+from hashlib import sha1
+from optparse import OptionParser
+from os import link, makedirs, path, remove, symlink
+import shutil
+from subprocess import check_call, CalledProcessError
+from sys import stderr
+from zipfile import ZipFile, BadZipfile, LargeZipFile
+
+REPO_ROOTS = {
+  'GERRIT': 'http://gerrit-maven.storage.googleapis.com',
+  'MAVEN_CENTRAL': 'http://repo1.maven.org/maven2',
+}
+
+GERRIT_HOME = path.expanduser('~/.gerritcodereview')
+CACHE_DIR = path.join(GERRIT_HOME, 'buck-cache')
+LOCAL_PROPERTIES = 'local.properties'
+
+
+def hashfile(p):
+  d = sha1()
+  with open(p, 'rb') as f:
+    while True:
+      b = f.read(8192)
+      if not b:
+        break
+      d.update(b)
+  return d.hexdigest()
+
+def safe_mkdirs(d):
+  if path.isdir(d):
+    return
+  try:
+    makedirs(d)
+  except OSError as err:
+    if not path.isdir(d):
+      raise err
+
+def download_properties(root_dir):
+  """ Get the download properties.
+
+  First tries to find the properties file in the given root directory,
+  and if not found there, tries in the Gerrit settings folder in the
+  user's home directory.
+
+  Returns a set of download properties, which may be empty.
+
+  """
+  p = {}
+  local_prop = path.join(root_dir, LOCAL_PROPERTIES)
+  if not path.isfile(local_prop):
+    local_prop = path.join(GERRIT_HOME, LOCAL_PROPERTIES)
+  if path.isfile(local_prop):
+    try:
+      with open(local_prop) as fd:
+        for line in fd:
+          if line.startswith('download.'):
+            d = [e.strip() for e in line.split('=', 1)]
+            name, url = d[0], d[1]
+            p[name[len('download.'):]] = url
+    except OSError:
+      pass
+  return p
+
+def cache_entry(args):
+  if args.v:
+    h = args.v
+  else:
+    h = sha1(args.u).hexdigest()
+  name = '%s-%s' % (path.basename(args.o), h)
+  return path.join(CACHE_DIR, name)
+
+def resolve_url(url, redirects):
+  s = url.find(':')
+  if s < 0:
+    return url
+  scheme, rest = url[:s], url[s+1:]
+  if scheme not in REPO_ROOTS:
+    return url
+  if scheme in redirects:
+    root = redirects[scheme]
+  else:
+    root = REPO_ROOTS[scheme]
+  root = root.rstrip('/')
+  rest = rest.lstrip('/')
+  return '/'.join([root, rest])
+
+opts = OptionParser()
+opts.add_option('-o', help='local output file')
+opts.add_option('-u', help='URL to download')
+opts.add_option('-v', help='expected content SHA-1')
+opts.add_option('-x', action='append', help='file to delete from ZIP')
+opts.add_option('--exclude_java_sources', action='store_true')
+args, _ = opts.parse_args()
+
+root_dir = args.o
+while root_dir:
+  root_dir, n = path.split(root_dir)
+  if n == 'buck-out':
+    break
+
+redirects = download_properties(root_dir)
+cache_ent = cache_entry(args)
+src_url = resolve_url(args.u, redirects)
+
+if not path.exists(cache_ent):
+  try:
+    safe_mkdirs(path.dirname(cache_ent))
+  except OSError as err:
+    print('error creating directory %s: %s' %
+          (path.dirname(cache_ent), err), file=stderr)
+    exit(1)
+
+  print('Download %s' % src_url, file=stderr)
+  try:
+    check_call(['curl', '--proxy-anyauth', '-sfo', cache_ent, src_url])
+  except OSError as err:
+    print('could not invoke curl: %s\nis curl installed?' % err, file=stderr)
+    exit(1)
+  except CalledProcessError as err:
+    print('error using curl: %s' % err, file=stderr)
+    exit(1)
+
+if args.v:
+  have = hashfile(cache_ent)
+  if args.v != have:
+    print((
+      '%s:\n' +
+      'expected %s\n' +
+      'received %s\n') % (src_url, args.v, have), file=stderr)
+    try:
+      remove(cache_ent)
+    except OSError as err:
+      if path.exists(cache_ent):
+        print('error removing %s: %s' % (cache_ent, err), file=stderr)
+    exit(1)
+
+exclude = []
+if args.x:
+  exclude += args.x
+if args.exclude_java_sources:
+  try:
+    zf = ZipFile(cache_ent, 'r')
+    try:
+      for n in zf.namelist():
+        if n.endswith('.java'):
+          exclude.append(n)
+    finally:
+      zf.close()
+  except (BadZipfile, LargeZipFile) as err:
+    print("error opening %s: %s"  % (cache_ent, err), file=stderr)
+    exit(1)
+
+safe_mkdirs(path.dirname(args.o))
+if exclude:
+  shutil.copyfile(cache_ent, args.o)
+  try:
+    check_call(['zip', '-d', args.o] + exclude)
+  except CalledProcessError as err:
+    print('error removing files from zip: %s' % err, file=stderr)
+else:
+  try:
+    link(cache_ent, args.o)
+  except OSError as err:
+    symlink(cache_ent, args.o)
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
new file mode 100644
index 0000000..f2b51a9
--- /dev/null
+++ b/tools/eclipse/BUCK
@@ -0,0 +1,76 @@
+include_defs('//tools/build.defs')
+
+genrule(
+  name = 'eclipse',
+  cmd = '',
+  srcs = [],
+  deps = [
+    ':_classpath',
+    ':_project',
+    '//tools:buck.properties',
+  ],
+  out = '__fake.eclipse__',
+)
+
+genrule(
+  name = 'eclipse_project',
+  cmd = '',
+  srcs = [],
+  deps = [
+    ':_classpath_nocompile',
+    ':_project',
+    '//tools:buck.properties',
+  ],
+  out = '__fake.eclipse__',
+)
+
+java_library(
+  name = 'classpath',
+  deps = LIBS + PGMLIBS + [
+    '//gerrit-acceptance-tests:acceptance_tests',
+    '//gerrit-gwtdebug:gwtdebug',
+    '//gerrit-gwtui:ui_module',
+    '//gerrit-gwtui:ui_tests',
+    '//gerrit-httpd:httpd_tests',
+    '//gerrit-main:main_lib',
+    '//gerrit-server:server__compile',
+    '//lib/prolog:compiler_lib',
+  ] + scan_plugins(),
+)
+
+genrule(
+  name = '_project',
+  cmd = '${:gen_project} $OUT',
+  srcs = [],
+  deps = [':gen_project'],
+  out = 'project',
+)
+
+genrule(
+  name = '_classpath',
+  cmd = '${:gen_classpath} $OUT $DEPS',
+  srcs = [],
+  deps = [
+    ':classpath',
+    ':gen_classpath',
+  ],
+  out = 'classpath',
+)
+
+genrule(
+  name = '_classpath_nocompile',
+  cmd = '${:gen_classpath}',
+  srcs = [],
+  deps = [':gen_classpath'],
+  out = '__fake.eclipse__',
+)
+
+python_binary(
+  name = 'gen_classpath',
+  main = 'gen_classpath.py',
+)
+
+python_binary(
+  name = 'gen_project',
+  main = 'gen_project.py',
+)
diff --git a/tools/eclipse/buck_daemon_ui_chrome.launch b/tools/eclipse/buck_daemon_ui_chrome.launch
new file mode 100644
index 0000000..efe2623
--- /dev/null
+++ b/tools/eclipse/buck_daemon_ui_chrome.launch
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_chrome"/>
+</launchConfiguration>
diff --git a/tools/eclipse/buck_daemon_ui_dbg.launch b/tools/eclipse/buck_daemon_ui_dbg.launch
new file mode 100644
index 0000000..a345f8a
--- /dev/null
+++ b/tools/eclipse/buck_daemon_ui_dbg.launch
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_dbg"/>
+</launchConfiguration>
diff --git a/tools/eclipse/buck_daemon_ui_firefox.launch b/tools/eclipse/buck_daemon_ui_firefox.launch
new file mode 100644
index 0000000..383b051
--- /dev/null
+++ b/tools/eclipse/buck_daemon_ui_firefox.launch
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_firefox"/>
+</launchConfiguration>
diff --git a/tools/eclipse/buck_daemon_ui_ie9.launch b/tools/eclipse/buck_daemon_ui_ie9.launch
new file mode 100644
index 0000000..18863e7
--- /dev/null
+++ b/tools/eclipse/buck_daemon_ui_ie9.launch
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_ie9"/>
+</launchConfiguration>
diff --git a/tools/eclipse/buck_daemon_ui_safari.launch b/tools/eclipse/buck_daemon_ui_safari.launch
new file mode 100644
index 0000000..55259a9
--- /dev/null
+++ b/tools/eclipse/buck_daemon_ui_safari.launch
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_safari"/>
+</launchConfiguration>
diff --git a/tools/eclipse/buck_gwt_debug.launch b/tools/eclipse/buck_gwt_debug.launch
new file mode 100644
index 0000000..1723cbf
--- /dev/null
+++ b/tools/eclipse/buck_gwt_debug.launch
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/gerrit/buck-out/gen/lib/gwt/dev/gwt-dev-2.5.0.jar"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-startupUrl /&#10;-war ${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui/ui_dbg__tmp/war&#10;-server com.google.gerrit.gwtdebug.GerritDebugLauncher&#10;com.google.gerrit.GerritGwtUI"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M&#10;-XX:MaxPermSize=128M&#10;-Dgwt.persistentunitcachedir=${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui/ui_dbg__tmp/unit_cache&#10;-Dgerrit.source_root=${resource_loc:/gerrit}&#10;-Dgerrit.site_path=${resource_loc:/gerrit}/../test_site&#10;-da:com.google.gwtexpui.globalkey.client.KeyCommandSet"/>
+</launchConfiguration>
diff --git a/tools/eclipse/gen_classpath.py b/tools/eclipse/gen_classpath.py
new file mode 100755
index 0000000..3b5f09d
--- /dev/null
+++ b/tools/eclipse/gen_classpath.py
@@ -0,0 +1,121 @@
+#!/usr/bin/python
+# 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.
+#
+# TODO(sop): Remove hack after Buck supports Eclipse
+
+from os import path, symlink
+import re
+from subprocess import Popen, PIPE
+from sys import argv
+from xml.dom import minidom
+
+OUT = argv[1] if len(argv) >= 2 else None
+ROOT = path.abspath(__file__)
+for _ in range(0, 3):
+  ROOT = path.dirname(ROOT)
+
+MAIN = ['//tools/eclipse:classpath']
+GWT = ['//gerrit-gwtui:ui_module']
+JRE = '/'.join([
+  'org.eclipse.jdt.launching.JRE_CONTAINER',
+  'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType',
+  'JavaSE-1.6',
+])
+
+def query_classpath(targets):
+  deps = []
+  p = Popen(['buck', 'audit', 'classpath'] + targets, stdout = PIPE)
+  for line in p.stdout:
+    deps.append(line.strip())
+  s = p.wait()
+  if s != 0:
+    exit(s)
+  return deps
+
+def make_classpath():
+  impl = minidom.getDOMImplementation()
+  return impl.createDocument(None, 'classpath', None)
+
+doc = make_classpath()
+src = set()
+lib = set()
+gwt_src = set()
+gwt_lib = set()
+
+def classpathentry(kind, path, src = None):
+  e = doc.createElement('classpathentry')
+  e.setAttribute('kind', kind)
+  e.setAttribute('path', path)
+  if src:
+    e.setAttribute('sourcepath', src)
+  doc.documentElement.appendChild(e)
+
+java_library = re.compile(r'[^/]+/gen/(.*)/lib__[^/]+__output/[^/]+[.]jar$')
+for p in query_classpath(MAIN):
+  if p.endswith('-src.jar'):
+    # gwt_module() depends on -src.jar for Java to JavaScript compiles.
+    gwt_lib.add(p)
+    continue
+
+  if p.startswith('buck-out/gen/lib/gwt/'):
+    # gwt_module() depends on huge shaded GWT JARs that import
+    # incorrect versions of classes for Gerrit. Collect into
+    # a private grouping for later use.
+    gwt_lib.add(p)
+    continue
+
+  m = java_library.match(p)
+  if m:
+    src.add(m.group(1))
+  else:
+    lib.add(p)
+
+for p in query_classpath(GWT):
+  m = java_library.match(p)
+  if m:
+    gwt_src.add(m.group(1))
+
+for s in sorted(src):
+  p = path.join(s, 'java')
+  if path.exists(p):
+    classpathentry('src', p)
+    continue
+
+  for env in ['main', 'test']:
+    for type in ['java', 'resources']:
+      p = path.join(s, 'src', env, type)
+      if path.exists(p):
+        classpathentry('src', p)
+
+for libs in [lib, gwt_lib]:
+  for j in sorted(libs):
+    s = None
+    if j.endswith('.jar'):
+      s = j[:-4] + '-src.jar'
+      if not path.exists(s):
+        s = None
+    classpathentry('lib', j, s)
+
+for s in sorted(gwt_src):
+  classpathentry('lib', path.join(ROOT, s, 'src', 'main', 'java'))
+
+classpathentry('con', JRE)
+classpathentry('output', 'buck-out/classes')
+
+p = path.join(ROOT, '.classpath')
+with open(p, 'w') as fd:
+  doc.writexml(fd, addindent = '  ', newl = '\n', encoding='UTF-8')
+if OUT:
+  symlink(p, OUT)
diff --git a/tools/eclipse/gen_project.py b/tools/eclipse/gen_project.py
new file mode 100755
index 0000000..53593c0
--- /dev/null
+++ b/tools/eclipse/gen_project.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+# 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.
+#
+# TODO(sop): Remove hack after Buck supports Eclipse
+
+from __future__ import print_function
+
+from os import path, symlink
+from sys import argv
+
+OUT = argv[1]
+ROOT = path.abspath(__file__)
+for _ in range(0, 3):
+  ROOT = path.dirname(ROOT)
+
+p = path.join(ROOT, '.project')
+with open(p, 'w') as fd:
+  print("""\
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+  <name>gerrit</name>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+    </buildCommand>
+  </buildSpec>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+</projectDescription>\
+""", file=fd)
+symlink(p, OUT)
diff --git a/tools/gwtui_dbg.launch b/tools/gwtui_dbg.launch
deleted file mode 100644
index 8a873be..0000000
--- a/tools/gwtui_dbg.launch
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
-<stringAttribute key="bad_container_name" value="/gerrit-appja"/>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit-gwtdebug"/>
-</listAttribute>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
-<listEntry value="4"/>
-</listAttribute>
-<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
-<stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"/>
-<stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;sourceLookupDirector&gt;&#10;&lt;sourceContainers duplicates=&quot;false&quot;&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-common&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-httpd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-commonsnet&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-prettify&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-jgit&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-pgm&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-server&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-sshd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-cli&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-ssl&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-reviewdb&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-gwtui&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-gwtdebug&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;default/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.debug.core.containerType.default&quot;/&gt;&#10;&lt;/sourceContainers&gt;&#10;&lt;/sourceLookupDirector&gt;&#10;"/>
-<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
-<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
-<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
-</listAttribute>
-<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
-<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;gerrit-gwtdebug&quot; type=&quot;1&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;gerrit-war&quot; type=&quot;1&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gerrit-prettify/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gerrit-patch-jgit/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gerrit-reviewdb/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gerrit-common/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gerrit-gwtui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER&quot; path=&quot;3&quot; type=&quot;4&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gerrit-gwtexpui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwtjsonrpc/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwtorm/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gerrit-gwtui/target/classes&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
-</listAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/>
-<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-startupUrl /&#10;-war ${resource_loc:/gerrit-gwtui/target}/gwt-hosted-mode&#10;-server com.google.gerrit.gwtdebug.GerritDebugLauncher&#10;com.google.gerrit.GerritGwtUI"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit-gwtdebug"/>
-<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M&#10;-da:com.google.gwtexpui.globalkey.client.KeyCommandSet&#10;&#10;-Dgerrit.site_path=${resource_loc:/gerrit-parent}/../test_site"/>
-</launchConfiguration>
diff --git a/tools/maven_deploy.py b/tools/maven_deploy.py
new file mode 100644
index 0000000..4779abe
--- /dev/null
+++ b/tools/maven_deploy.py
@@ -0,0 +1,92 @@
+#!/usr/bin/python
+# 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.
+
+from __future__ import print_function
+from optparse import OptionParser
+from os.path import exists
+from sys import stderr
+from util import check_output
+
+opts = OptionParser()
+opts.add_option('-a', help='action (valid actions are: install,deploy)')
+opts.add_option('-v', help='gerrit version')
+opts.add_option('-d', help='dependencies (jars artifacts)')
+
+args, ctx = opts.parse_args()
+action = args.a
+if action not in ['deploy', 'install']:
+  print("unknown action : %s" % action, file=stderr)
+  exit(1)
+
+deps = args.d.split()
+if not deps:
+  print('dependencies are empty')
+  exit(1)
+
+extension_jar = [x for x in deps if "extension-api.jar" in x][0]
+extension_src = [x for x in deps if "extension-api-src.jar" in x][0]
+plugin_jar = [x for x in deps if "plugin-api.jar" in x][0]
+plugin_src = [x for x in deps if "plugin-api-src.jar" in x][0]
+
+version = args.v
+if not version:
+  print('version is empty')
+  exit(1)
+
+REPO_TYPE = 'snapshot' if version.endswith("SNAPSHOT") else 'release'
+URL = 's3://gerrit-api@commondatastorage.googleapis.com/%s' % REPO_TYPE
+
+plugin = ['-DartifactId=gerrit-plugin-api']
+extension = ['-DartifactId=gerrit-extension-api']
+common = [
+  '-DgroupId=com.google.gerrit',
+  '-Dversion=%s' % version,
+]
+jar = ['-Dpackaging=jar']
+src = ['-Dpackaging=java-source']
+
+cmd = {
+  'deploy': ['mvn',
+             'deploy:deploy-file',
+             '-DrepositoryId=gerrit-api-repository',
+             '-Durl=%s' % URL],
+  'install': ['mvn',
+              'install:install-file'],
+  }
+
+try:
+  check_output(cmd[action] +
+               plugin +
+               common +
+               jar +
+               ['-Dfile=%s' % plugin_jar])
+  check_output(cmd[action] +
+               plugin +
+               common +
+               src +
+               ['-Dfile=%s' % plugin_src])
+  check_output(cmd[action] +
+               extension +
+               common +
+               jar +
+               ['-Dfile=%s' % extension_jar])
+  check_output(cmd[action] +
+               extension +
+               common +
+               src +
+               ['-Dfile=%s' % extension_src])
+except Exception as e:
+  print('%s command failed: %s' % (action, e), file=stderr)
+  exit(1)
diff --git a/tools/pack_war.py b/tools/pack_war.py
new file mode 100755
index 0000000..0ba802b
--- /dev/null
+++ b/tools/pack_war.py
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+# 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.
+
+from optparse import OptionParser
+from os import environ, makedirs, path, symlink
+from subprocess import check_call
+from util import check_output
+
+opts = OptionParser()
+opts.add_option('-o', help='path to write WAR to')
+opts.add_option('--lib', action='append', help='target for WEB-INF/lib')
+opts.add_option('--pgmlib', action='append', help='target for WEB-INF/pgm-lib')
+args, ctx = opts.parse_args()
+
+war = environ['TMP']
+root = war[:war.index('buck-out')]
+jars = set()
+
+def link_jars(libs, dir):
+  makedirs(dir)
+  cp = check_output(['buck', 'audit', 'classpath'] + libs)
+  for j in cp.strip().splitlines():
+    if j not in jars:
+      jars.add(j)
+      n = path.basename(j)
+      if j.startswith('buck-out/gen/gerrit-'):
+        n = j.split('/')[2] + '-' + n
+      symlink(path.join(root, j), path.join(dir, n))
+
+if args.lib:
+  link_jars(args.lib, path.join(war, 'WEB-INF', 'lib'))
+if args.pgmlib:
+  link_jars(args.pgmlib, path.join(war, 'WEB-INF', 'pgm-lib'))
+for s in ctx:
+  check_call(['unzip', '-q', '-d', war, s])
+check_call(['zip', '-9qr', args.o, '.'], cwd = war)
diff --git a/tools/pgm_daemon.launch b/tools/pgm_daemon.launch
deleted file mode 100644
index cedf470..0000000
--- a/tools/pgm_daemon.launch
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
-<stringAttribute key="bad_container_name" value="/gerrit-appja"/>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit-main/src/main/java/Main.java"/>
-</listAttribute>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
-<listEntry value="1"/>
-</listAttribute>
-<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
-<stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"/>
-<stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;sourceLookupDirector&gt;&#10;&lt;sourceContainers duplicates=&quot;false&quot;&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-common&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-httpd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-commonsnet&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-jgit&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-pgm&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-server&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-sshd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-cli&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-ssl&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-war&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-reviewdb&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-main&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;default/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.debug.core.containerType.default&quot;/&gt;&#10;&lt;/sourceContainers&gt;&#10;&lt;/sourceLookupDirector&gt;&#10;"/>
-<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
-<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
-<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
-</listAttribute>
-<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
-<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon&#10;--console-log&#10;--show-stack-trace&#10;-d ${resource_loc:/gerrit-parent}/../test_site"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit-war"/>
-<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M"/>
-</launchConfiguration>
diff --git a/tools/release.sh b/tools/release.sh
deleted file mode 100755
index 88e4a00..0000000
--- a/tools/release.sh
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/sh
-
-flags=
-
-while [ $# -gt 0 ]
-do
-	case "$1" in
-	--no-documentation|--without-documentation)
-		flags="$flags -Dgerrit.documentation.skip=true"
-		shift
-		;;
-	--no-plugins|--without-plugins)
-		flags="$flags -Dgerrit.plugins.skip=true"
-		shift
-		;;
-	--no-tests|--without-tests)
-		flags="$flags -Dgerrit.acceptance-tests.skip=true"
-		flags="$flags -Dmaven.tests.skip=true"
-		shift
-		;;
-	*)
-		echo >&2 "usage: $0 [--no-documentation] [--no-plugins] [--no-tests]"
-		exit 1
-	esac
-done
-
-git update-index -q --refresh
-
-if test -n "$(git diff-index --name-only HEAD --)" \
-|| test -n "$(git ls-files --others --exclude-standard)"
-then
-	echo >&2 "error: working directory is dirty, refusing to build"
-	exit 1
-fi
-
-./tools/version.sh --release &&
-mvn clean package verify $flags
-rc=$?
-./tools/version.sh --reset
-
-if test 0 = $rc
-then
-	echo
-	echo Built Gerrit Code Review `git describe`:
-	ls gerrit-war/target/gerrit-*.war
-	echo
-fi
-exit $rc
diff --git a/tools/util.py b/tools/util.py
new file mode 100644
index 0000000..0c121e1
--- /dev/null
+++ b/tools/util.py
@@ -0,0 +1,20 @@
+# 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.
+
+try:
+  from subprocess import check_output
+except ImportError:
+  from subprocess import Popen, PIPE
+  def check_output(*cmd):
+    return Popen(*cmd, stdout=PIPE).communicate()[0]
diff --git a/tools/version.sh b/tools/version.sh
deleted file mode 100755
index def099d..0000000
--- a/tools/version.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh
-
-# Update all pom.xml with new build number
-#
-# TODO(sop) This should be converted to some sort of
-# Java based Maven plugin so its fully portable.
-#
-
-SERVER_POMS=$(git ls-files | grep pom.xml | grep -v /src/main/resources/archetype-resources/pom.xml)
-POM_FILES=$SERVER_POMS
-
-# CORE PLUGIN LIST
-PLUGINS="commit-message-length-validator replication reviewnotes"
-for p in $PLUGINS
-do
-	POM_FILES="$POM_FILES $(cd plugins/$p && git ls-files | grep pom.xml | sed s,^,plugins/$p/,)"
-done
-
-case "$1" in
---snapshot=*)
-	V=$(echo "$1" | perl -pe 's/^--snapshot=//')
-	if [ -z "$V" ]
-	then
-		echo >&2 "usage: $0 --snapshot=0.n.0"
-		exit 1
-	fi
-	case "$V" in
-	*-SNAPSHOT) : ;;
-	*) V=$V-SNAPSHOT ;;
-	esac
-	;;
-
---release)
-	V=$(git describe HEAD) || exit
-	;;
-
---reset)
-	git checkout HEAD -- $SERVER_POMS
-	for p in $PLUGINS
-	do
-		(cd plugins/$p; git checkout $(git ls-files | grep pom.xml))
-	done
-	exit $?
-	;;
-
-*)
-	echo >&2 "usage: $0 {--snapshot=2.n | --release}"
-	exit 1
-esac
-
-case "$V" in
-v*) V=$(echo "$V" | perl -pe s/^v//) ;;
-esac
-
-perl -pi.bak -e '
-	if ($ARGV ne $old_argv) {
-		$seen_version = 0;
-		$old_argv = $ARGV;
-	}
-	if (!$seen_version) {
-		$seen_version = 1 if
-		s{(<version>).*(</version>)}{${1}'"$V"'${2}};
-	}
-	' $POM_FILES
-
-for pom in $POM_FILES
-do
-	rm -f ${pom}.bak
-done
diff --git a/website/releases/index.html b/website/releases/index.html
new file mode 100644
index 0000000..27d4fb9
--- /dev/null
+++ b/website/releases/index.html
@@ -0,0 +1,155 @@
+<html>
+<head>
+  <title>Gerrit Code Review - Releases</title>
+  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
+  <style>
+  #diffy_logo {
+    float: left;
+    width: 75px;
+    height: 70px;
+    margin-right: 20px;
+  }
+  #download_container table {
+    border-spacing: 0;
+  }
+  #download_container td {
+    padding-right: 5px;
+  }
+  .latest-release {
+    background-color: lightgreen;
+  }
+  .rc {
+    padding-left: 1em;
+    font-style: italic;
+  }
+  .size {
+    text-align: right;
+  }
+  </style>
+</head>
+<body>
+
+<h1>Gerrit Code Review - Releases</h1>
+<a href="http://code.google.com/p/gerrit">
+<img id="diffy_logo" src="https://gerrit-review.googlesource.com/static/logo.png">
+</a>
+
+<div id='download_container'>
+</div>
+
+<script>
+$.getJSON(
+'https://www.googleapis.com/storage/v1beta2/b/gerrit-releases/o?projection=noAcl&fields=items(name%2Csize)&callback=?',
+function(data) {
+  var doc = document;
+  var frg = doc.createDocumentFragment();
+  var rx = /^gerrit(?:-full)?-([0-9.]+(?:-rc[0-9]+)?)[.]war/;
+  var rel = 'http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/';
+  var src = 'https://gerrit.googlesource.com/gerrit/+/'
+
+  data.items.sort(function(a,b) {
+    var av = rx.exec(a.name);
+    var bv = rx.exec(b.name);
+    if (!av || !bv) {
+      return a.name > b.name ? 1 : -1;
+    }
+
+    var an = av[1].replace('-rc', '.rc').split('.')
+    var bn = bv[1].replace('-rc', '.rc').split('.')
+    while (an.length < bn.length) an.push('0');
+    while (an.length > bn.length) bn.push('0');
+    for (var i = 0; i < an.length; i++) {
+      var ai = an[i].indexOf('rc') == 0
+        ? parseInt(an[i].substring(2))
+        : 1000 + parseInt(an[i]);
+
+      var bi = bn[i].indexOf('rc') == 0
+        ? parseInt(bn[i].substring(2))
+        : 1000 + parseInt(bn[i]);
+
+      if (ai != bi) {
+        return ai > bi ? -1 : 1;
+      }
+    }
+    return 0;
+  });
+
+  var latest = false;
+  for (var i = 0; i < data.items.length; i++) {
+    var f = data.items[i];
+    var v = rx.exec(f.name);
+
+    if ('index.html' == f.name) {
+      continue;
+    }
+
+    var tr = doc.createElement('tr');
+    var td = doc.createElement('td');
+    var a = doc.createElement('a');
+    a.href = f.name;
+    if (v) {
+      a.appendChild(doc.createTextNode('Gerrit ' + v[1]));
+    } else {
+      a.appendChild(doc.createTextNode(f.name));
+    }
+    if (f.name.indexOf('-rc') > 0) {
+      td.className = 'rc';
+    } else if (!latest) {
+      latest = true;
+      tr.className='latest-release';
+    }
+    td.appendChild(a);
+    tr.appendChild(td);
+
+    td = doc.createElement('td');
+    td.className = 'size';
+    if (f.size/(1024*1024) < 1) {
+      sizeText = Math.round(f.size/1024*10)/10 + ' KiB';
+    } else {
+      sizeText = Math.round(f.size/(1024*1024)*10)/10 + ' MiB';
+    }
+    td.appendChild(doc.createTextNode(sizeText));
+    tr.appendChild(td);
+
+    td = doc.createElement('td');
+    if (v && f.name.indexOf('-rc') < 0) {
+      a = doc.createElement('a');
+      a.href = rel + 'ReleaseNotes-' + v[1] + '.html';
+      a.appendChild(doc.createTextNode('Release Notes'));
+      td.appendChild(a);
+    }
+    tr.appendChild(td);
+
+    td = doc.createElement('td');
+    if (v) {
+      a = doc.createElement('a');
+      a.href = src + 'v' + v[1];
+      a.appendChild(doc.createTextNode('src'));
+      td.appendChild(a);
+    }
+    tr.appendChild(td);
+
+    frg.appendChild(tr);
+  }
+
+  var tr = doc.createElement('tr');
+  var th = doc.createElement('th');
+  th.appendChild(doc.createTextNode('File'));
+  tr.appendChild(th);
+
+  th = doc.createElement('th');
+  th.appendChild(doc.createTextNode('Size'));
+  tr.appendChild(th);
+
+  tr.appendChild(doc.createElement('th'));
+  tr.appendChild(doc.createElement('th'));
+
+  var table = doc.createElement('table');
+  table.appendChild(tr);
+  table.appendChild(frg);
+  doc.getElementById('download_container').appendChild(table);
+});
+</script>
+
+</body>
+</html>