Merge "Fix preload of gr-app.js resource"
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index fd57ff7..172bb97 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -113,290 +113,4 @@
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.processAnnotations=enabled
-org.eclipse.jdt.core.compiler.source=1.8
-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_annotation=0
-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_method_declaration=0
-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_resources_in_try=80
-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.alignment_for_union_type_in_multicatch=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_lambda_body=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.comment.new_lines_at_block_boundaries=true
-org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
-org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
-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.disabling_tag=@formatter\:off
-org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
-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_field=insert
-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_method=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=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_annotation_on_type=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_label=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_after_type_annotation=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_lambda_arrow=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_try=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_semicolon_in_try_resources=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_try=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_lambda_arrow=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_try=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_semicolon_in_try_resources=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_on_off_tags=false
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
-org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
-org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
-org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
+org.eclipse.jdt.core.compiler.source=1.8
\ No newline at end of file
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
index d990610..3d5f5f6 100644
--- a/.settings/org.eclipse.jdt.ui.prefs
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -1,60 +1,5 @@
eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=12
org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=\#;com.google;com;dk;eu;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/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index c1cd0e8..4ef2a35 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -214,6 +214,18 @@
bazel test --test_env=GERRIT_USE_SSH=NO //...
----
+To exclude tests that have been marked as flaky:
+
+----
+ bazel test --test_tag_filters=-flaky //...
+----
+
+To ignore cached test results:
+
+----
+ bazel test --cache_test_results=NO //...
+----
+
== Dependencies
Dependency JARs are normally downloaded as needed, but you can
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 4e168bd..435e316 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -144,7 +144,7 @@
link:https://gerrit-review.googlesource.com/#/settings/http-password[HTTP
Password tab of the user settings page].
-
+[[style]]
=== Style
Gerrit generally follows the
@@ -153,7 +153,7 @@
To format source code, Gerrit uses the
link:https://github.com/google/google-java-format[`google-java-format`]
-tool (version 1.2), which automatically formats code to follow the
+tool (version 1.3), which automatically formats code to follow the
style guide. Using this tool streamlines code review by reducing the
need for time-consuming, tedious, and contentious discussions about
trivial issues like whitespace.
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 5ada1e2..39fa333 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -45,12 +45,16 @@
[[Formatting]]
== Code Formatter Settings
-Import `tools/GoogleFormat.xml` using Window -> Preferences ->
-Java -> Code Style -> Formatter -> Import...
-
-This will define the 'Google Format' profile, which the project
-settings prefer when formatting source code.
-
+To format source code, Gerrit uses the
+link:https://github.com/google/google-java-format[`google-java-format`]
+tool (version 1.3), which automatically formats code to follow the
+style guide. See link:dev-contributing.html#style[Code Style] for the
+instruction how to set up command line tool that uses this formatter.
+The Eclipse plugin is provided that allows to format with the same
+formatter from within the Eclipse IDE. See
+link:https://github.com/google/google-java-format#eclipse[Eclipse plugin]
+for details how to install it. It's important to use the same plugin version
+as the `google-java-format` script.
== Site Initialization
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 4803d83..5bd8d46 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -271,12 +271,14 @@
** [[delete]]`Delete Change` / `Delete Revision`:
+
-Deletes the draft change / the currently viewed draft patch set.
+Deletes the change / the currently viewed draft patch set.
+
-The `Delete Change` / `Delete Revision` buttons are only available if a
-draft patch set is viewed and the user is the change owner or has the
-link:access-control.html#category_delete_drafts[Delete Drafts] access
-right assigned.
+For normal changes, the `Delete Change` button will only be available if the
+user is an administrator and the change hasn't been merged. For draft changes,
+the `Delete Change` / `Delete Revision` buttons will be available if the user
+is the change owner or has the
+link:access-control.html#category_delete_drafts[Delete Drafts] access right
+assigned.
** [[plugin-actions]]Further actions may be available if plugins are installed.
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index b7358d6..2cc64d8 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -696,6 +696,16 @@
identifiedUserFactory.create(account.getId()));
}
+ /**
+ * Enforce a new request context for the current API user.
+ *
+ * <p>This recreates the IdentifiedUser, hence everything which is cached in the IdentifiedUser is
+ * reloaded (e.g. the email addresses of the user).
+ */
+ protected Context resetCurrentApiUser() {
+ return atrScope.set(newRequestContext(atrScope.get().getSession().getAccount()));
+ }
+
protected Context setApiUser(TestAccount account) {
return atrScope.set(newRequestContext(account));
}
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java
index 1f00248..f7369d7 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java
@@ -111,4 +111,8 @@
b.append(session.getPort());
return b.toString();
}
+
+ public TestAccount getAccount() {
+ return account;
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGerritConfigAnnotationTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGerritConfigAnnotationTest.java
index 705ad4f..12fa3cc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGerritConfigAnnotationTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/annotation/UseGerritConfigAnnotationTest.java
@@ -36,8 +36,11 @@
}
@Test
- @GerritConfig(name = "x.y", values = { "a", "b" })
+ @GerritConfig(
+ name = "x.y",
+ values = {"a", "b"}
+ )
public void testList() {
- assertThat(cfg.getStringList("x", null, "y")).asList().containsExactly("a" , "b");
+ assertThat(cfg.getStringList("x", null, "y")).asList().containsExactly("a", "b");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 11b452e..82aa576 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -386,9 +386,7 @@
gApi.accounts().self().addEmail(input);
}
- // enforce a new request context so that emails that are cached in
- // IdentifiedUser are reloaded
- setApiUser(admin);
+ resetCurrentApiUser();
assertThat(getEmails()).containsAllIn(emails);
}
@@ -428,20 +426,67 @@
input.noConfirmation = true;
gApi.accounts().self().addEmail(input);
- // enforce a new request context so that emails that are cached in
- // IdentifiedUser are reloaded
- setApiUser(admin);
+ resetCurrentApiUser();
assertThat(getEmails()).contains(email);
gApi.accounts().self().deleteEmail(input.email);
- // enforce a new request context so that emails that are cached in
- // IdentifiedUser are reloaded
- setApiUser(admin);
+ resetCurrentApiUser();
assertThat(getEmails()).doesNotContain(email);
}
@Test
+ public void deleteEmailFromCustomExternalIdSchemes() throws Exception {
+ String email = "foo.bar@example.com";
+ String extId1 = "foo:bar";
+ String extId2 = "foo:baz";
+ db.accountExternalIds()
+ .insert(
+ ImmutableList.of(
+ createExternalIdWithEmail(extId1, email),
+ createExternalIdWithEmail(extId2, email)));
+ accountCache.evict(admin.id);
+ assertThat(
+ gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
+ .containsAllOf(extId1, extId2);
+
+ resetCurrentApiUser();
+ assertThat(getEmails()).contains(email);
+
+ gApi.accounts().self().deleteEmail(email);
+
+ resetCurrentApiUser();
+ assertThat(getEmails()).doesNotContain(email);
+ assertThat(
+ gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
+ .containsNoneOf(extId1, extId2);
+ }
+
+ @Test
+ public void deleteEmailOfOtherUser() throws Exception {
+ String email = "foo.bar@example.com";
+ EmailInput input = new EmailInput();
+ input.email = email;
+ input.noConfirmation = true;
+ gApi.accounts().id(user.id.get()).addEmail(input);
+
+ setApiUser(user);
+ assertThat(getEmails()).contains(email);
+
+ // admin can delete email of user
+ setApiUser(admin);
+ gApi.accounts().id(user.id.get()).deleteEmail(email);
+
+ setApiUser(user);
+ assertThat(getEmails()).doesNotContain(email);
+
+ // user cannot delete email of admin
+ exception.expect(AuthException.class);
+ exception.expectMessage("not allowed to delete email address");
+ gApi.accounts().id(admin.id.get()).deleteEmail(admin.email);
+ }
+
+ @Test
public void putStatus() throws Exception {
List<String> statuses = ImmutableList.of("OOO", "Busy");
AccountInfo info;
@@ -886,4 +931,10 @@
private Set<String> getEmails() throws RestApiException {
return gApi.accounts().self().getEmails().stream().map(e -> e.email).collect(toSet());
}
+
+ private AccountExternalId createExternalIdWithEmail(String id, String email) {
+ AccountExternalId extId = new AccountExternalId(admin.id, new AccountExternalId.Key(id));
+ extId.setEmailAddress(email);
+ return extId;
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
index fe80032..fcf939d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
@@ -21,6 +21,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.client.Theme;
@@ -35,6 +36,7 @@
import org.junit.Test;
@NoHttpd
+@Sandboxed
public class DiffPreferencesIT extends AbstractDaemonTest {
@Inject private AllUsersName allUsers;
@@ -136,4 +138,39 @@
// assert hard-coded defaults
assertPrefs(o, d, "lineLength", "tabSize", "fontSize");
}
+
+ @Test
+ public void overwriteConfiguredDefaults() throws Exception {
+ DiffPreferencesInfo d = DiffPreferencesInfo.defaults();
+ int configuredDefaultLineLength = d.lineLength + 10;
+ DiffPreferencesInfo update = new DiffPreferencesInfo();
+ update.lineLength = configuredDefaultLineLength;
+ gApi.config().server().setDefaultDiffPreferences(update);
+
+ DiffPreferencesInfo o = gApi.accounts().id(admin.getId().toString()).getDiffPreferences();
+ assertThat(o.lineLength).isEqualTo(configuredDefaultLineLength);
+ assertPrefs(o, d, "lineLength");
+
+ int newLineLength = configuredDefaultLineLength + 10;
+ DiffPreferencesInfo i = new DiffPreferencesInfo();
+ i.lineLength = newLineLength;
+ DiffPreferencesInfo a = gApi.accounts().id(admin.getId().toString()).setDiffPreferences(i);
+ assertThat(a.lineLength).isEqualTo(newLineLength);
+ assertPrefs(a, d, "lineLength");
+
+ a = gApi.accounts().id(admin.getId().toString()).getDiffPreferences();
+ assertThat(a.lineLength).isEqualTo(newLineLength);
+ assertPrefs(a, d, "lineLength");
+
+ // overwrite the configured default with original hard-coded default
+ i = new DiffPreferencesInfo();
+ i.lineLength = d.lineLength;
+ a = gApi.accounts().id(admin.getId().toString()).setDiffPreferences(i);
+ assertThat(a.lineLength).isEqualTo(d.lineLength);
+ assertPrefs(a, d, "lineLength");
+
+ a = gApi.accounts().id(admin.getId().toString()).getDiffPreferences();
+ assertThat(a.lineLength).isEqualTo(d.lineLength);
+ assertPrefs(a, d, "lineLength");
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
index 334f336..8ed2a45 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -19,6 +19,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat;
@@ -41,6 +42,7 @@
import org.junit.Test;
@NoHttpd
+@Sandboxed
public class GeneralPreferencesIT extends AbstractDaemonTest {
@Inject private AllUsersName allUsers;
@@ -122,4 +124,39 @@
// assert hard-coded defaults
assertPrefs(o, d, "my", "changeTable", "changesPerPage");
}
+
+ @Test
+ public void overwriteConfiguredDefaults() throws Exception {
+ GeneralPreferencesInfo d = GeneralPreferencesInfo.defaults();
+ int configuredChangesPerPage = d.changesPerPage * 2;
+ GeneralPreferencesInfo update = new GeneralPreferencesInfo();
+ update.changesPerPage = configuredChangesPerPage;
+ gApi.config().server().setDefaultPreferences(update);
+
+ GeneralPreferencesInfo o = gApi.accounts().id(admin.getId().toString()).getPreferences();
+ assertThat(o.changesPerPage).isEqualTo(configuredChangesPerPage);
+ assertPrefs(o, d, "my", "changeTable", "changesPerPage");
+
+ int newChangesPerPage = configuredChangesPerPage * 2;
+ GeneralPreferencesInfo i = new GeneralPreferencesInfo();
+ i.changesPerPage = newChangesPerPage;
+ GeneralPreferencesInfo a = gApi.accounts().id(admin.getId().toString()).setPreferences(i);
+ assertThat(a.changesPerPage).isEqualTo(newChangesPerPage);
+ assertPrefs(a, d, "my", "changeTable", "changesPerPage");
+
+ a = gApi.accounts().id(admin.getId().toString()).getPreferences();
+ assertThat(a.changesPerPage).isEqualTo(newChangesPerPage);
+ assertPrefs(a, d, "my", "changeTable", "changesPerPage");
+
+ // overwrite the configured default with original hard-coded default
+ i = new GeneralPreferencesInfo();
+ i.changesPerPage = d.changesPerPage;
+ a = gApi.accounts().id(admin.getId().toString()).setPreferences(i);
+ assertThat(a.changesPerPage).isEqualTo(d.changesPerPage);
+ assertPrefs(a, d, "my", "changeTable", "changesPerPage");
+
+ a = gApi.accounts().id(admin.getId().toString()).getPreferences();
+ assertThat(a.changesPerPage).isEqualTo(d.changesPerPage);
+ assertPrefs(a, d, "my", "changeTable", "changesPerPage");
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EmailIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EmailIT.java
index f9d3357..186d625 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EmailIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EmailIT.java
@@ -42,7 +42,7 @@
String email = "foo.bar2@example.com";
assertThat(getEmails()).doesNotContain(email);
- createEmail(email.replaceAll("@", "%40"));
+ createEmail(email.replace("@", "%40"));
assertThat(getEmails()).contains(email);
}
@@ -67,8 +67,7 @@
createEmail(email);
assertThat(getEmails()).contains(email);
- RestResponse r =
- adminRestSession.delete("/accounts/self/emails/" + email.replaceAll("@", "%40"));
+ RestResponse r = adminRestSession.delete("/accounts/self/emails/" + email.replace("@", "%40"));
r.assertNoContent();
assertThat(getEmails()).doesNotContain(email);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
index ece30e9..f05ecce 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
@@ -15,12 +15,15 @@
package com.google.gerrit.acceptance.rest.config;
import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toSet;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.server.config.ListTasks.TaskInfo;
import com.google.gson.reflect.TypeToken;
import java.util.List;
+import java.util.Optional;
+import java.util.Set;
import org.junit.Test;
public class KillTaskIT extends AbstractDaemonTest {
@@ -30,17 +33,24 @@
List<TaskInfo> result =
newGson().fromJson(r.getReader(), new TypeToken<List<TaskInfo>>() {}.getType());
r.consume();
- int taskCount = result.size();
- assertThat(taskCount).isGreaterThan(0);
- r = adminRestSession.delete("/config/server/tasks/" + result.get(0).id);
+ Optional<String> id =
+ result
+ .stream()
+ .filter(t -> "Log File Compressor".equals(t.command))
+ .map(t -> t.id)
+ .findFirst();
+ assertThat(id.isPresent()).isTrue();
+
+ r = adminRestSession.delete("/config/server/tasks/" + id.get());
r.assertNoContent();
r.consume();
r = adminRestSession.get("/config/server/tasks/");
result = newGson().fromJson(r.getReader(), new TypeToken<List<TaskInfo>>() {}.getType());
r.consume();
- assertThat(result).hasSize(taskCount - 1);
+ Set<String> ids = result.stream().map(t -> t.id).collect(toSet());
+ assertThat(ids).doesNotContain(id.get());
}
private void killTask_NotFound() throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
index 7db5b9e..d423a2d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
@@ -208,6 +208,35 @@
gApi.accounts().id("user").setActive(true);
}
+ @Test
+ public void sendNotificationAfterPersistingComments() throws Exception {
+ String changeId = createChangeWithReview();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+ List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
+ assertThat(comments).hasSize(2);
+ String ts =
+ MailUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
+
+ // Build Message
+ String txt =
+ newPlaintextBody(
+ canonicalWebUrl.get() + "#/c/" + changeInfo._number + "/1",
+ "Test Message",
+ null,
+ null,
+ null);
+ MailMessage.Builder b =
+ messageBuilderWithDefaultFields()
+ .from(user.emailAddress)
+ .textContent(txt + textFooterForChange(changeId, ts));
+
+ sender.clear();
+ mailProcessor.process(b.build());
+
+ assertNotifyTo(admin);
+ }
+
private static CommentInput newComment(String path, Side side, int line, String message) {
CommentInput c = new CommentInput();
c.path = path;
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
index 96c4b8d..8541cf8 100644
--- 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
@@ -14,6 +14,8 @@
package com.google.gerrit.server.account;
+import static java.util.stream.Collectors.toSet;
+
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -31,6 +33,7 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
+import java.util.Set;
@Singleton
public class DeleteEmail implements RestModifyView<AccountResource.Email, Input> {
@@ -69,13 +72,26 @@
if (!realm.allowsEdit(AccountFieldName.REGISTER_NEW_EMAIL)) {
throw new MethodNotAllowedException("realm does not allow deleting emails");
}
- AccountExternalId.Key key = new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
- AccountExternalId extId = dbProvider.get().accountExternalIds().get(key);
- if (extId == null) {
+
+ Set<AccountExternalId> extIds =
+ dbProvider
+ .get()
+ .accountExternalIds()
+ .byAccount(user.getAccountId())
+ .toList()
+ .stream()
+ .filter(e -> email.equals(e.getEmailAddress()))
+ .collect(toSet());
+ if (extIds.isEmpty()) {
throw new ResourceNotFoundException(email);
}
+
try {
- accountManager.unlink(user.getAccountId(), AuthRequest.forEmail(email));
+ for (AccountExternalId extId : extIds) {
+ AuthRequest authRequest = new AuthRequest(extId.getKey().get());
+ authRequest.setEmailAddress(email);
+ accountManager.unlink(user.getAccountId(), authRequest);
+ }
} catch (AccountException e) {
throw new ResourceConflictException(e.getMessage());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneralPreferencesLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneralPreferencesLoader.java
index 1c44670..df64c0b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneralPreferencesLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneralPreferencesLoader.java
@@ -77,14 +77,6 @@
// Load all users default prefs
VersionedAccountPreferences dp = VersionedAccountPreferences.forDefault();
dp.load(allUsers);
- GeneralPreferencesInfo allUserPrefs = new GeneralPreferencesInfo();
- loadSection(
- dp.getConfig(),
- UserConfigSections.GENERAL,
- null,
- allUserPrefs,
- GeneralPreferencesInfo.defaults(),
- in);
// Load user prefs
VersionedAccountPreferences p = VersionedAccountPreferences.forUser(id);
@@ -95,13 +87,33 @@
UserConfigSections.GENERAL,
null,
new GeneralPreferencesInfo(),
- updateDefaults(allUserPrefs),
+ readDefaultsFromGit(dp.getConfig(), in),
in);
loadChangeTableColumns(r, p, dp);
return loadMyMenusAndUrlAliases(r, p, dp);
}
}
+ public GeneralPreferencesInfo readDefaultsFromGit(Repository git, GeneralPreferencesInfo in)
+ throws ConfigInvalidException, IOException {
+ VersionedAccountPreferences dp = VersionedAccountPreferences.forDefault();
+ dp.load(git);
+ return readDefaultsFromGit(dp.getConfig(), in);
+ }
+
+ private GeneralPreferencesInfo readDefaultsFromGit(Config config, GeneralPreferencesInfo in)
+ throws ConfigInvalidException {
+ GeneralPreferencesInfo allUserPrefs = new GeneralPreferencesInfo();
+ loadSection(
+ config,
+ UserConfigSections.GENERAL,
+ null,
+ allUserPrefs,
+ GeneralPreferencesInfo.defaults(),
+ in);
+ return updateDefaults(allUserPrefs);
+ }
+
private GeneralPreferencesInfo updateDefaults(GeneralPreferencesInfo input) {
GeneralPreferencesInfo result = GeneralPreferencesInfo.defaults();
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
index 5a39bea..0edff4f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
@@ -69,28 +69,30 @@
Account.Id id, GitRepositoryManager gitMgr, AllUsersName allUsersName, DiffPreferencesInfo in)
throws IOException, ConfigInvalidException, RepositoryNotFoundException {
try (Repository git = gitMgr.openRepository(allUsersName)) {
- // Load all users prefs.
- VersionedAccountPreferences dp = VersionedAccountPreferences.forDefault();
- dp.load(git);
- DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
- loadSection(
- dp.getConfig(),
- UserConfigSections.DIFF,
- null,
- allUserPrefs,
- DiffPreferencesInfo.defaults(),
- in);
-
- // Load user prefs
VersionedAccountPreferences p = VersionedAccountPreferences.forUser(id);
p.load(git);
DiffPreferencesInfo prefs = new DiffPreferencesInfo();
loadSection(
- p.getConfig(), UserConfigSections.DIFF, null, prefs, updateDefaults(allUserPrefs), in);
+ p.getConfig(), UserConfigSections.DIFF, null, prefs, readDefaultsFromGit(git, in), in);
return prefs;
}
}
+ static DiffPreferencesInfo readDefaultsFromGit(Repository git, DiffPreferencesInfo in)
+ throws ConfigInvalidException, IOException {
+ VersionedAccountPreferences dp = VersionedAccountPreferences.forDefault();
+ dp.load(git);
+ DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
+ loadSection(
+ dp.getConfig(),
+ UserConfigSections.DIFF,
+ null,
+ allUserPrefs,
+ DiffPreferencesInfo.defaults(),
+ in);
+ return updateDefaults(allUserPrefs);
+ }
+
private static DiffPreferencesInfo updateDefaults(DiffPreferencesInfo input) {
DiffPreferencesInfo result = DiffPreferencesInfo.defaults();
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
index 986124a..ac0cc96 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import static com.google.gerrit.server.account.GetDiffPreferences.readDefaultsFromGit;
import static com.google.gerrit.server.account.GetDiffPreferences.readFromGit;
import static com.google.gerrit.server.config.ConfigUtil.loadSection;
import static com.google.gerrit.server.config.ConfigUtil.storeSection;
@@ -74,18 +75,12 @@
throws RepositoryNotFoundException, IOException, ConfigInvalidException {
DiffPreferencesInfo out = new DiffPreferencesInfo();
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
+ DiffPreferencesInfo allUserPrefs = readDefaultsFromGit(md.getRepository(), null);
VersionedAccountPreferences prefs = VersionedAccountPreferences.forUser(userId);
prefs.load(md);
- DiffPreferencesInfo defaults = DiffPreferencesInfo.defaults();
- storeSection(prefs.getConfig(), UserConfigSections.DIFF, null, in, defaults);
+ storeSection(prefs.getConfig(), UserConfigSections.DIFF, null, in, allUserPrefs);
prefs.commit(md);
- loadSection(
- prefs.getConfig(),
- UserConfigSections.DIFF,
- null,
- out,
- DiffPreferencesInfo.defaults(),
- null);
+ loadSection(prefs.getConfig(), UserConfigSections.DIFF, null, out, allUserPrefs, null);
}
return out;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index e1ed6aa..91672f7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -104,7 +104,7 @@
UserConfigSections.GENERAL,
null,
i,
- GeneralPreferencesInfo.defaults());
+ loader.readDefaultsFromGit(md.getRepository(), null));
storeMyChangeTableColumns(prefs, i.changeTable);
storeMyMenus(prefs, i.my);
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 8e1ab10..be719c5 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
@@ -479,7 +479,7 @@
out._number = in.getId().get();
if (user.isIdentifiedUser()) {
- Collection<String> stars = cd.stars().get(user.getAccountId());
+ Collection<String> stars = cd.stars(user.getAccountId());
out.starred = stars.contains(StarredChangesUtil.DEFAULT_LABEL) ? true : null;
if (!stars.isEmpty()) {
out.stars = stars;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
index 392db81..2e8fc2d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -49,7 +49,7 @@
public class EmailReviewComments implements Runnable, RequestContext {
private static final Logger log = LoggerFactory.getLogger(EmailReviewComments.class);
- interface Factory {
+ public interface Factory {
EmailReviewComments create(
NotifyHandling notify,
ListMultimap<RecipientType, Account.Id> accountsToNotify,
@@ -111,7 +111,7 @@
this.labels = labels;
}
- void sendAsync() {
+ public void sendAsync() {
@SuppressWarnings("unused")
Future<?> possiblyIgnoredError = sendEmailsExecutor.submit(this);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
index 14e1f92..843ef3c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -140,8 +140,8 @@
* Partition the reference tips into two sets:
*
* <ul>
- * <li> before = commits with time < target.getCommitTime()
- * <li> after = commits with time >= target.getCommitTime()
+ * <li>before = commits with time < target.getCommitTime()
+ * <li>after = commits with time >= target.getCommitTime()
* </ul>
*
* Each of the before/after lists is sorted by the the commit time.
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 c7e0222..2526db194 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
@@ -304,9 +304,9 @@
* <p>This adds the following footers if they are missing:
*
* <ul>
- * <li> Reviewed-on: <i>url</i>
- * <li> Reviewed-by | Tested-by | <i>Other-Label-Name</i>: <i>reviewer</i>
- * <li> Change-Id
+ * <li>Reviewed-on: <i>url</i>
+ * <li>Reviewed-by | Tested-by | <i>Other-Label-Name</i>: <i>reviewer</i>
+ * <li>Change-Id
* </ul>
*
* @param n
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
index f4c0867..2316782 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
@@ -109,6 +109,7 @@
ImmutableSet.of(
// Required for ChangeIsVisibleToPrdicate.
ChangeField.CHANGE.getName(),
+ ChangeField.REVIEWER.getName(),
// Required during advertiseOpenChanges.
ChangeField.PATCH_SET.getName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
index 20e163e..163d414 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
@@ -90,12 +90,13 @@
&& elementName.equals("div")
&& !e.className().startsWith("gmail")) {
// This is a comment typed by the user
- String content = e.ownText().trim();
+ // Replace non-breaking spaces and trim string
+ String content = e.ownText() .replace('\u00a0', ' ').trim();
if (!Strings.isNullOrEmpty(content)) {
if (lastEncounteredComment == null && lastEncounteredFileName == null) {
// Remove quotation line, email signature and
// "Sent from my xyz device"
- content = ParserUtil.trimQuotationLine(content);
+ content = ParserUtil.trimQuotation(content);
// TODO(hiesel) Add more sanitizer
if (!Strings.isNullOrEmpty(content)) {
parsedComments.add(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 08bd0ba..54b30c7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -15,7 +15,10 @@
package com.google.gerrit.server.mail.receive;
import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -28,16 +31,19 @@
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.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountByEmailCache;
+import com.google.gerrit.server.change.EmailReviewComments;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.extensions.events.CommentAdded;
import com.google.gerrit.server.git.BatchUpdate;
-import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.mail.MailFilter;
import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.util.ManualRequestContext;
@@ -48,8 +54,10 @@
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
@@ -69,6 +77,9 @@
private final Provider<InternalChangeQuery> queryProvider;
private final Provider<ReviewDb> reviewDb;
private final DynamicMap<MailFilter> mailFilters;
+ private final EmailReviewComments.Factory outgoingMailFactory;
+ private final CommentAdded commentAdded;
+ private final ApprovalsUtil approvalsUtil;
private final Provider<String> canonicalUrl;
@Inject
@@ -83,6 +94,9 @@
Provider<InternalChangeQuery> queryProvider,
Provider<ReviewDb> reviewDb,
DynamicMap<MailFilter> mailFilters,
+ EmailReviewComments.Factory outgoingMailFactory,
+ ApprovalsUtil approvalsUtil,
+ CommentAdded commentAdded,
@CanonicalWebUrl Provider<String> canonicalUrl) {
this.accountByEmailCache = accountByEmailCache;
this.buf = buf;
@@ -94,6 +108,9 @@
this.queryProvider = queryProvider;
this.reviewDb = reviewDb;
this.mailFilters = mailFilters;
+ this.outgoingMailFactory = outgoingMailFactory;
+ this.commentAdded = commentAdded;
+ this.approvalsUtil = approvalsUtil;
this.canonicalUrl = canonicalUrl;
}
@@ -192,6 +209,10 @@
private final PatchSet.Id psId;
private final List<MailComment> parsedComments;
private final String tag;
+ private ChangeMessage changeMessage;
+ private List<Comment> comments;
+ private PatchSet patchSet;
+ private ChangeControl changeControl;
private Op(PatchSet.Id psId, List<MailComment> parsedComments, String messageId) {
this.psId = psId;
@@ -200,10 +221,11 @@
}
@Override
- public boolean updateChange(ChangeContext ctx)
+ public boolean updateChange(BatchUpdate.ChangeContext ctx)
throws OrmException, UnprocessableEntityException {
- PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
- if (ps == null) {
+ changeControl = ctx.getControl();
+ patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+ if (patchSet == null) {
throw new OrmException("patch set not found: " + psId);
}
@@ -217,10 +239,9 @@
changeMsg += "\n" + numComments(parsedComments.size());
}
- ChangeMessage msg = ChangeMessagesUtil.newMessage(ctx, changeMsg, tag);
- changeMessagesUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), msg);
-
- List<Comment> comments = new ArrayList<>();
+ changeMessage = ChangeMessagesUtil.newMessage(ctx, changeMsg, tag);
+ changeMessagesUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), changeMessage);
+ comments = new ArrayList<>();
for (MailComment c : parsedComments) {
if (c.type == MailComment.CommentType.CHANGE_MESSAGE) {
continue;
@@ -241,7 +262,7 @@
side = Side.fromShort(c.inReplyTo.side);
} else {
fileName = c.fileName;
- psForComment = ps;
+ psForComment = patchSet;
side = Side.REVISION;
}
@@ -272,6 +293,38 @@
return true;
}
+
+ @Override
+ public void postUpdate(BatchUpdate.Context ctx) throws Exception {
+ // Send email notifications
+ outgoingMailFactory
+ .create(
+ NotifyHandling.ALL,
+ ArrayListMultimap.create(),
+ changeControl.getNotes(),
+ patchSet,
+ ctx.getUser().asIdentifiedUser(),
+ changeMessage,
+ comments,
+ null,
+ ImmutableList.of())
+ .sendAsync();
+ // Get previous approvals from this user
+ Map<String, Short> approvals = new HashMap<>();
+ approvalsUtil
+ .byPatchSetUser(ctx.getDb(), changeControl, psId, ctx.getAccountId())
+ .forEach(a -> approvals.put(a.getLabel(), a.getValue()));
+ // Fire Gerrit event. Note that approvals can't be granted via email, so old and new approvals
+ // are always the same here.
+ commentAdded.fire(
+ changeControl.getChange(),
+ patchSet,
+ ctx.getAccount(),
+ changeMessage.getMessage(),
+ approvals,
+ approvals,
+ ctx.getWhen());
+ }
}
private static boolean useHtmlParser(MailMessage m) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
index 72eb18a..bfead94 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.mail.receive;
import com.google.gerrit.reviewdb.client.Comment;
+import java.util.StringJoiner;
import java.util.regex.Pattern;
public class ParserUtil {
@@ -24,39 +25,44 @@
+ "(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})");
/**
- * Trims the quotation line that email clients add Example: On Sun, Nov 20, 2016 at 10:33 PM,
- * <gerrit@hiesel.it> wrote:
+ * Trims the quotation that email clients add Example: On Sun, Nov 20, 2016 at 10:33 PM,
+ * <gerrit@gerritcodereview.com> wrote:
*
* @param comment Comment parsed from an email.
* @return Trimmed comment.
*/
- public static String trimQuotationLine(String comment) {
- // Identifying the quotation line is hard, as it can be in any language.
- // We identify this line by it's characteristics: It usually contains a
- // valid email address, some digits for the date in groups of 1-4 in a row
- // as well as some characters.
- StringBuilder b = new StringBuilder();
- for (String line : comment.split("\n")) {
- // Count occurrences of digit groups
- int numConsecutiveDigits = 0;
- int maxConsecutiveDigits = 0;
- int numDigitGroups = 0;
- for (char c : line.toCharArray()) {
- if (c >= '0' && c <= '9') {
- numConsecutiveDigits++;
- } else if (numConsecutiveDigits > 0) {
- maxConsecutiveDigits = Integer.max(maxConsecutiveDigits, numConsecutiveDigits);
- numConsecutiveDigits = 0;
- numDigitGroups++;
- }
+ public static String trimQuotation(String comment) {
+ StringJoiner j = new StringJoiner("\n");
+ String[] lines = comment.split("\n");
+ for (int i = 0; i < lines.length - 2; i++) {
+ j.add(lines[i]);
+ }
+
+ // Check if the last line contains the full quotation pattern (date + email)
+ String lastLine = lines[lines.length - 1];
+ if (containsQuotationPattern(lastLine)) {
+ if (lines.length > 1) {
+ j.add(lines[lines.length - 2]);
}
- if (numDigitGroups < 4
- || maxConsecutiveDigits > 4
- || !SIMPLE_EMAIL_PATTERN.matcher(line).find()) {
- b.append(line);
+ return j.toString().trim();
+ }
+
+ // Check if the second last line + the last line contain the full quotation pattern. This is
+ // necessary, as the quotation line can be split across the last two lines if it gets too long.
+ if (lines.length > 1) {
+ String lastLines = lines[lines.length - 2] + lastLine;
+ if (containsQuotationPattern(lastLines)) {
+ return j.toString().trim();
}
}
- return b.toString().trim();
+
+ // Add the last two lines
+ if (lines.length > 1) {
+ j.add(lines[lines.length - 2]);
+ }
+ j.add(lines[lines.length - 1]);
+
+ return j.toString().trim();
}
/** Check if string is an inline comment url on a patch set or the base */
@@ -69,4 +75,31 @@
public static String filePath(String changeUrl, Comment comment) {
return changeUrl + "/" + comment.key.patchSetId + "/" + comment.key.filename;
}
+
+ private static boolean containsQuotationPattern(String s) {
+ // Identifying the quotation line is hard, as it can be in any language.
+ // We identify this line by it's characteristics: It usually contains a
+ // valid email address, some digits for the date in groups of 1-4 in a row
+ // as well as some characters.
+
+ // Count occurrences of digit groups
+ int numConsecutiveDigits = 0;
+ int maxConsecutiveDigits = 0;
+ int numDigitGroups = 0;
+ for (char c : s.toCharArray()) {
+ if (c >= '0' && c <= '9') {
+ numConsecutiveDigits++;
+ } else if (numConsecutiveDigits > 0) {
+ maxConsecutiveDigits = Integer.max(maxConsecutiveDigits, numConsecutiveDigits);
+ numConsecutiveDigits = 0;
+ numDigitGroups++;
+ }
+ }
+ if (numDigitGroups < 4 || maxConsecutiveDigits > 4) {
+ return false;
+ }
+
+ // Check if the string contains an email address
+ return SIMPLE_EMAIL_PATTERN.matcher(s).find();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
index a752e89..fa33cc6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail.receive;
+import com.google.common.base.Strings;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import com.google.gerrit.reviewdb.client.Comment;
@@ -64,12 +65,21 @@
String lastEncounteredFileName = null;
Comment lastEncounteredComment = null;
for (String line : lines) {
+ if (line.equals(">")) {
+ // Skip empty lines
+ continue;
+ }
if (line.startsWith("> ")) {
line = line.substring("> ".length()).trim();
// This is not a comment, try to advance the file/comment pointers and
// add previous comment to list if applicable
if (currentComment != null) {
- parsedComments.add(currentComment);
+ if (currentComment.type == MailComment.CommentType.CHANGE_MESSAGE) {
+ currentComment.message = ParserUtil.trimQuotation(currentComment.message);
+ }
+ if (!Strings.isNullOrEmpty(currentComment.message)) {
+ parsedComments.add(currentComment);
+ }
currentComment = null;
}
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 2263873..17e43f2 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
@@ -24,6 +24,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
@@ -342,8 +344,8 @@
private Map<Account.Id, Ref> editsByUser;
private Set<Account.Id> reviewedBy;
private Map<Account.Id, Ref> draftsByUser;
- @Deprecated private Set<Account.Id> starredByUser;
private ImmutableListMultimap<Account.Id, String> stars;
+ private StarsOf starsOf;
private ImmutableMap<Account.Id, StarRef> starRefs;
private ReviewerSet reviewers;
private List<ReviewerStatusUpdate> reviewerUpdates;
@@ -1180,23 +1182,6 @@
this.hashtags = hashtags;
}
- @Deprecated
- public Set<Account.Id> starredBy() throws OrmException {
- if (starredByUser == null) {
- if (!lazyLoad) {
- return Collections.emptySet();
- }
- starredByUser =
- checkNotNull(starredChangesUtil).byChange(legacyId, StarredChangesUtil.DEFAULT_LABEL);
- }
- return starredByUser;
- }
-
- @Deprecated
- public void setStarredBy(Set<Account.Id> starredByUser) {
- this.starredByUser = starredByUser;
- }
-
public ImmutableListMultimap<Account.Id, String> stars() throws OrmException {
if (stars == null) {
if (!lazyLoad) {
@@ -1225,15 +1210,23 @@
return starRefs;
}
- @AutoValue
- abstract static class ReviewedByEvent {
- private static ReviewedByEvent create(ChangeMessage msg) {
- return new AutoValue_ChangeData_ReviewedByEvent(msg.getAuthor(), msg.getWrittenOn());
+ public Set<String> stars(Account.Id accountId) throws OrmException {
+ if (starsOf != null) {
+ if (!starsOf.accountId().equals(accountId)) {
+ starsOf = null;
+ }
}
-
- public abstract Account.Id author();
-
- public abstract Timestamp ts();
+ if (starsOf == null) {
+ if (stars != null) {
+ starsOf = StarsOf.create(accountId, stars.get(accountId));
+ } else {
+ if (!lazyLoad) {
+ return ImmutableSet.of();
+ }
+ starsOf = StarsOf.create(accountId, starredChangesUtil.getLabels(accountId, legacyId));
+ }
+ }
+ return starsOf.stars();
}
@Override
@@ -1272,4 +1265,26 @@
public void setRefStatePatterns(Iterable<byte[]> refStatePatterns) {
this.refStatePatterns = ImmutableList.copyOf(refStatePatterns);
}
+
+ @AutoValue
+ abstract static class ReviewedByEvent {
+ private static ReviewedByEvent create(ChangeMessage msg) {
+ return new AutoValue_ChangeData_ReviewedByEvent(msg.getAuthor(), msg.getWrittenOn());
+ }
+
+ public abstract Account.Id author();
+
+ public abstract Timestamp ts();
+ }
+
+ @AutoValue
+ abstract static class StarsOf {
+ private static StarsOf create(Account.Id accountId, Iterable<String> stars) {
+ return new AutoValue_ChangeData_StarsOf(accountId, ImmutableSortedSet.copyOf(stars));
+ }
+
+ public abstract Account.Id accountId();
+
+ public abstract ImmutableSortedSet<String> stars();
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/HtmlParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/HtmlParserTest.java
index 7d729bc..11bc4ff 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/HtmlParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/HtmlParserTest.java
@@ -41,7 +41,7 @@
b.htmlContent(
newHtmlBody(
"Looks good to me",
- "I have a comment on this.",
+ "I have a comment on this. ",
null,
"Also have a comment here.",
null,
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java
new file mode 100644
index 0000000..dfa492c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2016 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.receive;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class ParserUtilTest {
+ @Test
+ public void trimQuotationLineOnMessageWithoutQuoatationLine() throws Exception {
+ assertThat(ParserUtil.trimQuotation("One line")).isEqualTo("One line");
+ assertThat(ParserUtil.trimQuotation("Two\nlines")).isEqualTo("Two\nlines");
+ assertThat(ParserUtil.trimQuotation("Thr\nee\nlines")).isEqualTo("Thr\nee\nlines");
+ }
+
+ @Test
+ public void trimQuotationLineOnMixedMessages() throws Exception {
+ assertThat(
+ ParserUtil.trimQuotation(
+ "One line\n"
+ + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+ + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+ .isEqualTo("One line");
+ assertThat(
+ ParserUtil.trimQuotation(
+ "One line\n"
+ + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit) "
+ + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+ .isEqualTo("One line");
+ }
+
+ @Test
+ public void trimQuotationLineOnMessagesContainingQuoationLine() throws Exception {
+ assertThat(
+ ParserUtil.trimQuotation(
+ "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+ + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+ .isEqualTo("");
+ assertThat(
+ ParserUtil.trimQuotation(
+ "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit) "
+ + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+ .isEqualTo("");
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
index b23b341..a98835b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
@@ -180,6 +180,8 @@
private static String newPlaintextBody(
String changeMessage, String c1, String c2, String c3, String f1, String f2, String fc1) {
return (changeMessage == null ? "" : changeMessage + "\n")
+ + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+ + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote: \n"
+ "> Foo Bar has posted comments on this change. ( \n"
+ "> "
+ changeURL
@@ -217,6 +219,7 @@
+ "> Should entry.getKey() be included in this message?\n"
+ "> \n"
+ (c1 == null ? "" : c1 + "\n")
+ + ">\n"
+ "> \n"
+ "> "
+ changeURL
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 9f31550..ab3a98a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -84,6 +84,7 @@
on-tap-item-cherrypick="_handleCherrypickTap"
on-tap-item-delete="_handleDeleteTap"
hidden$="[[_shouldHideActions(_menuActions.*, _loading)]]"
+ disabled-ids="[[_disabledMenuActions]]"
items="[[_menuActions]]">More</gr-dropdown>
<section hidden$="[[_shouldHideActions(_topLevelActions.*, _loading)]]">
<template
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 148b724..aa90a09 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -170,6 +170,10 @@
type: Array,
value: function() { return []; },
},
+ _disabledMenuActions: {
+ type: Array,
+ value: function() { return []; },
+ },
},
ActionType: ActionType,
@@ -281,6 +285,7 @@
this._keyCount(revisionActionsChangeRecord) === 0 &&
additionalActions.length === 0;
this._actionLoadingMessage = null;
+ this._disabledMenuActions = [];
},
_getValuesFor: function(obj) {
@@ -563,9 +568,16 @@
_setLoadingOnButtonWithKey: function(key) {
this._actionLoadingMessage = this._computeLoadingLabel(key);
- // Return a NoOp for menu keys. @see Issue 5366
- if (MENU_ACTION_KEYS.indexOf(key) !== -1) { return function() {}; }
+ // If the action appears in the overflow menu.
+ if (MENU_ACTION_KEYS.indexOf(key) !== -1) {
+ this.push('_disabledMenuActions', key === '/' ? 'delete' : key);
+ return function() {
+ this._actionLoadingMessage = null;
+ this._disabledMenuActions = [];
+ }.bind(this);
+ }
+ // Otherwise it's a top-level action.
var buttonEl = this.$$('[data-action-key="' + key + '"]');
buttonEl.setAttribute('loading', true);
buttonEl.disabled = true;
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index c1b582e..9d27ddc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -399,11 +399,16 @@
});
test('_setLoadingOnButtonWithKey overflow menu', function() {
- // TODO(wyatta): Should not throw error when setting loading on an
- // overflow action. @see Issue 5366
var key = 'cherrypick';
var cleanup = element._setLoadingOnButtonWithKey(key);
+ assert.equal(element._actionLoadingMessage, 'Cherry-Picking...');
+ assert.include(element._disabledMenuActions, 'cherrypick');
assert.isFunction(cleanup);
+
+ cleanup();
+
+ assert.notOk(element._actionLoadingMessage);
+ assert.notInclude(element._disabledMenuActions, 'cherrypick');
});
suite('revert change', function() {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 53a0c9e..6bd98ff 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -106,7 +106,7 @@
}
.commitMessage {
font-family: var(--monospace-font-family);
- flex: 1 0 72ch;
+ max-width: 100ch;
margin-right: 2em;
margin-bottom: 1em;
}
@@ -188,6 +188,22 @@
.patchInfo-header-wrapper {
width: 100%;
}
+ #commitMessage.collapsed {
+ max-height: 36em;
+ overflow: hidden;
+ }
+ .commitContainer {
+ display: flex;
+ flex-direction: column;
+ }
+ .collapseToggleContainer {
+ display: flex;
+ justify-content: center;
+ }
+ .collapseToggleContainer gr-button {
+ display: block;
+ width: 8em;
+ }
@media screen and (max-width: 50em) {
.mobile {
display: block;
@@ -310,27 +326,44 @@
</div>
<hr class="mobile">
<div class="commitAndRelated">
- <div class="commitMessage">
- <gr-editable-content id="commitMessageEditor"
- editing="[[_editingCommitMessage]]"
- content="{{_latestCommitMessage}}">
- <gr-linked-text pre
- content="[[_latestCommitMessage]]"
- config="[[_projectConfig.commentlinks]]"
- remove-zero-width-space></gr-linked-text>
- </gr-editable-content>
- <gr-button link
- class="editCommitMessage"
- on-tap="_handleEditCommitMessage"
- hidden$="[[_hideEditCommitMessage]]">Edit</gr-button>
- <div class="changeId" hidden$="[[!_changeIdCommitMessageError]]">
- <hr>
- Change-Id:
- <span
- class$="[[_computeChangeIdClass(_changeIdCommitMessageError)]]"
- title$="[[_computeTitleAttributeWarning(_changeIdCommitMessageError)]]">
- [[_change.change_id]]
- </span>
+ <div class="commitContainer">
+ <div
+ id="commitMessage"
+ class$="commitMessage [[_computeCommitClass(_commitCollapsed, _latestCommitMessage)]]">
+ <gr-editable-content id="commitMessageEditor"
+ editing="[[_editingCommitMessage]]"
+ content="{{_latestCommitMessage}}"
+ remove-zero-width-space>
+ <gr-linked-text pre
+ content="[[_latestCommitMessage]]"
+ config="[[_projectConfig.commentlinks]]"
+ remove-zero-width-space></gr-linked-text>
+ </gr-editable-content>
+ <gr-button link
+ class="editCommitMessage"
+ on-tap="_handleEditCommitMessage"
+ hidden$="[[_hideEditCommitMessage]]">Edit</gr-button>
+ <div class="changeId" hidden$="[[!_changeIdCommitMessageError]]">
+ <hr>
+ Change-Id:
+ <span
+ class$="[[_computeChangeIdClass(_changeIdCommitMessageError)]]"
+ title$="[[_computeTitleAttributeWarning(_changeIdCommitMessageError)]]">
+ [[_change.change_id]]
+ </span>
+ </div>
+ </div>
+ <div
+ id="commitCollapseToggle"
+ class="collapseToggleContainer"
+ hidden$="[[_computeCommitToggleHidden(_latestCommitMessage)]]">
+ <gr-button
+ link
+ id="commitCollapseToggleButton"
+ class="collapseToggleButton"
+ on-tap="_toggleCommitCollapsed">
+ [[_computeCollapseCommitText(_commitCollapsed)]]
+ </gr-button>
</div>
</div>
<div class="relatedChanges">
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 3ec5847..7c8d92e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -20,6 +20,9 @@
};
var CHANGE_ID_REGEX_PATTERN = /^Change-Id\:\s(I[0-9a-f]{8,40})/gm;
var COMMENT_SAVE = 'Saving... Try again after all comments are saved.';
+
+ var MIN_LINES_FOR_COMMIT_COLLAPSE = 30;
+
// Maximum length for patch set descriptions.
var PATCH_DESC_MAX_LENGTH = 500;
var REVIEWERS_REGEX = /^R=/gm;
@@ -132,6 +135,10 @@
type: String,
computed: '_computeChangeStatus(_change, _patchRange.patchNum)',
},
+ _commitCollapsed: {
+ type: Boolean,
+ value: true,
+ },
},
behaviors: [
@@ -686,15 +693,6 @@
return label;
},
- _switchToMostRecentPatchNum: function() {
- this._reload().then(function() {
- var patchNum = this._computeLatestPatchNum(this._allPatchSets);
- if (patchNum !== this._patchRange.patchNum) {
- this._changePatchNum(patchNum);
- }
- }.bind(this));
- },
-
_handleAKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e) ||
this.modifierPressed(e) ||
@@ -714,9 +712,8 @@
_handleCapitalRKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
-
e.preventDefault();
- this._switchToMostRecentPatchNum();
+ page.show('/c/' + this._change._number);
},
_handleSKey: function(e) {
@@ -839,6 +836,7 @@
},
_prepareCommitMsgForLinkify: function(msg) {
+ // TODO(wyatta) switch linkify sequence, see issue 5526.
// This is a zero-with space. It is added to prevent the linkify library
// from including R= as part of the email address.
return msg.replace(REVIEWERS_REGEX, 'R=\u200B');
@@ -1025,5 +1023,23 @@
_computeChangePermalinkAriaLabel: function(changeNum) {
return 'Change ' + changeNum;
},
+
+ _computeCommitClass: function(collapsed, commitMessage) {
+ if (this._computeCommitToggleHidden(commitMessage)) { return ''; }
+ return collapsed ? 'collapsed' : '';
+ },
+
+ _computeCollapseCommitText: function(collapsed) {
+ return collapsed ? 'Show more' : 'Show less';
+ },
+
+ _toggleCommitCollapsed: function() {
+ this._commitCollapsed = !this._commitCollapsed;
+ },
+
+ _computeCommitToggleHidden: function(commitMessage) {
+ if (!commitMessage) { return true; }
+ return commitMessage.split('\n').length < MIN_LINES_FOR_COMMIT_COLLAPSE;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 8162102..e36a55b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -101,16 +101,6 @@
test('shift + R should fetch and navigate to the latest patch set',
function(done) {
- // Prevent all network requests to prevent random exceptions.
- sandbox.stub(window, 'fetch', function() {
- return Promise.resolve({
- ok: true,
- text: function() {
- return Promise.resolve(')]}\'\n');
- },
- });
- });
-
element._changeNum = '42';
element._patchRange = {
basePatchNum: 'PARENT',
@@ -118,6 +108,7 @@
};
element._change = {
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+ _number: 42,
revisions: {
rev1: {_number: 1},
},
@@ -128,23 +119,9 @@
};
sandbox.stub(element.$.actions, 'reload');
- sandbox.stub(element.$.restAPI, '_getChangeDetail', function() {
- // Mock change obj.
- return Promise.resolve({
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- revisions: {
- rev1: {_number: 1, commit: {}},
- rev13: {_number: 13},
- },
- current_revision: 'rev1',
- status: 'NEW',
- labels: {},
- actions: {},
- });
- });
var showStub = sandbox.stub(page, 'show', function(arg) {
- assert.equal(arg, '/c/42/13');
+ assert.equal(arg, '/c/42');
done();
});
@@ -971,5 +948,28 @@
element.serverConfig = {};
assert.isFalse(element._replyDisabled);
});
+
+ suite('commit message expand/collapse', function() {
+ test('commitCollapseToggle hidden for short commit message', function() {
+ element._latestCommitMessage = '';
+ assert.isTrue(element.$.commitCollapseToggle.hasAttribute('hidden'));
+ });
+
+ test('commitCollapseToggle shown for long commit message', function() {
+ element._latestCommitMessage = _.times(31, String).join('\n');
+ assert.isFalse(element.$.commitCollapseToggle.hasAttribute('hidden'));
+ });
+
+ test('commitCollapseToggle functions', function() {
+ element._latestCommitMessage = _.times(31, String).join('\n');
+ assert.isTrue(element._commitCollapsed);
+ assert.isTrue(
+ element.$.commitMessage.classList.contains('collapsed'));
+ MockInteractions.tap(element.$.commitCollapseToggleButton);
+ assert.isFalse(element._commitCollapsed);
+ assert.isFalse(
+ element.$.commitMessage.classList.contains('collapsed'));
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
index 94e15b6..8ec1055 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
@@ -198,6 +198,13 @@
<td>Open download overlay</td>
</tr>
<tr>
+ <td>
+ <span class="key modifier">Shift</span>
+ <span class="key">r</span>
+ </td>
+ <td>Reload the change at the latest patch</td>
+ </tr>
+ <tr>
<td></td><td class="header">File list</td>
</tr>
<tr>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
index 2c44813..adbb1a4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
@@ -24,6 +24,9 @@
display: block;
white-space: normal;
}
+ gr-diff-comment-thread + gr-diff-comment-thread {
+ margin-top: .2em;
+ }
</style>
<template is="dom-repeat" items="[[_threadGroups]]"
as="thread">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
index bcd9b27..b9fc9f7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
@@ -74,6 +74,14 @@
_sortByDate: function(threadGroups) {
if (!threadGroups.length) { return; }
return threadGroups.sort(function(a, b) {
+ // If a comment is a draft, it doesn't have a start_datetime yet.
+ // Assume it is newer than the comment it is being compared to.
+ if (!a.start_datetime) {
+ return 1;
+ }
+ if (!b.start_datetime) {
+ return -1;
+ }
return util.parseDate(a.start_datetime) -
util.parseDate(b.start_datetime);
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
index 7ae9b9d..79e58ee 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
@@ -165,6 +165,32 @@
];
assert.deepEqual(element._sortByDate(threadGroups), expectedResult);
+
+ // When a comment doesn't have a date, the one without the date should be
+ // last.
+ var threadGroups = [
+ {
+ start_datetime: '2015-12-23 15:00:20.396000000',
+ comments: [],
+ locationRange: 'line',
+ },
+ {
+ comments: [],
+ locationRange: 'range-1-1-1-2',
+ },
+ ];
+
+ var expectedResult = [
+ {
+ start_datetime: '2015-12-23 15:00:20.396000000',
+ comments: [],
+ locationRange: 'line',
+ },
+ {
+ comments: [],
+ locationRange: 'range-1-1-1-2',
+ },
+ ];
});
test('_calculateLocationRange', function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
index fcd7574..7a60317 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
@@ -28,6 +28,10 @@
margin-bottom: 1px;
white-space: normal;
}
+ .actions {
+ border-top: 1px dotted #bbb;
+ padding: .5em .7em;
+ }
#container {
background-color: #fcfad6;
}
@@ -36,21 +40,31 @@
}
</style>
<div id="container" class$="[[_computeHostClass(_unresolved)]]">
- <template id="commentList" is="dom-repeat" items="[[_orderedComments]]" as="comment">
+ <template id="commentList" is="dom-repeat" items="[[_orderedComments]]"
+ as="comment">
<gr-diff-comment
comment="{{comment}}"
+ robot-button-disabled="[[_hideActions(_showActions, _lastComment)]]"
change-num="[[changeNum]]"
patch-num="[[patchNum]]"
draft="[[comment.__draft]]"
show-actions="[[_showActions]]"
comment-side="[[comment.__commentSide]]"
project-config="[[projectConfig]]"
- on-comment-discard="_handleCommentDiscard"
- on-create-ack-comment="_handleCommentAck"
- on-create-done-comment="_handleCommentDone"
on-create-fix-comment="_handleCommentFix"
- on-create-reply-comment="_handleCommentReply"></gr-diff-comment>
+ on-comment-discard="_handleCommentDiscard"></gr-diff-comment>
</template>
+ <div class="actions"
+ hidden$="[[_hideActions(_showActions, _lastComment)]]">
+ <gr-button id="replyBtn" class="action reply"
+ on-tap="_handleCommentReply">Reply</gr-button>
+ <gr-button id="quoteBtn" class="action quote"
+ on-tap="_handleCommentQuote">Quote</gr-button>
+ <gr-button id="ackBtn" class="action ack" on-tap="_handleCommentAck">
+ Ack</gr-button>
+ <gr-button id="doneBtn" class="action done" on-tap="_handleCommentDone">
+ Done</gr-button>
+ </div>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-storage id="storage"></gr-storage>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
index 7d91e91..fc130c7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
@@ -47,6 +47,7 @@
},
_showActions: Boolean,
+ _lastComment: Object,
_orderedComments: Array,
_unresolved: {
type: Boolean,
@@ -77,22 +78,28 @@
this._setInitialExpandedState();
},
- addOrEditDraft: function(opt_lineNum) {
- var lastComment = this.comments[this.comments.length - 1];
- if (lastComment && lastComment.__draft) {
+ addOrEditDraft: function(opt_lineNum, opt_range) {
+ var lastComment = this.comments[this.comments.length - 1] || {};
+ if (lastComment.__draft) {
var commentEl = this._commentElWithDraftID(
lastComment.id || lastComment.__draftID);
commentEl.editing = true;
+
+ // If the comment was collapsed, re-open it to make it clear which
+ // actions are available.
+ commentEl.collapsed = false;
} else {
- this.addDraft(opt_lineNum,
- lastComment ? lastComment.range : undefined);
+ var range = opt_range ? opt_range :
+ lastComment ? lastComment.range : undefined;
+ var unresolved = lastComment ? lastComment.unresolved : undefined;
+ this.addDraft(opt_lineNum, range, unresolved);
}
},
- addDraft: function(opt_lineNum, opt_range) {
+ addDraft: function(opt_lineNum, opt_range, opt_unresolved) {
var draft = this._newDraft(opt_lineNum, opt_range);
draft.__editing = true;
- draft.unresolved = true;
+ draft.unresolved = opt_unresolved === false ? opt_unresolved : true;
this.push('comments', draft);
},
@@ -102,7 +109,14 @@
_commentsChanged: function(changeRecord) {
this._orderedComments = this._sortedComments(this.comments);
- this._unresolved = this._getLastComment().unresolved;
+ if (this._orderedComments.length) {
+ this._lastComment = this._getLastComment();
+ this._unresolved = this._lastComment.unresolved;
+ }
+ },
+
+ _hideActions: function(_showActions, _lastComment) {
+ return !_showActions || !_lastComment || !!_lastComment.__draft;
},
_getLastComment: function() {
@@ -191,23 +205,31 @@
}
},
- _handleCommentReply: function(e) {
- var comment = e.detail.comment;
+ _processCommentReply: function(opt_quote) {
+ var comment = this._lastComment;
var quoteStr;
- if (e.detail.quote) {
+ if (opt_quote) {
var msg = comment.message;
quoteStr = '> ' + msg.replace(NEWLINE_PATTERN, '\n> ') + '\n\n';
}
this._createReplyComment(comment, quoteStr, true, comment.unresolved);
},
+ _handleCommentReply: function(e) {
+ this._processCommentReply();
+ },
+
+ _handleCommentQuote: function(e) {
+ this._processCommentReply(true);
+ },
+
_handleCommentAck: function(e) {
- var comment = e.detail.comment;
+ var comment = this._lastComment;
this._createReplyComment(comment, 'Ack', false, comment.unresolved);
},
_handleCommentDone: function(e) {
- var comment = e.detail.comment;
+ var comment = this._lastComment;
this._createReplyComment(comment, 'Done', false, false);
},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
index ef4ab14..424cef6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
@@ -43,6 +43,7 @@
var sandbox;
setup(function() {
+ sandbox = sinon.sandbox.create();
stub('gr-rest-api-interface', {
getLoggedIn: function() { return Promise.resolve(false); },
});
@@ -147,6 +148,17 @@
assert.isFalse(commentElStub.called);
assert.isTrue(addDraftStub.called);
});
+
+ test('_hideActions', function() {
+ var showActions = true;
+ var lastComment = {};
+ assert.equal(element._hideActions(showActions, lastComment), false);
+ showActions = false;
+ assert.equal(element._hideActions(showActions, lastComment), true);
+ var showActions = true;
+ lastComment.__draft = true;
+ assert.equal(element._hideActions(showActions, lastComment), true);
+ });
});
suite('comment action tests', function() {
@@ -189,33 +201,35 @@
test('reply', function(done) {
var commentEl = element.$$('gr-diff-comment');
assert.ok(commentEl);
- commentEl.addEventListener('create-reply-comment', function() {
- var drafts = element._orderedComments.filter(function(c) {
- return c.__draft == true;
- });
- assert.equal(drafts.length, 1);
- assert.notOk(drafts[0].message, 'message should be empty');
- assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
- done();
+
+ var replyBtn = element.$.replyBtn;
+ MockInteractions.tap(replyBtn);
+ flushAsynchronousOperations();
+
+ var drafts = element._orderedComments.filter(function(c) {
+ return c.__draft == true;
});
- commentEl.fire('create-reply-comment', {comment: commentEl.comment},
- {bubbles: false});
+ assert.equal(drafts.length, 1);
+ assert.notOk(drafts[0].message, 'message should be empty');
+ assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+ done();
});
test('quote reply', function(done) {
var commentEl = element.$$('gr-diff-comment');
assert.ok(commentEl);
- commentEl.addEventListener('create-reply-comment', function() {
- var drafts = element._orderedComments.filter(function(c) {
+
+ var quoteBtn = element.$.quoteBtn;
+ MockInteractions.tap(quoteBtn);
+ flushAsynchronousOperations();
+
+ var drafts = element._orderedComments.filter(function(c) {
return c.__draft == true;
- });
- assert.equal(drafts.length, 1);
- assert.equal(drafts[0].message, '> is this a crossover episode!?\n\n');
- assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
- done();
});
- commentEl.fire('create-reply-comment', {comment: commentEl.comment,
- quote: true}, {bubbles: false});
+ assert.equal(drafts.length, 1);
+ assert.equal(drafts[0].message, '> is this a crossover episode!?\n\n');
+ assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+ done();
});
test('quote reply multiline', function(done) {
@@ -233,27 +247,32 @@
var commentEl = element.$$('gr-diff-comment');
assert.ok(commentEl);
- commentEl.addEventListener('create-reply-comment', function() {
- var drafts = element._orderedComments.filter(function(c) {
- return c.__draft == true;
- });
- assert.equal(drafts.length, 1);
- assert.equal(drafts[0].message,
- '> is this a crossover episode!?\n> It might be!\n\n');
- assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
- done();
+
+ var quoteBtn = element.$.quoteBtn;
+ MockInteractions.tap(quoteBtn);
+ flushAsynchronousOperations();
+
+ var drafts = element._orderedComments.filter(function(c) {
+ return c.__draft == true;
});
- commentEl.fire('create-reply-comment', {comment: commentEl.comment,
- quote: true}, {bubbles: false});
+ assert.equal(drafts.length, 1);
+ assert.equal(drafts[0].message,
+ '> is this a crossover episode!?\n> It might be!\n\n');
+ assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+ done();
});
test('ack', function(done) {
element.changeNum = '42';
element.patchNum = '1';
+
var commentEl = element.$$('gr-diff-comment');
assert.ok(commentEl);
- commentEl.addEventListener('create-ack-comment', function() {
- var drafts = element._orderedComments.filter(function(c) {
+
+ var ackBtn = element.$.ackBtn;
+ MockInteractions.tap(ackBtn);
+ flush(function() {
+ var drafts = element.comments.filter(function(c) {
return c.__draft == true;
});
assert.equal(drafts.length, 1);
@@ -261,8 +280,6 @@
assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
done();
});
- commentEl.fire('create-ack-comment', {comment: commentEl.comment},
- {bubbles: false});
});
test('done', function(done) {
@@ -270,8 +287,11 @@
element.patchNum = '1';
var commentEl = element.$$('gr-diff-comment');
assert.ok(commentEl);
- commentEl.addEventListener('create-done-comment', function() {
- var drafts = element._orderedComments.filter(function(c) {
+
+ var doneBtn = element.$.doneBtn;
+ MockInteractions.tap(doneBtn);
+ flush(function() {
+ var drafts = element.comments.filter(function(c) {
return c.__draft == true;
});
assert.equal(drafts.length, 1);
@@ -280,8 +300,6 @@
assert.isFalse(drafts[0].unresolved);
done();
});
- commentEl.fire('create-done-comment', {comment: commentEl.comment},
- {bubbles: false});
});
test('please fix', function(done) {
@@ -328,7 +346,7 @@
});
test('first editing comment does not add __otherEditing attribute',
- function(done) {
+ function() {
var commentEl = element.$$('gr-diff-comment');
element.comments = [{
author: {
@@ -341,101 +359,16 @@
updated: '2015-12-08 19:48:33.843000000',
__draft: true,
}];
+
+ var replyBtn = element.$.replyBtn;
+ MockInteractions.tap(replyBtn);
flushAsynchronousOperations();
- commentEl.addEventListener('create-reply-comment', function() {
- var editing = element._orderedComments.filter(function(c) {
- return c.__editing == true;
- });
- assert.equal(editing.length, 1);
- assert.equal(!!editing[0].__otherEditing, false);
- done();
+ var editing = element._orderedComments.filter(function(c) {
+ return c.__editing == true;
});
- commentEl.fire('create-reply-comment', {comment: commentEl.comment},
- {bubbles: false});
- });
-
- test('two editing comments adds __otherEditing attribute', function(done) {
- var commentEl = element.$$('gr-diff-comment');
- element.comments = [{
- author: {
- name: 'Mr. Peanutbutter',
- email: 'tenn1sballchaser@aol.com',
- },
- id: 'baf0414d_60047215',
- line: 5,
- message: 'is this a crossover episode!?',
- updated: '2015-12-08 19:48:33.843000000',
- __editing: true,
- __draft: true,
- }];
- flushAsynchronousOperations();
-
- commentEl.addEventListener('create-reply-comment', function() {
- var editing = element._orderedComments.filter(function(c) {
- return c.__editing == true;
- });
- assert.equal(editing.length, 2);
- assert.equal(editing[1].__otherEditing, true);
- done();
- });
- commentEl.fire('create-reply-comment', {comment: commentEl.comment},
- {bubbles: false});
- });
-
- test('When editing other comments, local storage set after discard',
- function(done) {
- element.changeNum = '42';
- element.patchNum = '1';
- element.comments = [{
- author: {
- name: 'Mr. Peanutbutter',
- email: 'tenn1sballchaser@aol.com',
- },
- id: 'baf0414d_60047215',
- in_reply_to: 'baf0414d_60047215',
- line: 5,
- message: 'is this a crossover episode!?',
- updated: '2015-12-08 19:48:31.843000000',
- },
- {
- author: {
- name: 'Mr. Peanutbutter',
- email: 'tenn1sballchaser@aol.com',
- },
- __draftID: '1',
- in_reply_to: 'baf0414d_60047215',
- line: 5,
- message: 'yes',
- updated: '2015-12-08 19:48:32.843000000',
- __draft: true,
- __editing: true,
- },
- {
- author: {
- name: 'Mr. Peanutbutter',
- email: 'tenn1sballchaser@aol.com',
- },
- __draftID: '2',
- in_reply_to: 'baf0414d_60047215',
- line: 5,
- message: 'no',
- updated: '2015-12-08 19:48:33.843000000',
- __draft: true,
- __editing: true,
- }];
- var storageStub = sinon.stub(element.$.storage, 'setDraftComment');
- flushAsynchronousOperations();
-
- var draftEl =
- Polymer.dom(element.root).querySelectorAll('gr-diff-comment')[1];
- assert.ok(draftEl);
- draftEl.addEventListener('comment-discard', function() {
- assert.isTrue(storageStub.called);
- storageStub.restore();
- done();
- });
- draftEl.fire('comment-discard', null, {bubbles: false});
+ assert.equal(editing.length, 1);
+ assert.equal(!!editing[0].__otherEditing, false);
});
test('When not editing other comments, local storage not set after discard',
@@ -581,10 +514,37 @@
assert.equal(element._computeHostClass(false), '');
});
+ test('addDraft sets unresolved state correctly', function() {
+ var unresolved = true;
+ element.comments = [];
+ element.addDraft(null, null, unresolved);
+ assert.equal(element.comments[0].unresolved, true);
+
+ unresolved = false; // comment should get added as actually resolved.
+ element.comments = [];
+ element.addDraft(null, null, unresolved);
+ assert.equal(element.comments[0].unresolved, false);
+
+ element.comments = [];
+ element.addDraft();
+ assert.equal(element.comments[0].unresolved, true);
+ });
+
test('_newDraft', function() {
element.commentSide = 'left';
var draft = element._newDraft();
assert.equal(draft.__commentSide, 'left');
});
+
+ test('new comment gets created', function() {
+ element.comments = [];
+ element.addOrEditDraft(1);
+ assert.equal(element.comments.length, 1);
+ // Mock a submitted comment.
+ element.comments[0].__draft = false;
+ element.comments[0].id = element.comments[0].__draftID;
+ element.addOrEditDraft(1);
+ assert.equal(element.comments.length, 2);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index ceedf73..8863aa6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -134,7 +134,7 @@
margin-top: -.4em;
}
.runIdInformation {
- margin-bottom: .5em;
+ margin: 1em 0;
}
.robotRun {
margin-left: .5em;
@@ -218,6 +218,7 @@
on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
<gr-formatted-text class="message"
content="[[comment.message]]"
+ no-trailing-margin="[[!comment.__draft]]"
collapsed="[[collapsed]]"
config="[[projectConfig.commentlinks]]"></gr-formatted-text>
<div hidden$="[[!comment.robot_run_id]]">
@@ -229,11 +230,6 @@
</div>
</div>
<div class="actions humanActions" hidden$="[[!_showHumanActions]]">
- <gr-button class="action reply" on-tap="_handleReply">Reply</gr-button>
- <gr-button class="action quote" on-tap="_handleQuote">Quote</gr-button>
- <gr-button class="action ack" on-tap="_handleAck">Ack</gr-button>
- <gr-button class="action done" on-tap="_handleDone">
- Done</gr-button>
<gr-button class="action edit hideOnPublished" on-tap="_handleEdit">
Edit</gr-button>
<gr-button class="action save hideOnPublished" on-tap="_handleSave"
@@ -254,7 +250,9 @@
</div>
</div>
<div class="actions robotActions" hidden$="[[!_showRobotActions]]">
- <gr-button class="action fix" on-tap="_handleFix">
+ <gr-button class="action fix"
+ on-tap="_handleFix"
+ disabled="[[robotButtonDisabled]]">
Please Fix
</gr-button>
</div>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index bc12b8f..1e6eef8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -20,24 +20,6 @@
is: 'gr-diff-comment',
/**
- * Fired when the Reply action is triggered.
- *
- * @event create-reply-comment
- */
-
- /**
- * Fired when the Ack action is triggered.
- *
- * @event create-ack-comment
- */
-
- /**
- * Fired when the Done action is triggered.
- *
- * @event create-done-comment
- */
-
- /**
* Fired when the create fix comment action is triggered.
*
* @event create-fix-comment
@@ -107,6 +89,7 @@
observer: '_toggleCollapseClass',
},
projectConfig: Object,
+ robotButtonDisabled: Boolean,
_xhrPromise: Object, // Used for testing.
_messageText: {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index 6ea5b96..1196c1b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -98,37 +98,6 @@
'header middle content is not visible');
});
- test('proper event fires on reply', function(done) {
- element.addEventListener('create-reply-comment', function(e) {
- assert.ok(e.detail.comment);
- done();
- });
- MockInteractions.tap(element.$$('.reply'));
- });
-
- test('proper event fires on quote', function(done) {
- element.addEventListener('create-reply-comment', function(e) {
- assert.ok(e.detail.comment);
- assert.isTrue(e.detail.quote);
- done();
- });
- MockInteractions.tap(element.$$('.quote'));
- });
-
- test('proper event fires on ack', function(done) {
- element.addEventListener('create-ack-comment', function(e) {
- done();
- });
- MockInteractions.tap(element.$$('.ack'));
- });
-
- test('proper event fires on done', function(done) {
- element.addEventListener('create-done-comment', function(e) {
- done();
- });
- MockInteractions.tap(element.$$('.done'));
- });
-
test('clicking on date link does not trigger nav', function() {
var showStub = sinon.stub(page, 'show');
var dateEl = element.$$('.date');
@@ -286,10 +255,6 @@
assert.isTrue(isVisible(element.$$('.discard')), 'discard is visible');
assert.isFalse(isVisible(element.$$('.save')), 'save is not visible');
assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is not visible');
- assert.isFalse(isVisible(element.$$('.reply')), 'reply is not visible');
- assert.isFalse(isVisible(element.$$('.quote')), 'quote is not visible');
- assert.isFalse(isVisible(element.$$('.ack')), 'ack is not visible');
- assert.isFalse(isVisible(element.$$('.done')), 'done is not visible');
assert.isFalse(isVisible(element.$$('.resolve')),
'resolve is not visible');
assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
@@ -300,10 +265,6 @@
assert.isTrue(isVisible(element.$$('.discard')), 'discard is visible');
assert.isTrue(isVisible(element.$$('.save')), 'save is visible');
assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is visible');
- assert.isFalse(isVisible(element.$$('.reply')), 'reply is not visible');
- assert.isFalse(isVisible(element.$$('.quote')), 'quote is not visible');
- assert.isFalse(isVisible(element.$$('.ack')), 'ack is not visible');
- assert.isFalse(isVisible(element.$$('.done')), 'done is not visible');
assert.isTrue(isVisible(element.$$('.resolve')), 'resolve is visible');
assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
@@ -315,10 +276,6 @@
'discard is not visible');
assert.isFalse(isVisible(element.$$('.save')), 'save is not visible');
assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is not visible');
- assert.isTrue(isVisible(element.$$('.reply')), 'reply is visible');
- assert.isTrue(isVisible(element.$$('.quote')), 'quote is visible');
- assert.isTrue(isVisible(element.$$('.ack')), 'ack is visible');
- assert.isTrue(isVisible(element.$$('.done')), 'done is visible');
assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 6221481..a5147f1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -230,7 +230,7 @@
var threadEl = this._getOrCreateThreadAtLineRange(contentEl, patchNum,
diffSide, side, range);
- threadEl.addDraft(line, range);
+ threadEl.addOrEditDraft(line, range);
},
_addDraft: function(lineEl, opt_lineNum) {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
index c72e3da..db07de4 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
@@ -62,12 +62,16 @@
display: block;
padding: .85em 1em;
}
+ li .itemAction.disabled {
+ color: #ccc;
+ cursor: default;
+ }
li .itemAction:link,
li .itemAction:visited {
color: #00e;
text-decoration: none;
}
- li .itemAction:hover {
+ li .itemAction:not(.disabled):hover {
background-color: #6B82D6;
color: #fff;
}
@@ -127,7 +131,7 @@
initial-count="75">
<li>
<span
- class="itemAction"
+ class$="itemAction [[_computeDisabledClass(link.id, disabledIds.*)]]"
data-id$="[[link.id]]"
on-tap="_handleItemTap"
hidden$="[[link.url]]">[[link.name]]</span>
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
index d1fae7f..6ab1301 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
@@ -44,6 +44,15 @@
value: 40,
},
+ /**
+ * List the IDs of dropdown buttons to be disabled. (Note this only
+ * diisables bittons and not link entries.)
+ */
+ disabledIds: {
+ type: Array,
+ value: function() { return []; },
+ },
+
_hasAvatars: String,
},
@@ -76,7 +85,13 @@
_handleItemTap: function(e) {
var id = e.target.getAttribute('data-id');
- if (id) { this.dispatchEvent(new CustomEvent('tap-item-' + id)); }
+ if (id && this.disabledIds.indexOf(id) === -1) {
+ this.dispatchEvent(new CustomEvent('tap-item-' + id));
+ }
+ },
+
+ _computeDisabledClass: function(id, disabledIdsRecord) {
+ return disabledIdsRecord.base.indexOf(id) === -1 ? '' : 'disabled';
},
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
index 2794caf..390213c 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
@@ -80,5 +80,16 @@
MockInteractions.tap(element.$$('.itemAction'));
assert.isTrue(stub.called);
});
+
+ test('disabled non link item', function() {
+ element.items = [{name: 'item one', id: 'foo'}];
+ element.disabledIds = ['foo'];
+
+ var stub = sinon.stub();
+ element.addEventListener('tap-item-foo', stub);
+ flushAsynchronousOperations();
+ MockInteractions.tap(element.$$('.itemAction'));
+ assert.isFalse(stub.called);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
index 77e2272..86211a4 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
@@ -44,6 +44,7 @@
type: Boolean,
value: false,
},
+ removeZeroWidthSpace: Boolean,
_saveDisabled: {
computed: '_computeSaveDisabled(disabled, content, _newContent)',
type: Boolean,
@@ -58,7 +59,10 @@
_editingChanged: function(editing) {
if (!editing) { return; }
- this._newContent = this.content;
+
+ // TODO(wyatta) switch linkify sequence, see issue 5526.
+ this._newContent = this.removeZeroWidthSpace ?
+ this.content.replace(/^R=\u200B/gm, 'R=') : this.content;
},
_computeSaveDisabled: function(disabled, content, newContent) {
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
index 999f171..2515fe1 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
@@ -69,6 +69,13 @@
assert.equal(element._newContent, 'stale content');
});
+ test('zero width spaces are removed properly', function() {
+ element.removeZeroWidthSpace = true;
+ element.content = 'R=\u200Btest@google.com';
+ element.editing = true;
+ assert.equal(element._newContent, 'R=test@google.com');
+ });
+
suite('editing', function() {
setup(function() {
element.content = 'current content';
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
index 8855b0a..d719f70 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
@@ -45,6 +45,7 @@
gr-linked-text.pre {
font-family: var(--monospace-font-family);
}
+
</style>
<div id="container"></div>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index f4d99d6..e7ca50d 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -129,6 +129,7 @@
};
GrLinkTextParser.prototype.parseChunk = function(text, href) {
+ // TODO(wyatta) switch linkify sequence, see issue 5526.
if (this.removeZeroWidthSpace) {
// Remove the zero-width space added in gr-change-view.
text = text.replace(/^R=\u200B/gm, 'R=');
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index 33f33b7..b19137e 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -33,6 +33,7 @@
port = flag.String("port", ":8081", "Port to serve HTTP requests on")
prod = flag.Bool("prod", false, "Serve production assets")
scheme = flag.String("scheme", "https", "URL scheme")
+ plugins = flag.String("plugins", "", "Path to local plugins folder")
)
func main() {
@@ -49,6 +50,11 @@
http.HandleFunc("/config/", handleRESTProxy)
http.HandleFunc("/projects/", handleRESTProxy)
http.HandleFunc("/accounts/self/detail", handleAccountDetail)
+ if len(*plugins) > 0 {
+ http.Handle("/plugins/", http.StripPrefix("/plugins/",
+ http.FileServer(http.Dir(*plugins))))
+ log.Println("Local plugins at", *plugins)
+ }
log.Println("Serving on port", *port)
log.Fatal(http.ListenAndServe(*port, &server{}))
}
diff --git a/tools/gerrit.importorder b/tools/gerrit.importorder
deleted file mode 100644
index 398130e..0000000
--- a/tools/gerrit.importorder
+++ /dev/null
@@ -1,12 +0,0 @@
-#Organize Import Order
-#Mon Mar 23 17:27:34 PDT 2015
-9=javax
-8=java
-7=org
-6=net
-5=junit
-4=eu
-3=dk
-2=com
-1=com.google
-0=\#
diff --git a/tools/maven/BUILD b/tools/maven/BUILD
index 9ac46ac..d46a954 100644
--- a/tools/maven/BUILD
+++ b/tools/maven/BUILD
@@ -3,8 +3,7 @@
MAVEN_REPOSITORY = "sonatype-nexus-staging"
-# TODO(davido): support snapshot repositories
-MAVEN_RELEASE_URL = "https://oss.sonatype.org/service/local/staging/deploy/maven2"
+URL = "https://oss.sonatype.org/content/repositories/snapshots" if GERRIT_VERSION.endswith("-SNAPSHOT") else "https://oss.sonatype.org/service/local/staging/deploy/maven2"
maven_package(
src = {
@@ -26,7 +25,7 @@
"gerrit-plugin-gwtui": "//gerrit-plugin-gwtui:gwtui-api_deploy.jar",
},
repository = MAVEN_REPOSITORY,
- url = MAVEN_RELEASE_URL,
+ url = URL,
version = GERRIT_VERSION,
war = {"gerrit-war": "//:release"},
)
diff --git a/tools/setup_gjf.sh b/tools/setup_gjf.sh
index 443f1ee..0cf2f3f 100755
--- a/tools/setup_gjf.sh
+++ b/tools/setup_gjf.sh
@@ -17,8 +17,8 @@
set -eu
# Keep this version in sync with dev-contributing.txt.
-VERSION="1.2"
-SHA1="b3614824e178dd918dc2cc7c7b54804200ebfc64"
+VERSION="1.3"
+SHA1="a73cfe6f9af01bd6ff150c0b50c9d620400f784c"
root="$(git rev-parse --show-toplevel)"
if [[ -z "$root" ]]; then