{"thread":{"id":"36381","subject":"[PATCH] Add support for commit attributes","startedAt":"2014-04-09T19:38:15Z","lastAt":"2014-04-10T18:47:26Z","messageCount":8,"participants":["Diego Lago","Duy Nguyen","Diego Lago González","Junio C Hamano","Felipe Contreras","Max Horn"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"238601","messageId":"1397072295-7670-1-git-send-email-diego.lago.gonzalez@gmail.com","threadId":"36381","inReplyTo":null,"subject":"[PATCH] Add support for commit attributes","fromName":"Diego Lago","fromEmail":"diego.lago.gonzalez@gmail.com","sentAt":"2014-04-09T19:38:15Z","receivedAt":"2014-04-09T19:38:15Z","isPatch":true,"sender":{"key":"diego.lago.gonzalez@gmail.com","avatar":null},"body":"Commit attributes are custom commit extra headers the user can\nadd to the commit object.\n\nThe motivation for this patch is that in my company we have a custom\ncontinuous integration software that uses a custom formatted commit\nmessage (currently in YALM format) to show several information into\nour CI server front-end.\n\nBut this YALM-based commit message pollutes the commit object not being\nhuman readable, so a good form of achieve the YALM's behaviour (without\nusing YALM nor any other structured language) is to add custom attributes\nto the commit object itself.\n\nFor example, in our CI server we show the risk of the change (that can\nbe low, medium or high); we, as said before, add this information by putting\nYALM code inside the commit message, but the problem is that this message\nis not human readable.\n\nTo solve this in Git, we can use this patch to add a commit attribute\nindicating the risk of the commit out of the commit message, so that commit\nmessage can be a normal message (git notes would be a good approach, but this\ninformation is not inside of the commit object and in my company we need the\ncommit carries the whole information).\n\nWe could achieve this behaviour using git notes, but this approach implies\nusing two operations per commit, and we have found with complaints from users.\n\nFor example, to do a commit with 'risk' attribute, we would do:\n\n$ edit file.txt\n$ git add file.txt\n$ git commit -m \"Commit message.\" --attr risk=low file.txt\n\nAttributes are not shown in normal git log/show. They must be obtained in\nan explicit way. For example, to show the previous attribute, we would do:\n\n$ git log -1 --format=\"%A(risk)\"\nlow\n$ git log -1 --format=\"%A(not_found)\"\n%A(not_found)\n$ git log -1 --format=\"%A?(not_found)\"\n\n$\n\n\nDiego Lago (1):\n  Add support for commit attributes\n\n Documentation/git-commit.txt     |   12 ++-\n Documentation/pretty-formats.txt |   17 ++++\n builtin/commit.c                 |  200 ++++++++++++++++++++++++++++++++++++++\n pretty.c                         |   41 ++++++++\n t/t2400-commit-attributes.sh     |  187 +++++++++++++++++++++++++++++++++++\n 5 files changed, 456 insertions(+), 1 deletion(-)\n create mode 100755 t/t2400-commit-attributes.sh\n\n-- \n1.7.9.5\n\nFrom 5d63d32fc94c5824e6189092065160eaf075ff5e Mon Sep 17 00:00:00 2001\nFrom: Diego Lago <diego.lago.gonzalez@gmail.com>\nDate: Tue, 8 Apr 2014 17:29:48 +0200\nSubject: [PATCH] Add support for commit attributes\n\nCommit objects support extended attributes using '-A' or '--attr' flag to\nadd them with 'git commit' command. Also, commit attributes are obtained\nusing '%A(...)' format in git log and git show command. Examples:\n\ngit commit -m \"Commit message.\" --attr name=value\ngit log -1 --format=\"%A(name)\" # Shows 'value'.\n\nIn addition, documentation and tests have been added.\n\nSigned-off-by: Diego Lago <diego.lago.gonzalez@gmail.com>\n---\n Documentation/git-commit.txt     |   12 ++-\n Documentation/pretty-formats.txt |   17 ++++\n builtin/commit.c                 |  200 ++++++++++++++++++++++++++++++++++++++\n pretty.c                         |   41 ++++++++\n t/t2400-commit-attributes.sh     |  187 +++++++++++++++++++++++++++++++++++\n 5 files changed, 456 insertions(+), 1 deletion(-)\n create mode 100755 t/t2400-commit-attributes.sh\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 429267a..7967eca 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n \t   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n-\t   [-i | -o] [-S[<keyid>]] [--] [<file>...]\n+\t   [(-A | --attr) <key=value>] [-i | -o] [-S[<keyid>]] [--] [<file>...]\n \n DESCRIPTION\n -----------\n@@ -304,6 +304,16 @@ configuration variable documented in linkgit:git-config[1].\n \tcommit message template when using an editor to prepare the\n \tdefault commit message.\n \n+-A <key=value>::\n+--attr=<key=value>::\n+\tAdd an attribute (extra header) to the commit object. The attribute form\n+\tis a 'key=value' pair and neither the key nor the value cannot be empty (if\n+\tthe commit is an amend commit, value can be empty). You can add as many\n+\tattributes as you want and these attributes can be shown either by\n+\tlinkgit:git-log[1] or linkgit:git-show[1]. If used with --amend option,\n+\tduplicated attributes are replaced and if attributes have an empty value,\n+\tthey are removed from the commit object.\n+\n -S[<keyid>]::\n --gpg-sign[=<keyid>]::\n \tGPG-sign commit.\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex 1d174fd..23d54b6 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -134,6 +134,8 @@ The placeholders are:\n - '%b': body\n - '%B': raw body (unwrapped subject and body)\n - '%N': commit notes\n+- '%A[?](...)': commit attribute (specifying attribute name inside brackets).\n+  If used with '?', the placeholder is not shown if attribute is not found.\n - '%GG': raw verification message from GPG for a signed commit\n - '%G?': show \"G\" for a Good signature, \"B\" for a Bad signature, \"U\" for a good,\n   untrusted signature and \"N\" for no signature\n@@ -197,6 +199,21 @@ If you add a ` ` (space) after '%' of a placeholder, a space\n is inserted immediately before the expansion if and only if the\n placeholder expands to a non-empty string.\n \n+* 'Commit attributes'\n++\n+In order to show commit attributes (extra headers), you have to put the name\n+of the attribute inside brackets of the %A placeholder. For example:\n++\n+---------------------\n+$ git commit --attr \"example_attr=The attribute value.\" -m \"Commit message.\"\n+$ git show --format=\"%h %s -- Example attribute: %A(example_attr) [%an]\"\n+4dabe05 Commit message. -- Example attribute: The attribute value. [John Doe]\n+$ git show --format=\"%h %s -- Not found: %A(not_found_attr) [%an]\"\n+4dbbe05 Commit message. -- Not found: %A(not_found_attr). [John Doe]\n+$ git show --format=\"%h %s -- Not found: %A?(not_found_attr). [%an]\"\n+4dbbe05 Commit message. -- Not found: . [John Doe]\n+---------------------\n+\n * 'tformat:'\n +\n The 'tformat:' format works exactly like 'format:', except that it\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex d9550c5..3ba4995 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -103,6 +103,9 @@ static int no_post_rewrite, allow_empty_message;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg;\n static char *sign_commit;\n \n+/* Commit attributes (commit extra headers) */\n+static struct commit_extra_header *commit_attrs = NULL;\n+\n /*\n  * The default commit message cleanup mode will remove the lines\n  * beginning with # (shell comments) and leading and trailing\n@@ -1479,6 +1482,192 @@ int run_commit_hook(int editor_is_used, const char *index_file, const char *name\n \treturn ret;\n }\n \n+/* Removes the given commit extra header returning its parent. */\n+static struct commit_extra_header *remove_commit_extra_header(\n+\tstruct commit_extra_header **headers, struct commit_extra_header *to_remove)\n+{\n+\tif (!headers || !*headers)\n+\t\treturn NULL;\n+\n+\tstruct commit_extra_header *r = *headers, *parent = NULL;\n+\twhile(r != NULL) {\n+\t\tif (r == to_remove) {\n+\t\t\tif (parent == NULL) {\n+\t\t\t\t*headers = r->next;\n+\t\t\t} else {\n+\t\t\t\tparent->next = r->next;\n+\t\t\t}\n+\t\t\tfree(to_remove->key);\n+\t\t\tfree(to_remove->value);\n+\t\t\tfree(to_remove);\n+\t\t\tbreak;\n+\t\t}\n+\t\tparent = r;\n+\t\tr = r->next;\n+\t}\n+\treturn parent != NULL ? parent : *headers;\n+}\n+\n+/* Check for duplicated/empty attributes. */\n+static void process_commit_attrs(struct commit_extra_header **attrs,\n+\t\t\t\tstruct commit_extra_header **new_attrs, int is_amend)\n+{\n+\t/*\n+\t * Check first if there are duplicated attributes from the attributes\n+\t * to be appended (new_attrs).\n+\t */\n+\tstruct commit_extra_header *r = *new_attrs, *r2 = *new_attrs;\n+\twhile(r != NULL) {\n+\t\tr2 = *new_attrs;\n+\t\twhile(r2 != NULL) {\n+\t\t\tif (r != r2 && strcmp(r->key, r2->key) == 0) {\n+\t\t\t\tdie(_(\"Cannot add duplicated attributes to a commit: %s\"),\n+\t\t\t\t\tr->key);\n+\t\t\t}\n+\t\t\tr2 = r2->next;\n+\t\t}\n+\t\tr = r->next;\n+\t}\n+\n+\t/*\n+\t * Now, check if there are duplicates for amended commits. Then, replace\n+\t * if they have a value or remove if they have an empty value.\n+\t */\n+\tif (is_amend) {\n+\t\tr = *attrs, r2 = *new_attrs;\n+\t\twhile(r != NULL) {\n+\t\t\tr2 = *new_attrs;\n+\t\t\twhile(r2 != NULL) {\n+\t\t\t\tif (r != r2 && strcmp(r->key, r2->key) == 0) {\n+\t\t\t\t\tstruct commit_extra_header *to_remove = r2;\n+\t\t\t\t\tif (r2->len == 0) {\n+\t\t\t\t\t\t/* If attribute is 'key=', remove it from main attrs. */\n+\t\t\t\t\t\tr = remove_commit_extra_header(attrs, r);\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tfree(r->value);\n+\t\t\t\t\t\tr->value = xmalloc(r2->len);\n+\t\t\t\t\t\tmemcpy(r->value, r2->value, r2->len);\n+\t\t\t\t\t\tr->len = r2->len;\n+\t\t\t\t\t}\n+\t\t\t\t\tr2 = r2->next;\n+\t\t\t\t\tremove_commit_extra_header(new_attrs, to_remove);\n+\t\t\t\t} else {\n+\t\t\t\t\tr2 = r2->next;\n+\t\t\t\t}\n+\t\t\t}\t// while r2\n+\t\t\tif (!r)\n+\t\t\t\tbreak;\n+\t\t\tr = r->next;\n+\t\t}\t// while r\n+\t}\n+}\n+\n+/* Checks if there are any commit extra header with an empty value. */\n+static void check_for_empty_attrs(struct commit_extra_header *attrs)\n+{\n+\tstruct commit_extra_header *r = attrs;\n+\n+\twhile(r != NULL) {\n+\t\tif (r->len == 0)\n+\t\t\tdie(_(\"Cannot add an empty attribute to a commit: %s\"), r->key);\n+\t\tr = r->next;\n+\t}\n+}\n+\n+/* Append attrs to current_attrs. */\n+static void append_commit_attrs(struct commit_extra_header **current_attrs,\n+\t\t\t\tstruct commit_extra_header *attrs)\n+{\n+\tif(*current_attrs == NULL) {\n+\t\t*current_attrs = attrs;\n+\t\treturn;\n+\t}\n+\n+\tstruct commit_extra_header *r = *current_attrs;\n+\n+\twhile(r->next != NULL)\n+\t\tr = r->next;\n+\n+\tr->next = attrs;\n+}\n+\n+static void check_disallowed_attribute_names(const char *attr_name)\n+{\n+\tconst char *disallowed_names[] = { \"author\", \"committer\", \"encoding\",\n+\t\t\"gpgsig\", \"mergetag\", \"parent\", \"tree\" };\n+\n+\tint i;\n+\tfor (i = 0; i < ARRAY_SIZE(disallowed_names); i++) {\n+\t\tif (strcmp(attr_name, disallowed_names[i]) == 0) {\n+\t\t\tdie(_(\"Invalid commit attribute name: %s\"), attr_name);\n+\t\t}\n+\t}\n+}\n+\n+/* Parses -A/--attr value of the form 'key=value'. */\n+static int parse_attr_option_callback(const struct option *option,\n+\t\t\t\tconst char *arg, int unset)\n+{\n+\tunsigned short key_len = 0;\n+\tunsigned short value_len = 0;\n+\tshort equal_found = 0;\n+\tshort invalid_char_in_name = 0;\n+\tshort invalid_char_in_value = 0;\n+\n+\tint i;\n+\tfor (i = 0; arg[i] != '\\0'; i++) {\n+\t\tif (arg[i] == '=') {\n+\t\t\tequal_found = 1;\n+\t\t\tif (invalid_char_in_name) {\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t} else if (!equal_found && (arg[i] == ' ' || arg[i] == '\\n')) {\n+\t\t\tinvalid_char_in_name = 1;\n+\t\t\tkey_len++;\n+\t\t} else if (equal_found && (arg[i] == '\\n')) {\n+\t\t\tinvalid_char_in_value = 1;\n+\t\t\tvalue_len++;\n+\t\t} else {\n+\t\t\tif(equal_found) {\n+\t\t\t\tvalue_len++;\n+\t\t\t} else {\n+\t\t\t\tkey_len++;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif(!equal_found || key_len == 0) {\n+\t\tdie(_(\"Invalid commit attribute format (must be 'key=value'): %s\"), arg);\n+\t}\n+\n+\tstruct commit_extra_header *attr = xmalloc(sizeof(*attr));\n+\tattr->next = NULL;\n+\tattr->key = xmalloc(key_len + 1);\n+\tmemcpy(attr->key, arg, key_len);\n+\tattr->key[key_len] = '\\0';\n+\tif (invalid_char_in_name) {\n+\t\tdie(_(\"Invalid character in commit attribute name: %s\"), attr->key);\n+\t}\n+\n+\tcheck_disallowed_attribute_names(attr->key);\n+\n+\tattr->value = xmalloc(value_len);\n+\tmemcpy(attr->value, arg + key_len + 1, value_len);\n+\tattr->len = value_len;\n+\n+\tif (invalid_char_in_value) {\n+\t\tdie(_(\"Invalid character in commit attribute value: %s\"), attr->value);\n+\t}\n+\n+\tif (!amend && value_len == 0) {\n+\t\tdie(_(\"Commit attribute value cannot be empty if not amend.\"));\n+\t}\n+\n+\tappend_commit_attrs(&commit_attrs, attr);\n+\n+\treturn 0;\n+}\n+\n int cmd_commit(int argc, const char **argv, const char *prefix)\n {\n \tstatic struct wt_status s;\n@@ -1526,6 +1715,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"amend\", &amend, N_(\"amend previous commit\")),\n \t\tOPT_BOOL(0, \"no-post-rewrite\", &no_post_rewrite, N_(\"bypass post-rewrite hook\")),\n \t\t{ OPTION_STRING, 'u', \"untracked-files\", &untracked_files_arg, N_(\"mode\"), N_(\"show untracked files, optional modes: all, normal, no. (Default: all)\"), PARSE_OPT_OPTARG, NULL, (intptr_t)\"all\" },\n+\t\tOPT_CALLBACK('A', \"attr\", NULL, N_(\"key=value\"), N_(\"add a commit attribute\"), &parse_attr_option_callback),\n \t\t/* end commit contents options */\n \n \t\tOPT_HIDDEN_BOOL(0, \"allow-empty\", &allow_empty,\n@@ -1659,6 +1849,16 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tappend_merge_tag_headers(parents, &tail);\n \t}\n \n+\t/* Check for duplicated commit attributes on extra and commit_attrs.\n+\t   Replace them if we are amending the commit. */\n+\tprocess_commit_attrs(&extra, &commit_attrs, amend);\n+\n+\t/* Append extra attributes (supplied with --attr or -A). */\n+\tappend_commit_attrs(&extra, commit_attrs);\n+\n+\t/* Check for empty attributes again (may come from amend). */\n+\tcheck_for_empty_attrs(extra);\n+\n \tif (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,\n \t\t\t\t author_ident.buf, sign_commit, extra)) {\n \t\trollback_index_files();\ndiff --git a/pretty.c b/pretty.c\nindex 3c43db5..8f4dc95 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1081,6 +1081,45 @@ static size_t parse_padding_placeholder(struct strbuf *sb,\n \treturn 0;\n }\n \n+static size_t parse_commit_attribute(struct strbuf *sb, /* in UTF-8 */\n+\t\t\t  const char *placeholder,\n+\t\t\t  struct format_commit_context *c,\n+\t\t\t  const struct commit *commit)\n+{\n+\tunsigned short offset = 1;\n+\tunsigned short hide_if_not_found = 0;\n+\n+\tif (placeholder[offset] == '?') {\n+\t\toffset++;\n+\t\thide_if_not_found = 1;\n+\t}\n+\n+\tif (placeholder[offset] == '(') {\n+\t\tconst char *start = placeholder + offset + 1;\n+\t\tconst char *end = strchr(start, ')');\n+\n+\t\tif (!end || start == end)\n+\t\t\treturn 0;\n+\n+\t\tconst size_t len = end - start;\n+\n+\t\tchar *attr_name = xmalloc(len + 1);\n+\t\tmemcpy(attr_name, start, len);\n+\t\tattr_name[len] = '\\0';\n+\n+\t\tconst char *msg = commit->buffer;\n+\t\tchar *header = get_header(commit, msg, attr_name);\n+\n+\t\tif (header) {\n+\t\t\tstrbuf_addstr(sb, header);\n+\t\t\treturn len + offset + 2;\n+\t\t} else {\n+\t\t\treturn hide_if_not_found ? len + offset + 2 : 0;\n+\t\t}\n+\t} else\n+\t\treturn 0;\n+}\n+\n static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\t\t\tconst char *placeholder,\n \t\t\t\tvoid *context)\n@@ -1240,6 +1279,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\t\treturn 1;\n \t\t}\n \t\treturn 0;\n+\tcase 'A':\n+\t\treturn parse_commit_attribute(sb, placeholder, c, commit);\n \t}\n \n \tif (placeholder[0] == 'G') {\ndiff --git a/t/t2400-commit-attributes.sh b/t/t2400-commit-attributes.sh\nnew file mode 100755\nindex 0000000..8993322\n--- /dev/null\n+++ b/t/t2400-commit-attributes.sh\n@@ -0,0 +1,187 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2014 Diego Lago González\n+# <diego.lago.gonzalez@gmail.com>\n+#\n+\n+test_description='git commit attributes (--attr option)\n+\n+This script test the commit attributes feature with\n+command git commit --attr <key=value>.'\n+\n+. ./test-lib.sh\n+\n+count=1\n+\n+test_expect_success 'git commit --attr key=value' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --attr key=value -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit -A key=value' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit -A key=value -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr=key=value' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --attr=key=value -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr with utf8 key' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --attr key→=value -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr with utf8 value' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --attr key=valu€ -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr with a long value' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --attr \"key=Long message for an attribute.\" -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr with a very long value' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --attr \"key=Very long message for a commit attribute. This is a very long message to check if commit attributes (extra headers for commit objects) work as expected.\" -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr key= (no value)' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+test_must_fail git commit --attr key= -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr = (no key and no value)' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+test_must_fail git commit --attr = -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr key (no value and no = sign)' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+test_must_fail git commit --attr key -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr =value (no key with value)' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+test_must_fail git commit --attr =value -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr \"key =value\" (key with space)' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+test_must_fail git commit --attr \"key =value\" -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr: key with invalid chars' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+test_must_fail git commit --attr \"key\n+=value\" -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --attr: value with invalid chars' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+test_must_fail git commit --attr \"key=value\n+with invalid\n+chars\" -m \"Commit message.\"\n+'\n+\n+test_expect_success 'git commit --amend: new attribute on amend' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --amend --attr \"new_key=new value\" -m \"New attribute on amend.\"\n+'\n+\n+test_expect_success 'git commit --amend: replace attribute on amend' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --amend --attr \"new_key=replaced value\" -m \"Replaced on amend.\"\n+'\n+\n+test_expect_success 'git commit --amend: remove attribute on amend' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --amend --attr \"new_key=\" -m \"Removed on amend.\"\n+'\n+\n+test_expect_success 'git log -1: get attribute' '\n+echo test_$count > test &&\n+count=$(($count+1)) &&\n+git add test &&\n+git commit --attr \"key=testthis\" -m \"Commit message.\" &&\n+test $(git log -1 --format=\"%A(key)\") = \"testthis\"\n+'\n+\n+test_expect_success 'git log -1: get attribute (key not found)' '\n+test_must_fail test $(git log -1 --format=\"%A(key_invalid)\") = \"testthis\"\n+'\n+\n+test_expect_success 'git log -1: get attribute (text of key not found)' '\n+test $(git log -1 --format=\"%A(key_invalid)\") = \"%A(key_invalid)\"\n+'\n+\n+test_expect_success 'git log -1: get attribute (invalid attribute name)' '\n+test_must_fail test $(git log -1 --format=\"%A(key)\") = \"testthis_invalid\"\n+'\n+\n+test_expect_success 'git log -1: get attribute (key not found but hidden)' '\n+test \"$(git log -1 --format=\"%A?(key_invalid)\")\" = \"\"\n+'\n+\n+test_expect_success 'git log -1: get attribute (key with space)' '\n+test \"$(git log -1 --format=\"% A(key)\")\" = \" testthis\"\n+'\n+\n+test_expect_success 'git log -1: get attribute (key -hidden- and space)' '\n+test \"$(git log -1 --format=\"% A?(key)\")\" = \" testthis\"\n+'\n+\n+test_expect_success 'git log -1: get attribute (key not found but hidden -no space-)' '\n+test \"$(git log -1 --format=\"% A?(key_invalid)\")\" = \"\"\n+'\n+\n+test_expect_success 'git commit --amend: check for replaced attribute' '\n+git commit --amend --attr key=testthat -m \"Amend: replace attribute.\"\n+test_must_fail test \"$(git log -1 --format=\"%A(key)\")\" = \"testthis\"\n+test \"$(git log -1 --format=\"%A(key)\")\" = \"testthat\"\n+'\n+\n+test_expect_success 'git commit --amend: check for removed attribute' '\n+git commit --amend --attr key= -m \"Amend: replace attribute.\"\n+test_must_fail test \"$(git log -1 --format=\"%A(key)\")\" = \"testthis\"\n+test \"$(git log -1 --format=\"%A(key)\")\" = \"%A(key)\"\n+test \"$(git log -1 --format=\"%A?(key)\")\" = \"\"\n+'\n+\n+test_done\n-- \n1.7.9.5\n"},{"id":"238613","messageId":"CACsJy8BJw3+=vSHzfBYigoK6ejt-DNHJPTcOWS3Nv=zxpF1f7g@mail.gmail.com","threadId":"36381","inReplyTo":"1397072295-7670-1-git-send-email-diego.lago.gonzalez@gmail.com","subject":"Re: [PATCH] Add support for commit attributes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-04-10T04:25:35Z","receivedAt":"2014-04-10T04:25:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Apr 10, 2014 at 2:38 AM, Diego Lago\n<diego.lago.gonzalez@gmail.com> wrote:\n> Commit attributes are custom commit extra headers the user can\n> add to the commit object.\n>\n> The motivation for this patch is that in my company we have a custom\n> continuous integration software that uses a custom formatted commit\n> message (currently in YALM format) to show several information into\n> our CI server front-end.\n>\n> But this YALM-based commit message pollutes the commit object not being\n> human readable, so a good form of achieve the YALM's behaviour (without\n> using YALM nor any other structured language) is to add custom attributes\n> to the commit object itself.\n>\n> For example, in our CI server we show the risk of the change (that can\n> be low, medium or high); we, as said before, add this information by putting\n> YALM code inside the commit message, but the problem is that this message\n> is not human readable.\n\nIf the problem is polluting human eyes, wouldn't it be better to make\ngit-log to filter it out? For example, we could tell git that all\nfields (in the message body) that start with X- are \"rubbish\", so\ninstead of showing \"X-something: base64 stuff...\", it shows\n\"X-something: <filtered out>\" instead? At least people will see that\nthis commit carries human-unreadable stuff.\n-- \nDuy\n"},{"id":"238616","messageId":"CAFozjsg_sta+c4=Cbrj=cfS=geOnnWic4wU_hd6=1Gaf4PAUcg@mail.gmail.com","threadId":"36381","inReplyTo":"CACsJy8BJw3+=vSHzfBYigoK6ejt-DNHJPTcOWS3Nv=zxpF1f7g@mail.gmail.com","subject":"Re: [PATCH] Add support for commit attributes","fromName":"Diego Lago González","fromEmail":"diego.lago.gonzalez@gmail.com","sentAt":"2014-04-10T06:32:42Z","receivedAt":"2014-04-10T06:32:42Z","isPatch":true,"sender":{"key":"diego.lago.gonzalez@gmail.com","avatar":null},"body":"2014-04-10 6:25 GMT+02:00 Duy Nguyen <pclouds@gmail.com>:\n> On Thu, Apr 10, 2014 at 2:38 AM, Diego Lago\n> <diego.lago.gonzalez@gmail.com> wrote:\n>> Commit attributes are custom commit extra headers the user can\n>> add to the commit object.\n>>\n>> The motivation for this patch is that in my company we have a custom\n>> continuous integration software that uses a custom formatted commit\n>> message (currently in YALM format) to show several information into\n>> our CI server front-end.\n>>\n>> But this YALM-based commit message pollutes the commit object not being\n>> human readable, so a good form of achieve the YALM's behaviour (without\n>> using YALM nor any other structured language) is to add custom attributes\n>> to the commit object itself.\n>>\n>> For example, in our CI server we show the risk of the change (that can\n>> be low, medium or high); we, as said before, add this information by putting\n>> YALM code inside the commit message, but the problem is that this message\n>> is not human readable.\n>\n> If the problem is polluting human eyes, wouldn't it be better to make\n> git-log to filter it out? For example, we could tell git that all\n> fields (in the message body) that start with X- are \"rubbish\", so\n> instead of showing \"X-something: base64 stuff...\", it shows\n> \"X-something: <filtered out>\" instead? At least people will see that\n> this commit carries human-unreadable stuff.\n> --\n> Duy\n\nWriting this data into the message, the user is forced to write it in\nthe correct format (I think is better to write key=value pairs as an\noption instead of writing as message lines with spaces in key, between\nkey and equal sign and value, and other mistakes). And is simpler to\nparse these attributes than the message itself.\n\nAnd, what if the log message is seen from the command line instead of\nour CI front-end? Why the CLI user (for example) should see\ninformation that does not need or does not want to see?\n\nCommit attributes are extra information, not the main information,\nhence this patch.\n\nPD: Sorry for the previous message not in plain text.\n\n-- \nDiego Lago González\n"},{"id":"238618","messageId":"CACsJy8Ddg99Foi89ntNQ7NB=J9qCH4hcXZ0maM7vxigfO8QVTQ@mail.gmail.com","threadId":"36381","inReplyTo":"CAFozjshu92OZbqWzcDZKN4v26Fb-7K=uJRYs4L41csrxfnSTUw@mail.gmail.com","subject":"Re: [PATCH] Add support for commit attributes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-04-10T08:05:06Z","receivedAt":"2014-04-10T08:05:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Apr 10, 2014 at 1:27 PM, Diego Lago González\n<diego.lago.gonzalez@gmail.com> wrote:\n> Writing this data into the message, the user is forced to write it in the\n> correct format (I think is better to write key=value pairs as an option\n> instead of writing as message lines with spaces in key, between key and\n> equal sign and value, and other mistakes). And is simpler to parse these\n> attributes than the message itself.\n\nthe interpret-trailers series Christian Couder is cooking in 'pu'\nshould handle this.\n\n> And, what if the log message is seen from the command line instead of our CI\n> front-end? Why the CLI user (for example) should see information that does\n> not need or does not want to see?\n\nwhich is why git-log (and all other porcelain commands) should learn\nto hide the value part (but not the key part).\n-- \nDuy\n"},{"id":"238623","messageId":"xmqqk3axxfoh.fsf@gitster.dls.corp.google.com","threadId":"36381","inReplyTo":"CACsJy8BJw3+=vSHzfBYigoK6ejt-DNHJPTcOWS3Nv=zxpF1f7g@mail.gmail.com","subject":"Re: [PATCH] Add support for commit attributes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-10T17:22:38Z","receivedAt":"2014-04-10T17:22:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> If the problem is polluting human eyes, wouldn't it be better to make\n> git-log to filter it out? For example, we could tell git that all\n> fields (in the message body) that start with X- are \"rubbish\", so\n> instead of showing \"X-something: base64 stuff...\", it shows\n> \"X-something: <filtered out>\" instead? At least people will see that\n> this commit carries human-unreadable stuff.\n\nWe had lengthy discussions in early 2010 [*1*].  The whole thread,\nat least the whole sub-thread that contains the focused message, is\na required reading to understand where we stand with respect to\n\"extra headers in commit objects\".\n\n\"Any additional information about the commit can be added\" this\npatch implements is exactly the kind of thing we want to avoid,\nwhich made Linus say in an even older discussion [*2*]:\n\n    No \"this random field could be used this random way\" crud, please.\n\nEven worse, the \"--attr\" pretends to be opaque by not defining what\neach \"attribute\" really means, but the patch hardcodes arbitrary\nrules like \"an attribute is unconditionally copied during amends\"\nand \"an attribute cannot be multi-valued\", if I read it correctly.\n\nI actually think this \"recording information about commits\" is\nexactly the use-case notes were invented to address, and if it is\nfound cumbersome to use, the reason why it is cumbersome needs to be\ndiscovered and use of notes needs to be improved.  Hooks and/or a\nwrapper around \"git commit\" to implement their custom workflow may\nbe involved as part of the solution and \"git notes\" may need to\nlearn a new trick or two along the way.\n\nI am not interested in hearing \"let's add random crud to commit\nobject header\" before \"let's improve notes so that it can be more\nsmoothly used\" is fully explored.\n\n\n[References]\n\n*1* http://thread.gmane.org/gmane.comp.version-control.git/138848/focus=138892\n\n*2* http://thread.gmane.org/gmane.comp.version-control.git/19126/focus=19149\n"},{"id":"238624","messageId":"xmqqfvllxfie.fsf@gitster.dls.corp.google.com","threadId":"36381","inReplyTo":"xmqqk3axxfoh.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH] Add support for commit attributes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-10T17:26:17Z","receivedAt":"2014-04-10T17:26:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I actually think this \"recording information about commits\" is\n> exactly the use-case notes were invented to address, and if it is\n> found cumbersome to use, the reason why it is cumbersome needs to be\n> discovered and use of notes needs to be improved.  Hooks and/or a\n> wrapper around \"git commit\" to implement their custom workflow may\n> be involved as part of the solution and \"git notes\" may need to\n> learn a new trick or two along the way.\n>\n> I am not interested in hearing \"let's add random crud to commit\n> object header\" before \"let's improve notes so that it can be more\n> smoothly used\" is fully explored.\n\nOh, I forgot to say that I do not have anything against embedding\nthe extra info as part of the free-form \"log message\" text part of\nthe commit object like you have been suggesting.  If the information\ncan be cast in stone at the commit time, that would actually be\npreferrable, as you do not have to worry about transferring notes\nseparately.\n"},{"id":"238626","messageId":"5346d4adf4f_69dfeb32f0b9@nysa.notmuch","threadId":"36381","inReplyTo":"1397072295-7670-1-git-send-email-diego.lago.gonzalez@gmail.com","subject":"RE: [PATCH] Add support for commit attributes","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2014-04-10T17:28:13Z","receivedAt":"2014-04-10T17:28:13Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Diego Lago wrote:\n> Commit attributes are custom commit extra headers the user can\n> add to the commit object.\n> \n> The motivation for this patch is that in my company we have a custom\n> continuous integration software that uses a custom formatted commit\n> message (currently in YALM format) to show several information into\n> our CI server front-end.\n\nThese attributes can be used for remote-helpers as well; to store extra\ninformation that cannot be stored otherwise in Git's data structures.\n\n-- \nFelipe Contreras\n"},{"id":"238636","messageId":"934D49AB-0E6B-4DF8-A67C-CE311561378E@quendi.de","threadId":"36381","inReplyTo":"5346d4adf4f_69dfeb32f0b9@nysa.notmuch","subject":"Re: [PATCH] Add support for commit attributes","fromName":"Max Horn","fromEmail":"max@quendi.de","sentAt":"2014-04-10T18:47:26Z","receivedAt":"2014-04-10T18:47:26Z","isPatch":true,"sender":{"key":"max@quendi.de","avatar":"https://avatars.githubusercontent.com/u/241512?v=4"},"body":"\nOn 10.04.2014, at 19:28, Felipe Contreras <felipe.contreras@gmail.com> wrote:\n\n> Diego Lago wrote:\n>> Commit attributes are custom commit extra headers the user can\n>> add to the commit object.\n>> \n>> The motivation for this patch is that in my company we have a custom\n>> continuous integration software that uses a custom formatted commit\n>> message (currently in YALM format) to show several information into\n>> our CI server front-end.\n> \n> These attributes can be used for remote-helpers as well; to store extra\n> information that cannot be stored otherwise in Git's data structures.\n\n+1 to that. This is reminds me of what Kiln Harmony does as part of their effort to enable full round-robin transfer between Git and Mercurial (a goal from which all other tools I know of still are far away). See <http://blog.fogcreek.com/kiln-harmony-internals-the-basics/>.\n\n\n\nCheers,\nMax\n"}]}