From patchwork Tue Dec 11 23:49:05 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tejun Heo X-Patchwork-Id: 10725307 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C5FB614E2 for ; Tue, 11 Dec 2018 23:49:33 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B554E2B203 for ; Tue, 11 Dec 2018 23:49:33 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A9FC72B222; Tue, 11 Dec 2018 23:49:33 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1D0E12B203 for ; Tue, 11 Dec 2018 23:49:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726225AbeLKXtc (ORCPT ); Tue, 11 Dec 2018 18:49:32 -0500 Received: from mail-qt1-f193.google.com ([209.85.160.193]:45716 "EHLO mail-qt1-f193.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726183AbeLKXtb (ORCPT ); Tue, 11 Dec 2018 18:49:31 -0500 Received: by mail-qt1-f193.google.com with SMTP id e5so18534549qtr.12 for ; Tue, 11 Dec 2018 15:49:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=AlftHj2ujftvPOoUScTIaiLEPtRGjHi1QQWqXjN43VQ=; b=R7MGAdCFfbPIi/DyXL9J8H+zQwXsBWXAbm9NpOIwQnDSxfQYyx8mTvbUWFVZBTjKVE CR+6iTep9p5h6yhfMGr3nBwLolzNbQ1UMMeg0sqgckJZhX75eyDB3WGtmOiyhNRWT7Cc DlpK+vMgDUf8LfJcwM/Hx6OE4DH5arTWIw12e9YhAKLD8ykCEsR9/j6FdHAoQC6V6D5L VnR7qspDIdFNZAIqFyzvCtDPkdwcFlmq8TPXC1tYPxTN+KXHrNXuYzU5eXU2Zy9YqcyY XVEN9JPFsH0/SeMZlWpiqxlyTQDfAsTyHMaPDl6qjNPupEBZCleHnMtTToRbxoawcMQT MxqA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references; bh=AlftHj2ujftvPOoUScTIaiLEPtRGjHi1QQWqXjN43VQ=; b=eewzGz/mE+Ow0kIvjRSMdGHnQ5+N4BDxg6LcvLdaQIkKbbLKcKhZzwCt81RjZUpXih niASqTE+G+2cUculNqrcwJCDOuc6cjSybjWgr7lEodQ5X977Xl+kGT84U2E2I+AHJAeg EQI/hi1CRBZxpjemmMmPTgtgP9/qi3HJci1Ul/V0MLPyK0Uz/7un55pORYNGPc6vv1xw KDTzXCdYKz2JnpEqO0hQVhQWc6MLSGGWo1xrEwQ1490giqJPUCrPWY3JhcV5Op67rFf8 orHsRJQpIxBcs4nK6Hr131/+kpmoFhuPu6v5iaH09J7LsjnurLeQu8CtlHMATZr9IBhM fY/Q== X-Gm-Message-State: AA+aEWbg0w2prT0zzWIG07IrADAjSu4J1D4eXnY2ughR5Axbf+HNHN7V HINbOBXoUeKFPSHUznHTYWOB8UQk X-Google-Smtp-Source: AFSGD/VZMEvaq86WAdKMScNEw2/AtANsf6JCl5v4FpVII4EztUPNNPOFFbqf7Hu/gEMXUMxaPzAEmg== X-Received: by 2002:a0c:88e6:: with SMTP id 35mr17243036qvo.61.1544572169908; Tue, 11 Dec 2018 15:49:29 -0800 (PST) Received: from localhost ([2620:10d:c091:200::7:fd8f]) by smtp.gmail.com with ESMTPSA id q38sm10108450qtj.65.2018.12.11.15.49.28 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 11 Dec 2018 15:49:29 -0800 (PST) From: Tejun Heo To: git@vger.kernel.org, Junio C Hamano , Jeff King Cc: kernel-team@fb.com, Tejun Heo Subject: [PATCH 1/5] trailer: Implement a helper to reverse-map trailer xrefs Date: Tue, 11 Dec 2018 15:49:05 -0800 Message-Id: <20181211234909.2855638-2-tj@kernel.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181211234909.2855638-1-tj@kernel.org> References: <20181211234909.2855638-1-tj@kernel.org> Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Tejun Heo Some trailers refer to other commits. Let's call them xrefs (cross-references). For example, a cherry pick trailer points to the source commit. It is sometimes useful to build a reverse map of these xrefs - ie. source -> cherry-pick instead of cherry-pick -> source. This patch implements trailer helpers to enable such reverse maps. The helpers can process arbitrary trailers and once the xrefs are recorded, the reverse-mapping can be iterated per source commit using trailer_rev_xrefs_for_each(). Signed-off-by: Tejun Heo --- trailer.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ trailer.h | 38 ++++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/trailer.c b/trailer.c index 0796f326b..15744600b 100644 --- a/trailer.c +++ b/trailer.c @@ -2,6 +2,7 @@ #include "config.h" #include "string-list.h" #include "run-command.h" +#include "object-store.h" #include "commit.h" #include "tempfile.h" #include "trailer.h" @@ -1170,3 +1171,107 @@ void format_trailers_from_commit(struct strbuf *out, const char *msg, format_trailer_info(out, &info, opts); trailer_info_release(&info); } + +implement_static_commit_slab(trailer_rxrefs_slab, struct object_array *); + +static struct object_array *get_trailer_rxrefs( + struct trailer_rev_xrefs *rxrefs, struct commit *commit) +{ + struct object_array **slot = + trailer_rxrefs_slab_peek(&rxrefs->slab, commit); + + return slot ? *slot : NULL; +} + +static struct object_array *get_create_trailer_rxrefs( + struct trailer_rev_xrefs *rxrefs, struct commit *commit) +{ + struct object_array **slot = + trailer_rxrefs_slab_at(&rxrefs->slab, commit); + + if (*slot) + return *slot; + + add_object_array(&commit->object, oid_to_hex(&commit->object.oid), + &rxrefs->dst_commits); + *slot = xmalloc(sizeof(struct object_array)); + **slot = (struct object_array)OBJECT_ARRAY_INIT; + return *slot; +} + +void trailer_rev_xrefs_init(struct trailer_rev_xrefs *rxrefs, + const char *trailer_prefix) +{ + rxrefs->trailer_prefix = xstrdup(trailer_prefix); + rxrefs->trailer_prefix_len = strlen(trailer_prefix); + init_trailer_rxrefs_slab(&rxrefs->slab); + rxrefs->dst_commits = (struct object_array)OBJECT_ARRAY_INIT; +} + +/* record the reverse mapping of @commit's xref trailer */ +void trailer_rev_xrefs_record(struct trailer_rev_xrefs *rxrefs, + struct commit *commit) +{ + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + enum object_type type; + unsigned long size; + void *buffer; + struct trailer_info info; + int i; + + buffer = read_object_file(&commit->object.oid, &type, &size); + trailer_info_get(&info, buffer, &opts); + + /* when nested, the last trailer describes the latest cherry-pick */ + for (i = info.trailer_nr - 1; i >= 0; i--) { + char *line = info.trailers[i]; + + if (starts_with(line, rxrefs->trailer_prefix)) { + struct object_id dst_oid; + struct object *dst_object; + struct commit *dst_commit; + struct object_array *src_objs; + char cherry_hex[GIT_MAX_HEXSZ + 1]; + + if (get_oid_hex(line + rxrefs->trailer_prefix_len, + &dst_oid)) + continue; + + dst_object = parse_object(the_repository, &dst_oid); + if (!dst_object || dst_object->type != OBJ_COMMIT) + continue; + + dst_commit = (struct commit *)dst_object; + src_objs = get_create_trailer_rxrefs(rxrefs, dst_commit); + + oid_to_hex_r(cherry_hex, &commit->object.oid); + add_object_array(&commit->object, cherry_hex, src_objs); + break; + } + } + + free(buffer); +} + +void trailer_rev_xrefs_release(struct trailer_rev_xrefs *rxrefs) +{ + clear_trailer_rxrefs_slab(&rxrefs->slab); + object_array_clear(&rxrefs->dst_commits); + free(rxrefs->trailer_prefix); +} + +void trailer_rev_xrefs_next(struct trailer_rev_xrefs *rxrefs, int *idx_p, + struct commit **dst_commit_p, + struct object_array **src_objs_p) +{ + if (*idx_p >= rxrefs->dst_commits.nr) { + *dst_commit_p = NULL; + *src_objs_p = NULL; + return; + } + + *dst_commit_p = (struct commit *) + rxrefs->dst_commits.objects[*idx_p].item; + *src_objs_p = get_trailer_rxrefs(rxrefs, *dst_commit_p); + (*idx_p)++; +} diff --git a/trailer.h b/trailer.h index b99773964..07090a11f 100644 --- a/trailer.h +++ b/trailer.h @@ -2,6 +2,8 @@ #define TRAILER_H #include "list.h" +#include "object.h" +#include "commit-slab.h" struct strbuf; @@ -99,4 +101,40 @@ void trailer_info_release(struct trailer_info *info); void format_trailers_from_commit(struct strbuf *out, const char *msg, const struct process_trailer_options *opts); +/* + * Helpers to reverse trailers referencing to other commits. + * + * Some trailers, e.g. "(cherry picked from...)", references other commits. + * The following helpers can be used to reverse map those references. See + * builtin/reverse-trailer-xrefs.c for a usage example. + */ +declare_commit_slab(trailer_rxrefs_slab, struct object_array *); + +struct trailer_rev_xrefs { + char *trailer_prefix; + int trailer_prefix_len; + struct trailer_rxrefs_slab slab; + struct object_array dst_commits; +}; + +void trailer_rev_xrefs_init(struct trailer_rev_xrefs *rxrefs, + const char *trailer_prefix); +void trailer_rev_xrefs_record(struct trailer_rev_xrefs *rxrefs, + struct commit *commit); +void trailer_rev_xrefs_release(struct trailer_rev_xrefs *rxrefs); + +void trailer_rev_xrefs_next(struct trailer_rev_xrefs *rxrefs, + int *idx_p, struct commit **dst_commit_p, + struct object_array **src_objs_p); + +/* + * Iterate the recorded reverse mappings - @dst_commit was pointed to by + * commits in @src_objs. + */ +#define trailer_rev_xrefs_for_each(rxrefs, idx, dst_commit, src_objs) \ + for ((idx) = 0, \ + trailer_rev_xrefs_next(rxrefs, &(idx), &(dst_commit), &(src_objs));\ + (dst_commit); \ + trailer_rev_xrefs_next(rxrefs, &(idx), &(dst_commit), &(src_objs))) + #endif /* TRAILER_H */ From patchwork Tue Dec 11 23:49:06 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tejun Heo X-Patchwork-Id: 10725309 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C082B1751 for ; Tue, 11 Dec 2018 23:49:37 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id AE5042B203 for ; Tue, 11 Dec 2018 23:49:37 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A2AD02B222; Tue, 11 Dec 2018 23:49:37 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 6B3422B203 for ; Tue, 11 Dec 2018 23:49:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726230AbeLKXtf (ORCPT ); Tue, 11 Dec 2018 18:49:35 -0500 Received: from mail-qt1-f172.google.com ([209.85.160.172]:41010 "EHLO mail-qt1-f172.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726183AbeLKXtd (ORCPT ); Tue, 11 Dec 2018 18:49:33 -0500 Received: by mail-qt1-f172.google.com with SMTP id d18so18563697qto.8 for ; Tue, 11 Dec 2018 15:49:33 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=ThpFVe+gpyhdsQf3kolYFImGfjyCYdur5lE+CcfoR+4=; b=OrKPDi1B6lmW+aWoQPgacrvcnhFKYoUSReJMma5M8UgwRTy6ticTeLhHabMHa7Yzq5 Y9rDpSDNbJY65/YWpG4p7dpGVBHjIUkkC8dxP1/N2AmQ0ohJF4GAdbdWBbOY1UV64jgt FE432FxRotZwqtZUdet6U+NMYGl3gIPnhjnG3DJS4KLJs1w8NQ/l888Y5if3U0PkjFVV ICalwAsDDdTsV5DMM+aDyziPTHUejEPrSXv8o4uen9k0RGBT3Ym2Fzx1F4PWpTbpfTeP CBnVcHb8I7L7FtFmu2lFNLMulnp/tUUeHyTAw/IthBXTm06GvQlVUNlu1rCqnbGEqwZf A0iw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references; bh=ThpFVe+gpyhdsQf3kolYFImGfjyCYdur5lE+CcfoR+4=; b=A6aKCTzPwIf+COgZYmnRgqy3unP7ZodIDyqsl6k8pkfVX+/f8bvCJz0Zt1ba8HRDnR P4rlpYuMH/ujXNR1tDwVj+AzJ3puBiVAGX8RAKMHLtveZ2riPQfTS05gCeOSSfA7O7DR rWvJvxBrmrFW2KqfTC1C75AZK/jBdytXW/PPPco96mzgFcvuFCIxys4Tu+ohdm6STMje w83/cOnKv0w5D+1aV2987Popl/Sxr7cMKPL3EgX0fIq6rZs1y/XzfQRqGxKkzn6UyO5l kKMscE8qVzP2aCH1uycf0KMLedaE40TWvzHJI6/wDTmrDCVZsuhhyQZ9hU6llEsgZsVp lhdA== X-Gm-Message-State: AA+aEWZlT32mj7UFsrdoidXziTh8eng19ImE2O4axCSBA7r37GgF1Kg+ QR/IkoPdaUGp1Gd1XIn55DG/E+LI X-Google-Smtp-Source: AFSGD/XklddEnMGh/QUBrW6wHLtGj+uQk34Ixwn7pmU2y0eGo4rrLnjucaDTTW3aHwVrSeskpgXteQ== X-Received: by 2002:a0c:f787:: with SMTP id s7mr17271081qvn.167.1544572172252; Tue, 11 Dec 2018 15:49:32 -0800 (PST) Received: from localhost ([2620:10d:c091:200::7:fd8f]) by smtp.gmail.com with ESMTPSA id s36sm11869645qth.17.2018.12.11.15.49.31 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 11 Dec 2018 15:49:31 -0800 (PST) From: Tejun Heo To: git@vger.kernel.org, Junio C Hamano , Jeff King Cc: kernel-team@fb.com, Tejun Heo Subject: [PATCH 2/5] notes: Implement special handlings for refs/notes/xref- Date: Tue, 11 Dec 2018 15:49:06 -0800 Message-Id: <20181211234909.2855638-3-tj@kernel.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181211234909.2855638-1-tj@kernel.org> References: <20181211234909.2855638-1-tj@kernel.org> Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Tejun Heo Some trailers refer to other commits. Let's call them xrefs (cross-references). For example, a cherry pick trailer points to the source commit. It is sometimes useful to build a reverse map of these xrefs - ie. source -> cherry-pick instead of cherry-pick -> source. These reverse-maps will be recorded in special notes whose refs start with refs/notes/xref-. This patch implements the following special handling for the xref notes. * When xref notes are appended to an existing one, both parts get parsed and dead or dupliate references are dropped, so that the merged note contains only valid and unique xrefs. * When xref notes are formatted for printing, the formatter recurses into each xref and prints the nested xrefs with increasing indentation to show the comprehensive xref chains. The latter part will be documented by a future patch with the actual use case. Signed-off-by: Tejun Heo --- Documentation/git-notes.txt | 8 ++ notes-merge.c | 9 ++ notes-utils.c | 2 + notes-utils.h | 3 +- notes.c | 260 +++++++++++++++++++++++++++++++++++- notes.h | 9 ++ 6 files changed, 288 insertions(+), 3 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index df2b64dbb..872919ad4 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -88,6 +88,10 @@ the command can read the input given to the `post-rewrite` hook.) Append to the notes of an existing object (defaults to HEAD). Creates a new notes object if needed. + If the notes ref starts with `/refs/notes/xref-`, each + line is expected to contain `: ` and + only lines with reachable unique commit IDs are kept. + edit:: Edit the notes for a given object (defaults to HEAD). @@ -273,6 +277,10 @@ Note that if either the local or remote version contain duplicate lines prior to the merge, these will also be removed by this notes merge strategy. +"cat_xrefs" is similar to "union" but expects each line to contain +`: `. Only lines with reachable unique commit IDs are +kept. + EXAMPLES -------- diff --git a/notes-merge.c b/notes-merge.c index bd05d50b0..3fe2389a1 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -464,6 +464,15 @@ static int merge_one_change(struct notes_merge_options *o, die("failed to concatenate notes " "(combine_notes_cat_sort_uniq)"); return 0; + case NOTES_MERGE_RESOLVE_CAT_XREFS: + if (o->verbosity >= 2) + printf("Concatenating unique and valid cross-references " + "in local and remote notes for %s\n", + oid_to_hex(&p->obj)); + if (add_note(t, &p->obj, &p->remote, combine_notes_cat_xrefs)) + die("failed to concatenate notes " + "(combine_notes_cat_xrefs)"); + return 0; } die("Unknown strategy (%i).", o->strategy); } diff --git a/notes-utils.c b/notes-utils.c index 14ea03178..db6363c39 100644 --- a/notes-utils.c +++ b/notes-utils.c @@ -70,6 +70,8 @@ int parse_notes_merge_strategy(const char *v, enum notes_merge_strategy *s) *s = NOTES_MERGE_RESOLVE_UNION; else if (!strcmp(v, "cat_sort_uniq")) *s = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ; + else if (!strcmp(v, "cat_xrefs")) + *s = NOTES_MERGE_RESOLVE_CAT_XREFS; else return -1; diff --git a/notes-utils.h b/notes-utils.h index 540830652..b604f9b51 100644 --- a/notes-utils.h +++ b/notes-utils.h @@ -28,7 +28,8 @@ enum notes_merge_strategy { NOTES_MERGE_RESOLVE_OURS, NOTES_MERGE_RESOLVE_THEIRS, NOTES_MERGE_RESOLVE_UNION, - NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ + NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ, + NOTES_MERGE_RESOLVE_CAT_XREFS, }; struct notes_rewrite_cfg { diff --git a/notes.c b/notes.c index 25cdce28b..835466787 100644 --- a/notes.c +++ b/notes.c @@ -9,6 +9,7 @@ #include "tree-walk.h" #include "string-list.h" #include "refs.h" +#include "hashmap.h" /* * Use a non-balancing simple 16-tree structure with struct int_node as @@ -996,8 +997,12 @@ void init_notes(struct notes_tree *t, const char *notes_ref, if (!notes_ref) notes_ref = default_notes_ref(); - if (!combine_notes) - combine_notes = combine_notes_concatenate; + if (!combine_notes) { + if (starts_with(notes_ref, "refs/notes/xref-")) + combine_notes = combine_notes_cat_xrefs; + else + combine_notes = combine_notes_concatenate; + } t->root = (struct int_node *) xcalloc(1, sizeof(struct int_node)); t->first_non_note = NULL; @@ -1189,6 +1194,76 @@ void free_notes(struct notes_tree *t) memset(t, 0, sizeof(struct notes_tree)); } +/* + * Parse a "[TAG:]HEX" line. @line is trimmed. If @tag_p is not NULL and + * TAG exists, the string is split. Returns the pointer to OID and updates + * *@tag_p to point to TAG. + */ +static char *parse_xref(char *line, char **tag_p) +{ + char *p, *hex; + + /* ltrim */ + while (isspace(*line)) + line++; + + p = strchr(line, ':'); + if (p) { + if (tag_p) { + /* split and store TAG */ + *tag_p = line; + *p = '\0'; + } + /* trim whitespaces after ':' */ + p++; + while (isspace(*p)) + p++; + hex = p; + } else { + if (tag_p) + *tag_p = NULL; + hex = line; + } + + /* rtrim */ + p = hex; + while (*p != '\0' && !isspace(*p)) + p++; + *p = '\0'; + return hex; +} + +static void walk_xrefs(const char *tree_ref, struct object_id *from_oid, + struct strbuf *sb, int level) +{ + struct object_array xrefs = OBJECT_ARRAY_INIT; + struct string_list lines = STRING_LIST_INIT_DUP; + int i; + + read_xref_note(tree_ref, from_oid, &xrefs, &lines); + + for (i = 0; i < xrefs.nr; i++) { + char *line = lines.items[i].string; + char *tag; + + parse_xref(line, &tag); + strbuf_addf(sb, " %s%s%*s%s\n", + tag ?: "", tag ? ": " : "", 2 * level, "", + xrefs.objects[i].name); + if (xrefs.objects[i].item) { + if (level < 32) + walk_xrefs(tree_ref, &xrefs.objects[i].item->oid, + sb, level + 1); + else + warning("xref nested deeper than %d levels, terminating walk", + level); + } + } + + object_array_clear(&xrefs); + string_list_clear(&lines, 0); +} + /* * Fill the given strbuf with the notes associated with the given object. * @@ -1208,6 +1283,7 @@ static void format_note(struct notes_tree *t, const struct object_id *object_oid char *msg, *msg_p; unsigned long linelen, msglen; enum object_type type; + int format_xrefs; if (!t) t = &default_notes_tree; @@ -1250,6 +1326,8 @@ static void format_note(struct notes_tree *t, const struct object_id *object_oid } } + format_xrefs = !raw && starts_with(t->ref, "refs/notes/xref-"); + for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { linelen = strchrnul(msg_p, '\n') - msg_p; @@ -1257,6 +1335,14 @@ static void format_note(struct notes_tree *t, const struct object_id *object_oid strbuf_addstr(sb, " "); strbuf_add(sb, msg_p, linelen); strbuf_addch(sb, '\n'); + + if (format_xrefs) { + struct object_id oid; + + msg_p[linelen] = '\0'; + if (!get_oid_hex(parse_xref(msg_p, NULL), &oid)) + walk_xrefs(t->ref, &oid, sb, 1); + } } free(msg); @@ -1309,3 +1395,173 @@ void expand_loose_notes_ref(struct strbuf *sb) expand_notes_ref(sb); } } + +/* + * Parse a cross-referencing note. + * + * @note contains lines of "[TAG:]HEX" pointing to other commits. Read the + * target commits and add the objects to @result. If @result_lines is not + * NULL, it should point to a strdup'ing string_list. The verbatim note + * lines matching the target commits are appened to the list. + */ +static void parse_xref_note(const char *note, unsigned long size, + const struct object_id *commit_oid, + struct object_array *result, + struct string_list *result_lines) +{ + struct strbuf **lines, **pline; + + lines = strbuf_split_buf(note, size, '\n', 0); + + for (pline = lines; *pline; pline++) { + struct strbuf *line = *pline; + const char *target_hex; + struct object_id target_oid; + struct object *target_obj; + + target_hex = parse_xref(line->buf, NULL); + if (get_oid_hex(target_hex, &target_oid)) { + if (commit_oid) + warning("read invalid sha1 on %s: %s", + oid_to_hex(commit_oid), line->buf); + continue; + } + + target_obj = parse_object(the_repository, &target_oid); + if (!target_obj || target_obj->type != OBJ_COMMIT) { + if (commit_oid) + warning("read invalid commit on %s: %s", + oid_to_hex(commit_oid), line->buf); + continue; + } + + add_object_array(target_obj, target_hex, result); + if (result_lines) { + assert(result_lines->strdup_strings); + string_list_append(result_lines, line->buf); + } + } + + strbuf_list_free(lines); +} + +struct notes_tree_entry { + struct hashmap_entry ent; + struct notes_tree tree; +}; + +static int notes_tree_cmp(const void *hashmap_cmp_fn_data, + const void *entry, const void *entry_or_key, + const void *keydata) +{ + const struct notes_tree_entry *e1 = entry; + const struct notes_tree_entry *e2 = entry_or_key; + + return strcmp(e1->tree.ref, e2->tree.ref); +} + +/* + * Read and parse a cross-referencing note. + * + * Read the @notes_ref note of @commit_oid and parse it with + * parse_xref_note(). + */ +void read_xref_note(const char *notes_ref, const struct object_id *commit_oid, + struct object_array *result, + struct string_list *result_lines) +{ + static struct hashmap *notes_tree_map = NULL; + unsigned hash = memhash(notes_ref, strlen(notes_ref)); + struct notes_tree_entry key, *ent; + const struct object_id *note_oid; + unsigned long size; + enum object_type type; + char *note; + + if (!notes_tree_map) { + notes_tree_map = xcalloc(1, sizeof(struct hashmap)); + hashmap_init(notes_tree_map, notes_tree_cmp, NULL, 0); + } + + hashmap_entry_init(&key.ent, hash); + key.tree.ref = (char *)notes_ref; + ent = hashmap_get(notes_tree_map, &key, NULL); + if (!ent) { + ent = xcalloc(1, sizeof(struct notes_tree_entry)); + init_notes(&ent->tree, notes_ref, NULL, 0); + hashmap_entry_init(&ent->ent, hash); + hashmap_put(notes_tree_map, ent); + } + + note_oid = get_note(&ent->tree, commit_oid); + if (!note_oid) + return; + + note = read_object_file(note_oid, &type, &size); + if (!size) { + free(note); + return; + } + + parse_xref_note(note, size, commit_oid, result, result_lines); + free(note); +} + +/* + * Combine a xref note in @new_oid into @cur_oid. Unreachable or duplicate + * xrefs are dropped. This is the default combine_notes callback for + * refs/notes/xref-. + */ +int combine_notes_cat_xrefs(struct object_id *cur_oid, + const struct object_id *new_oid) +{ + char *cur_msg = NULL, *new_msg = NULL; + unsigned long cur_len, new_len; + enum object_type cur_type, new_type; + struct object_array xrefs = OBJECT_ARRAY_INIT; + struct string_list lines = STRING_LIST_INIT_DUP; + struct strbuf output = STRBUF_INIT; + int i, j, cur_nr, ret; + + /* read in both note blob objects */ + if (!is_null_oid(new_oid)) + new_msg = read_object_file(new_oid, &new_type, &new_len); + if (!new_msg || !new_len || new_type != OBJ_BLOB) { + free(new_msg); + return 0; + } + if (!is_null_oid(cur_oid)) + cur_msg = read_object_file(cur_oid, &cur_type, &cur_len); + if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) { + free(cur_msg); + free(new_msg); + oidcpy(cur_oid, new_oid); + return 0; + } + + /* parse xrefs and de-dup */ + parse_xref_note(cur_msg, cur_len, NULL, &xrefs, &lines); + cur_nr = xrefs.nr; + parse_xref_note(new_msg, new_len, NULL, &xrefs, &lines); + + for (i = 0; i < cur_nr; i++) + for (j = cur_nr; j < xrefs.nr; j++) + if (!strcmp(xrefs.objects[i].name, + xrefs.objects[j].name)) + lines.items[j].string[0] = '\0'; + + /* write out the combined object */ + for (i = 0; i < lines.nr; i++) + if (lines.items[i].string[0] != '\0') + strbuf_addf(&output, "%s\n", lines.items[i].string); + + ret = write_object_file(output.buf, output.len, blob_type, cur_oid); + + strbuf_release(&output); + object_array_clear(&xrefs); + string_list_clear(&lines, 0); + free(cur_msg); + free(new_msg); + + return ret; +} diff --git a/notes.h b/notes.h index 414bc6855..09ca92f36 100644 --- a/notes.h +++ b/notes.h @@ -2,6 +2,7 @@ #define NOTES_H #include "string-list.h" +#include "object.h" struct object_id; struct strbuf; @@ -317,4 +318,12 @@ void expand_notes_ref(struct strbuf *sb); */ void expand_loose_notes_ref(struct strbuf *sb); +/* For refs/notes/xref- */ +void read_xref_note(const char *notes_ref, const struct object_id *commit_oid, + struct object_array *result, + struct string_list *result_lines); + +int combine_notes_cat_xrefs(struct object_id *cur_oid, + const struct object_id *new_oid); + #endif From patchwork Tue Dec 11 23:49:07 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tejun Heo X-Patchwork-Id: 10725311 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C093F1751 for ; Tue, 11 Dec 2018 23:49:39 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id AE1CA2B203 for ; Tue, 11 Dec 2018 23:49:39 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A1F322B222; Tue, 11 Dec 2018 23:49:39 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BFECC2B203 for ; Tue, 11 Dec 2018 23:49:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726237AbeLKXth (ORCPT ); Tue, 11 Dec 2018 18:49:37 -0500 Received: from mail-qk1-f179.google.com ([209.85.222.179]:39750 "EHLO mail-qk1-f179.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726183AbeLKXth (ORCPT ); Tue, 11 Dec 2018 18:49:37 -0500 Received: by mail-qk1-f179.google.com with SMTP id q70so9736530qkh.6 for ; Tue, 11 Dec 2018 15:49:36 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=IALSmofIaWf1vFuXc45WCy6UQ2PMILhIWN78E2vXyv4=; b=ZpZwVopPduG06TSLIK8dc6Ys0tizIFr9ZsykaF1cNmjuRPILE0ZBquwgneuvDA1OoB BdPDS5dSMYJbkdd7rW5M00hCjxBhzStvgqWBxI6oYLAJSY8hui1VqBFSW3nVYuxOqUp8 P/uGQiu7cUbrtWpJX2WtfEuVR/P+WhBRU9VpB1warjTGtod7Jzo+svgAI1Y5glX7bPem kwSeVW/5OblLfwsZ/Y7lHdeP6ToNwqJgzVdwCUyDY1RMDs6AiKtKXZLp+1FmnDLfMTLS 1pdffGIJzi2BLMLU1G43tp2wQ+du9g9F+/f17gqemXD3GuFHlZA9VOVHELK/a0dEBn9S WEAA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references; bh=IALSmofIaWf1vFuXc45WCy6UQ2PMILhIWN78E2vXyv4=; b=HPQyEcoJs8ER3ow95hICvqxEEAVzLig5847MLkEI/IZMcQ8gLcPJswt2B6bMXo3vX2 zSmgzl8lf8Q+HmX+NuLVj3Tp2cbg6TVCu3PcXTJtRz87LxZks1+Hjr+zGKDhmn0E/H6P 5DCinOyMbsra4VJZCEwXcdolKlXTNDv59QKruw9HRpLz+SnZPbDzpTC7+cy+HRyPREOv hD2fBgkj5mi3kfIrbjRGeS68/NUPoHw4BzgzD/z18YBncbNHMSRJzdco2QuFvJa5cTyg SJHcwnaYsFklLO7Q+KZzpCBpLxMbLsdSd4z20K826llsd9fpHFXYSJLR163EtiCYa4MK p9Wg== X-Gm-Message-State: AA+aEWZ1CtxLpix3BZYJXAzXcgXhW7oIpLtWH7UaF3eyhCr07kgF64Hc 9I5jWTSQAZVrK0HxEyx1am+m02j1 X-Google-Smtp-Source: AFSGD/Wsn7jgD9YCBeYUiqwqvv4IOe3yCi7Z6/ZKSDlizY82lTAbRiOvQYcy28D9i/yWWXkyAZVmIg== X-Received: by 2002:a37:688c:: with SMTP id d134mr16540855qkc.57.1544572175408; Tue, 11 Dec 2018 15:49:35 -0800 (PST) Received: from localhost ([2620:10d:c091:200::7:fd8f]) by smtp.gmail.com with ESMTPSA id v186sm8666802qkd.13.2018.12.11.15.49.34 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 11 Dec 2018 15:49:34 -0800 (PST) From: Tejun Heo To: git@vger.kernel.org, Junio C Hamano , Jeff King Cc: kernel-team@fb.com, Tejun Heo Subject: [PATCH 3/5] notes: Implement git-reverse-trailer-xrefs Date: Tue, 11 Dec 2018 15:49:07 -0800 Message-Id: <20181211234909.2855638-4-tj@kernel.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181211234909.2855638-1-tj@kernel.org> References: <20181211234909.2855638-1-tj@kernel.org> Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Tejun Heo Some trailers refer to other commits. Let's call them xrefs (cross-references). For example, a cherry pick trailer points to the source commit. It is sometimes useful to build a reverse map of these xrefs - ie. source -> cherry-pick instead of cherry-pick -> source. This patch implements git-reverse-trailer-xrefs which can process a given trailer for the specified commits and generate and update the matching refs/notes/xref- notes. The command reverse-maps trailers beginning with --trailer-prefix and stores them in --ref notes with each line tagged with --tag. It also has a refs/notes/xref-cherry-picks preset for the three params. Signed-off-by: Tejun Heo --- Documentation/git-reverse-trailer-xrefs.txt | 136 +++++++++++++++++ Makefile | 1 + builtin.h | 1 + builtin/reverse-trailer-xrefs.c | 160 ++++++++++++++++++++ git.c | 1 + 5 files changed, 299 insertions(+) create mode 100644 Documentation/git-reverse-trailer-xrefs.txt create mode 100644 builtin/reverse-trailer-xrefs.c diff --git a/Documentation/git-reverse-trailer-xrefs.txt b/Documentation/git-reverse-trailer-xrefs.txt new file mode 100644 index 000000000..20d260486 --- /dev/null +++ b/Documentation/git-reverse-trailer-xrefs.txt @@ -0,0 +1,136 @@ +git-reverse-trailer-xrefs(1) +============================ + +NAME +---- +git-reverse-trailer-xrefs - Record reverse-map of trailer commit references into notes + +SYNOPSIS +-------- +[verse] +`git reverse_trailer_xrefs` --xref-cherry-picks [--clear] [] [...] +`git reverse_trailer_xrefs` --trailer-prefix= --ref= [--tag=] [] [...] +`git reverse_trailer_xrefs` --ref= --clear [] [...] + + +DESCRIPTION +----------- +Record or clear reverse-map of trailer commit references in the +specified notes ref. + +Some commit trailers reference other commits. For example, +`git-cherry-pick -x` adds the following trailer to record the source +commit. +---------- +(cherry picked from commit ) +---------- +The reverse mappings of such cross references can be useful. For +cherry-picks, it would allow finding all the cherry-picked commits of +a given source commit. `git-reverse-trailer-xrefs` can be used to +create and maintain such reverse mappings in notes. + +When used with `--xref-cherry-picks`, the cherry-pick trailers are +parsed from the specified commits and the reverse mappings are +recorded in the `refs/notes/xref-cherry-picks` notes of the source +commits in the following format. +---------- +Cherry-picked-to: +---------- + +When a note with its notes ref starting with `refs/notes/xref-` is +formatted to be displayed with the commit for, e.g., `git-show` or +`git-log`, the destination commit is followed recursively and the +matching notes are shown with increasing level of indentations. + +`--trailer-prefix`, `--notes` and `--tag` can be used to use a custom +set of trailer, notes ref and reverse mapping tag. + +OPTIONS +------- +...:: + Commit-ish object names to describe. Defaults to HEAD if omitted. + +--xref-cherry-picks:: + Use the preset to reverse map `git-cherry-pick -x` + trailers. `--trailer-prefix` is set to `(cherry-picked from + commit `, `--notes` is set to `refs/notes/xref-cherry-picks` + and `--tag` is set to `Cherry-picked-to`. This option can't be + specified with the three preset options. + +--trailer-prefix=:: + Process trailers which start with . It is matched + character-by-character and should be followed by the + referenced commit ID. When there are multiple matching + trailers, the last one is used. + +--notes=:: + The notes ref to use for the reverse mapping. While this can + be any notes ref, it is recommented to use ones starting with + `refs/notes/xref-` as they are recognized as cross-referencing + notes and handled specially when updating and showing. + +--tag=:: + Optional tag to use when generating reverse reference + notes. If specified, each note line is formatted as `: + `; otherwise, ``. + +--clear:: + Instead of creating reverse mapping notes, clear them from the + specified commits. + + +EXAMPLES +-------- + +Assume the following history. Development is happening in "master" and +releases are branched off and fixes are cherry-picked into them. + +------------ + D'---E'' release-B + / + C' E' release-D + / / +A---B---C---D---E master +------------ + +The following cherry-picks took place. + +------------ +C -> C' +D -> D' +E -> E' -> E'' +------------ + +The reverse mappings for all commits can be created using the +following command. + +------------ +$ git reverse-trailer-xrefs --all --xref-cherry-picks +------------ + +With the notes added, where each commit ended up can be easily +determined. + +------------ +$ git log --notes=xref-cherry-picks --oneline | git name-rev --name-only --stdin +4b165af commit E +Notes (xref-cherry-picks): + Cherry-picked-to: release-D + Cherry-picked-to: release-B + +82bf1f3 commit D +Notes (xref-cherry-picks): + Cherry-picked-to: release-B~1 + +364eccf commit C +Notes (xref-cherry-picks): + Cherry-picked-to: release-B~2 + +ed3adf3 commit B +ddd1bf2 commit A +------------ + + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Makefile b/Makefile index 1a44c811a..3c23ecf9d 100644 --- a/Makefile +++ b/Makefile @@ -1086,6 +1086,7 @@ BUILTIN_OBJS += builtin/multi-pack-index.o BUILTIN_OBJS += builtin/mv.o BUILTIN_OBJS += builtin/name-rev.o BUILTIN_OBJS += builtin/notes.o +BUILTIN_OBJS += builtin/reverse-trailer-xrefs.o BUILTIN_OBJS += builtin/pack-objects.o BUILTIN_OBJS += builtin/pack-redundant.o BUILTIN_OBJS += builtin/pack-refs.o diff --git a/builtin.h b/builtin.h index 6538932e9..51089e258 100644 --- a/builtin.h +++ b/builtin.h @@ -195,6 +195,7 @@ extern int cmd_multi_pack_index(int argc, const char **argv, const char *prefix) extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_notes(int argc, const char **argv, const char *prefix); +extern int cmd_reverse_trailer_xrefs(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix); extern int cmd_patch_id(int argc, const char **argv, const char *prefix); diff --git a/builtin/reverse-trailer-xrefs.c b/builtin/reverse-trailer-xrefs.c new file mode 100644 index 000000000..0edb3b91a --- /dev/null +++ b/builtin/reverse-trailer-xrefs.c @@ -0,0 +1,160 @@ +#include "builtin.h" +#include "cache.h" +#include "strbuf.h" +#include "repository.h" +#include "config.h" +#include "commit.h" +#include "blob.h" +#include "notes.h" +#include "notes-utils.h" +#include "trailer.h" +#include "revision.h" +#include "list-objects.h" +#include "object-store.h" +#include "parse-options.h" + +static const char * const reverse_trailer_xrefs_usage[] = { + N_("git reverse_trailer_xrefs --xref-cherry-picks [--clear] [] [...]"), + N_("git reverse_trailer_xrefs --trailer-prefix= --notes= --tag= [] [...]"), + N_("git reverse_trailer_xrefs --notes= --clear [] [...]"), + NULL +}; + +#define CHERRY_PICKED_PREFIX "(cherry picked from commit " +#define CHERRY_PICKS_REF "refs/notes/xref-cherry-picks" +#define CHERRY_PICKED_TO_TAG "Cherry-picked-to" + +static int verbose; + +static void clear_trailer_xref_note(struct commit *commit, void *data) +{ + struct notes_tree *tree = data; + int status; + + status = remove_note(tree, commit->object.oid.hash); + + if (verbose) { + if (status) + fprintf(stderr, "Object %s has no note\n", + oid_to_hex(&commit->object.oid)); + else + fprintf(stderr, "Removing note for object %s\n", + oid_to_hex(&commit->object.oid)); + } +} + +static void record_trailer_xrefs(struct commit *commit, void *data) +{ + trailer_rev_xrefs_record(data, commit); +} + +static int note_trailer_xrefs(struct notes_tree *tree, + struct commit *dst_commit, + struct object_array *src_objs, const char *tag) +{ + char dst_hex[GIT_MAX_HEXSZ + 1]; + struct strbuf note = STRBUF_INIT; + struct object_id note_oid; + int i, ret; + + oid_to_hex_r(dst_hex, &dst_commit->object.oid); + + for (i = 0; i < src_objs->nr; i++) { + const char *hex = src_objs->objects[i].name; + + if (tag) + strbuf_addf(¬e, "%s: %s\n", tag, hex); + else + strbuf_addf(¬e, "%s\n", tag); + if (verbose) + fprintf(stderr, "Adding note %s -> %s\n", dst_hex, hex); + } + + ret = write_object_file(note.buf, note.len, blob_type, ¬e_oid); + strbuf_release(¬e); + if (ret) + return ret; + + ret = add_note(tree, &dst_commit->object.oid, ¬e_oid, NULL); + return ret; +} + +static int reverse_trailer_xrefs(struct rev_info *revs, int clear, + const char *trailer_prefix, + const char *notes_ref, const char *tag) +{ + struct notes_tree tree = { }; + int i, ret; + + init_notes(&tree, notes_ref, NULL, NOTES_INIT_WRITABLE); + + if (prepare_revision_walk(revs)) + die("revision walk setup failed"); + + if (clear) { + traverse_commit_list(revs, clear_trailer_xref_note, NULL, &tree); + } else { + struct trailer_rev_xrefs rxrefs; + struct commit *dst_commit; + struct object_array *src_objs; + + trailer_rev_xrefs_init(&rxrefs, trailer_prefix); + traverse_commit_list(revs, record_trailer_xrefs, NULL, &rxrefs); + + trailer_rev_xrefs_for_each(&rxrefs, i, dst_commit, src_objs) { + ret = note_trailer_xrefs(&tree, dst_commit, src_objs, + tag); + if (ret) + return ret; + } + + trailer_rev_xrefs_release(&rxrefs); + } + + commit_notes(&tree, "Notes updated by 'git reverse-trailer-xrefs'"); + return 0; +} + +int cmd_reverse_trailer_xrefs(int argc, const char **argv, const char *prefix) +{ + struct rev_info revs; + struct setup_revision_opt s_r_opt = { + .def = "HEAD", + .revarg_opt = REVARG_CANNOT_BE_FILENAME + }; + int cherry = 0, clear = 0; + const char *trailer_prefix = NULL, *notes_ref = NULL, *tag = NULL; + struct option options[] = { + OPT_BOOL(0, "xref-cherry-picks", &cherry, N_("use preset for xref-cherry-picks notes")), + OPT_STRING(0, "trailer-prefix", &trailer_prefix, N_("prefix"), N_("process trailers starting with ")), + OPT_STRING(0, "notes", ¬es_ref, N_("notes-ref"), N_("update notes in ")), + OPT_STRING(0, "tag", &tag, N_("tag"), N_("tag xref notes with ")), + OPT_BOOL(0, "clear", &clear, N_("clear trailer xref notes from the specified commits")), + OPT__VERBOSE(&verbose, N_("verbose")), + OPT_END() + }; + + git_config(git_default_config, NULL); + + argc = parse_options(argc, argv, prefix, options, + reverse_trailer_xrefs_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); + + init_revisions(&revs, prefix); + argc = setup_revisions(argc, argv, &revs, &s_r_opt); + + if (cherry) { + trailer_prefix = CHERRY_PICKED_PREFIX; + notes_ref = CHERRY_PICKS_REF; + tag = CHERRY_PICKED_TO_TAG; + } + + if (!notes_ref || (!clear && (!trailer_prefix || !tag))) + die(_("insufficient arguments")); + + if (argc > 1) + die(_("unrecognized argument: %s"), argv[1]); + + return reverse_trailer_xrefs(&revs, clear, + trailer_prefix, notes_ref, tag); +} diff --git a/git.c b/git.c index 2f604a41e..4948c8e01 100644 --- a/git.c +++ b/git.c @@ -515,6 +515,7 @@ static struct cmd_struct commands[] = { { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "notes", cmd_notes, RUN_SETUP }, + { "reverse-trailer-xrefs", cmd_reverse_trailer_xrefs, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, { "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT }, { "pack-refs", cmd_pack_refs, RUN_SETUP }, From patchwork Tue Dec 11 23:49:08 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tejun Heo X-Patchwork-Id: 10725313 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 6EF1814E2 for ; Tue, 11 Dec 2018 23:49:42 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 5E4422B203 for ; Tue, 11 Dec 2018 23:49:42 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 526882B222; Tue, 11 Dec 2018 23:49:42 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7E7522B203 for ; Tue, 11 Dec 2018 23:49:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726240AbeLKXtk (ORCPT ); Tue, 11 Dec 2018 18:49:40 -0500 Received: from mail-qt1-f196.google.com ([209.85.160.196]:39783 "EHLO mail-qt1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726238AbeLKXtk (ORCPT ); Tue, 11 Dec 2018 18:49:40 -0500 Received: by mail-qt1-f196.google.com with SMTP id n21so18584905qtl.6 for ; Tue, 11 Dec 2018 15:49:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=WnAGjh6B8DdIzUArnjsZYkwCB5rgZsX9uDkvzrM4f34=; b=WYqDTJG+KFWfpz4ILk+SO9zRfwEu9mW+gvaaHhjaZFiTcbXNL1HL+EI81Dz0ULFEQT A5tjYsdEegyASTn8VvtYLgfbvRJ2PniCP7YSndJ7z50g2m9cbi2fHHrwUVSLFS7OfofD InTYpApqAojv+dAc2LDJOAkdsx0gV/YNs+WkNQlDHanR6IPoek+a5KaZxKqMrg1gAePV tpIjREomE1KRmY9KTkmZ1EAea2Y0jhh5pcaGlPkKBUdoKuuPIfbHslWW63Ncb0oKtIoB HczqIrCt6yFxoJVIcRMJwUU73LgbJLm3E4GpjCHY6QeZ+o9AuIwUtrQ+VyEybZMYjPcY QAYA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references; bh=WnAGjh6B8DdIzUArnjsZYkwCB5rgZsX9uDkvzrM4f34=; b=g67znnuoHAwK/RuxMZLvX954clvKyG6symfQ0IO0dbiVYIMd63rpkRzUm+R74i5KZp 4z7eaXt+1LsZDLZXRnKIqqLsm5vdTDV6Vhu7gVuy73S9qlP8qrI1GNcpHYIlCv9PaGqC iWJQLQ+qTwfeo2TKt2U3sL/J1E9X69+B2Wv5UesPGqZyPOBh6JgaftK1H/3O2snSvCOh m8laSfdRyrhvjqbHP8lJYYbwAxL1xQCLR3FcggAnX0Hmk5HJQJLb15QZEhHgJKziotOO qaQSunpk6QTaWoyzoScAkKO1OjXo6Y/w1wkWuBAeBBmpzc8u4f1rNoKO1YjTl43O74Ph nRZg== X-Gm-Message-State: AA+aEWZScl8EvtBUQe+jKKpMj0G1lfter3vtd4BhX7BnT/M++cNeKaC5 AC1Y0a8OefPHEHrux5Bcxdr9LK6U X-Google-Smtp-Source: AFSGD/UTAYogyWPmf8kc47cKHHdtbxDKDwibzLQ8WVagI9TQQULKDYcvVKTMPWEzVceHwToxHRORYg== X-Received: by 2002:ac8:1185:: with SMTP id d5mr18227572qtj.117.1544572178597; Tue, 11 Dec 2018 15:49:38 -0800 (PST) Received: from localhost ([2620:10d:c091:200::7:fd8f]) by smtp.gmail.com with ESMTPSA id l73sm12435953qkl.16.2018.12.11.15.49.37 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 11 Dec 2018 15:49:37 -0800 (PST) From: Tejun Heo To: git@vger.kernel.org, Junio C Hamano , Jeff King Cc: kernel-team@fb.com, Tejun Heo Subject: [PATCH 4/5] githooks: Add post-cherry-pick and post-fetch hooks Date: Tue, 11 Dec 2018 15:49:08 -0800 Message-Id: <20181211234909.2855638-5-tj@kernel.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181211234909.2855638-1-tj@kernel.org> References: <20181211234909.2855638-1-tj@kernel.org> Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Tejun Heo * post-cherry-pick: Called after a cherry-pick and given parameters so that it can tell which are the new cherry-picks. * post-fetch: Called after a fetch. Each updated ref and sha1 are fed on stdin. These two hooks will be used to keep refs/notes/xref-cherry-picks up-to-date. Signed-off-by: Tejun Heo --- Documentation/git-cherry-pick.txt | 5 +++ Documentation/git-fetch.txt | 5 +++ Documentation/githooks.txt | 23 ++++++++++ builtin/fetch.c | 72 ++++++++++++++++++++++++++++--- builtin/revert.c | 14 ++++++ 5 files changed, 114 insertions(+), 5 deletions(-) diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index d35d771fc..527cb9fea 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -224,6 +224,11 @@ the working tree. spending extra time to avoid mistakes based on incorrectly matching context lines. +HOOKS +----- +This command can run `post-cherry-pick` hook. See linkgit:githooks[5] +for more information. + SEE ALSO -------- linkgit:git-revert[1] diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index e31993559..a04c6079a 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -290,6 +290,11 @@ fetched, making it impossible to check out that submodule later without having to do a fetch again. This is expected to be fixed in a future Git version. +HOOKS +----- +This command can run `post-fetch` hook. See linkgit:githooks[5] +for more information. + SEE ALSO -------- linkgit:git-pull[1] diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 959044347..24c122343 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -149,6 +149,14 @@ invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of `git commit`. +post-cherry-pick +~~~~~~~~~~~~~~~~ + +This hook is invoked by linkgit:git-cherry-pick[1]. This hook is +called with two parameters. The first is `` and the second +``, where `..` describes all new +cherry-picked commits. + pre-rebase ~~~~~~~~~~ @@ -191,6 +199,21 @@ save and restore any form of metadata associated with the working tree (e.g.: permissions/ownership, ACLS, etc). See contrib/hooks/setgitperms.perl for an example of how to do this. +post-fetch +~~~~~~~~~~ +This hook is called by linkgit:git-fetch[1] and can be used to process +newly fetched commits and tags. + +Information about what was fetched is provided on the hook's standard +input with lines of the form: + + SP SP SP LF + +where `` got updated from `` to `` as a +result of fetching ``. If a branch or tag was created, +`` will be 40 `0`. If a tag was pruned, `` will +be `(delete)` and will be 40 `0`. + pre-push ~~~~~~~~ diff --git a/builtin/fetch.c b/builtin/fetch.c index e0140327a..eac792a33 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -66,6 +66,7 @@ static struct refspec refmap = REFSPEC_INIT_FETCH; static struct list_objects_filter_options filter_options; static struct string_list server_options = STRING_LIST_INIT_DUP; static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; +static struct strbuf post_fetch_sb = STRBUF_INIT; static int git_fetch_config(const char *k, const char *v, void *cb) { @@ -510,10 +511,24 @@ static struct ref *get_ref_map(struct remote *remote, return ref_map; } +static void record_post_fetch(const char *name, + const struct object_id *old_oid, + const char *remote, + const struct object_id *new_oid) +{ + char old_hex[GIT_MAX_HEXSZ + 1], new_hex[GIT_MAX_HEXSZ + 1]; + + oid_to_hex_r(old_hex, old_oid); + oid_to_hex_r(new_hex, new_oid); + strbuf_addf(&post_fetch_sb, "%s %s %s %s\n", + name, old_hex, remote ?: "(delete)", new_hex); +} + #define STORE_REF_ERROR_OTHER 1 #define STORE_REF_ERROR_DF_CONFLICT 2 static int s_update_ref(const char *action, + const char *remote, struct ref *ref, int check_old) { @@ -546,6 +561,7 @@ static int s_update_ref(const char *action, ref_transaction_free(transaction); strbuf_release(&err); free(msg); + record_post_fetch(ref->name, &ref->old_oid, remote, &ref->new_oid); return 0; fail: ref_transaction_free(transaction); @@ -726,7 +742,7 @@ static int update_local_ref(struct ref *ref, starts_with(ref->name, "refs/tags/")) { if (force || ref->force) { int r; - r = s_update_ref("updating tag", ref, 0); + r = s_update_ref("updating tag", remote, ref, 0); format_display(display, r ? '!' : 't', _("[tag update]"), r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -766,7 +782,7 @@ static int update_local_ref(struct ref *ref, if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(&ref->new_oid); - r = s_update_ref(msg, ref, 0); + r = s_update_ref(msg, remote, ref, 0); format_display(display, r ? '!' : '*', what, r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -782,7 +798,7 @@ static int update_local_ref(struct ref *ref, if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(&ref->new_oid); - r = s_update_ref("fast-forward", ref, 1); + r = s_update_ref("fast-forward", remote, ref, 1); format_display(display, r ? '!' : ' ', quickref.buf, r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -797,7 +813,7 @@ static int update_local_ref(struct ref *ref, if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(&ref->new_oid); - r = s_update_ref("forced-update", ref, 1); + r = s_update_ref("forced-update", remote, ref, 1); format_display(display, r ? '!' : '+', quickref.buf, r ? _("unable to update local ref") : _("forced update"), remote, pretty_ref, summary_width); @@ -1071,8 +1087,11 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map, if (!dry_run) { struct string_list refnames = STRING_LIST_INIT_NODUP; - for (ref = stale_refs; ref; ref = ref->next) + for (ref = stale_refs; ref; ref = ref->next) { string_list_append(&refnames, ref->name); + record_post_fetch(ref->name, &ref->old_oid, + NULL, &null_oid); + } result = delete_refs("fetch: prune", &refnames, 0); string_list_clear(&refnames, 0); @@ -1561,6 +1580,47 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int pru return exit_code; } +static int run_post_fetch_hook(void) +{ + int ret = 0, x; + struct child_process proc = CHILD_PROCESS_INIT; + const char *argv[2]; + + if (!(argv[0] = find_hook("post-fetch"))) + return 0; + argv[1] = NULL; + + proc.argv = argv; + proc.in = -1; + + if (start_command(&proc)) { + finish_command(&proc); + return -1; + } + + sigchain_push(SIGPIPE, SIG_IGN); + + if (write_in_full(proc.in, post_fetch_sb.buf, post_fetch_sb.len) < 0) { + /* We do not mind if a hook does not read all refs. */ + if (errno != EPIPE) + ret = -1; + } + + strbuf_release(&post_fetch_sb); + + x = close(proc.in); + if (!ret) + ret = x; + + sigchain_pop(SIGPIPE); + + x = finish_command(&proc); + if (!ret) + ret = x; + + return ret; +} + int cmd_fetch(int argc, const char **argv, const char *prefix) { int i; @@ -1669,6 +1729,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) close_all_packs(the_repository->objects); + run_post_fetch_hook(); + argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); if (verbosity < 0) argv_array_push(&argv_gc_auto, "--quiet"); diff --git a/builtin/revert.c b/builtin/revert.c index c93393c89..0b7e578cc 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -8,6 +8,8 @@ #include "dir.h" #include "sequencer.h" #include "branch.h" +#include "refs.h" +#include "run-command.h" /* * This implements the builtins revert and cherry-pick. @@ -223,12 +225,24 @@ int cmd_revert(int argc, const char **argv, const char *prefix) int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { struct replay_opts opts = REPLAY_OPTS_INIT; + struct object_id old_oid, new_oid; + char old_hex[GIT_MAX_HEXSZ + 1], new_hex[GIT_MAX_HEXSZ + 1]; int res; + if (read_ref("HEAD", &old_oid)) + die(_("failed to read HEAD, cherry-pick failed")); + opts.action = REPLAY_PICK; sequencer_init_config(&opts); res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("cherry-pick failed")); + + if (read_ref("HEAD", &new_oid)) + die(_("failed to read HEAD after cherry-pick")); + + oid_to_hex_r(old_hex, &old_oid); + oid_to_hex_r(new_hex, &new_oid); + run_hook_le(0, "post-cherry-pick", old_hex, new_hex, NULL); return res; } From patchwork Tue Dec 11 23:49:09 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tejun Heo X-Patchwork-Id: 10725315 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id EF53C1751 for ; Tue, 11 Dec 2018 23:49:45 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DDF0E2B203 for ; Tue, 11 Dec 2018 23:49:45 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D079F2B222; Tue, 11 Dec 2018 23:49:45 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 31DE32B203 for ; Tue, 11 Dec 2018 23:49:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726247AbeLKXto (ORCPT ); Tue, 11 Dec 2018 18:49:44 -0500 Received: from mail-qt1-f195.google.com ([209.85.160.195]:38549 "EHLO mail-qt1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726238AbeLKXtn (ORCPT ); Tue, 11 Dec 2018 18:49:43 -0500 Received: by mail-qt1-f195.google.com with SMTP id p17so18592801qtl.5 for ; Tue, 11 Dec 2018 15:49:42 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=fl//SosWD9V3LGcQpdy9Q7CqmvlVv+Wc6+3GcdJQII0=; b=WputaopfU/A242uDFwRb5jrmBmh42V3ivabkta4tBDag47tzdMW7wqfu6sxzz8REqh RgPYfwe+Si2td8oebH33Myh3Qj+sluQIExMzx+uZlfwvAkd1Qf8drKKkfcbyX20XqLXO gkpttcOpp+m66mUEKAv56lAQYKT2VYC9SAci+T/6Y6ITUUNYEr/dFX0bN3yf8wDbnO6j V1on0if1KdvhxrlUgX1SbpPwd6DpMxEJVEZlf67layDY1TXMMmpuV4skS6lKk0dQiphN 4gN3dqUudPcZusaZhOflkKqqc5SBcnrFwfIBkSuRq3Re2WQTd2I2vjIGcagE4BwIqkfW QnGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references; bh=fl//SosWD9V3LGcQpdy9Q7CqmvlVv+Wc6+3GcdJQII0=; b=Cz8CTtEFf0bbYwcDavAhi6QdfA0qOP89t1CxOJugOkf5ugvohGLrFkDrpm6VYIsqUL /k/dPrtAv7PsLLxANl5GpQ/SrtUAo80Jb7jnZMQBxGAT+OB1FmD/wpy+QtIf3jk5jycJ oHKgmHbz2coIO09BOz8x6K/zPfJCFur21MeLwKiKr4R69R0ALlnm8l8ARrVs7/0l13pl lJchiOKJsGHaDrC+h12M6GQUGgD3mcoqlEey7CQzHXDXTgi+Cke+uczaxTQT8kzH9S++ AkIHdw4VpDjoIexxiAe7LM9Bhi2LRkeccwp77nPryC43CatHRXFT93ZoGerKAwu8zZR7 L9lw== X-Gm-Message-State: AA+aEWYWpyjsygNt/qNY86l5bCDFjTX5FbcsEnIOki4cUMvfnLZAiSVY CeAsod0rFzqb8WGya+o4ySVVgMl4 X-Google-Smtp-Source: AFSGD/UQQmE06BDa/9xhotryBpvbA37QqCxlsai+0QsUba68tUsITEdmEIXW3vPM4wjOIuQqzKzF9A== X-Received: by 2002:a05:6214:1087:: with SMTP id o7mr17494333qvr.115.1544572181329; Tue, 11 Dec 2018 15:49:41 -0800 (PST) Received: from localhost ([2620:10d:c091:200::7:fd8f]) by smtp.gmail.com with ESMTPSA id o21sm10759544qto.18.2018.12.11.15.49.40 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 11 Dec 2018 15:49:40 -0800 (PST) From: Tejun Heo To: git@vger.kernel.org, Junio C Hamano , Jeff King Cc: kernel-team@fb.com, Tejun Heo Subject: [PATCH 5/5] notes: Implement xref-cherry-picks hooks and tests Date: Tue, 11 Dec 2018 15:49:09 -0800 Message-Id: <20181211234909.2855638-6-tj@kernel.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181211234909.2855638-1-tj@kernel.org> References: <20181211234909.2855638-1-tj@kernel.org> Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Tejun Heo Add post-cherry-pick.sample and post-fetch.sample which, when enabled, will keep refs/notes/xref-cherry-picks up-to-date as new cherry-picks are created and fetched. Also, add tests to verify xref-cherry-picks. Signed-off-by: Tejun Heo --- Documentation/git-reverse-trailer-xrefs.txt | 9 ++ t/t3321-notes-xref-cherry-picks.sh | 124 ++++++++++++++++++++ templates/hooks--post-cherry-pick.sample | 8 ++ templates/hooks--post-fetch.sample | 30 +++++ 4 files changed, 171 insertions(+) create mode 100644 t/t3321-notes-xref-cherry-picks.sh create mode 100644 templates/hooks--post-cherry-pick.sample create mode 100644 templates/hooks--post-fetch.sample diff --git a/Documentation/git-reverse-trailer-xrefs.txt b/Documentation/git-reverse-trailer-xrefs.txt index 20d260486..651ecdce1 100644 --- a/Documentation/git-reverse-trailer-xrefs.txt +++ b/Documentation/git-reverse-trailer-xrefs.txt @@ -131,6 +131,15 @@ ddd1bf2 commit A ------------ +Keeping xref-cherry-picks up-to-date +------------------------------------ + +Reverse-maps can be kept-up incrementally with hooks. For example, to +keep xref-cherry-picks up-to-date, `git-reverse-trailer-xrefs` should +be invoked on new cherry-picks and fetched commits. See +`hooks/post-cherry-pick.sample` and `hooks/post-fetch.sample`. + + GIT --- Part of the linkgit:git[1] suite diff --git a/t/t3321-notes-xref-cherry-picks.sh b/t/t3321-notes-xref-cherry-picks.sh new file mode 100644 index 000000000..96b6731c9 --- /dev/null +++ b/t/t3321-notes-xref-cherry-picks.sh @@ -0,0 +1,124 @@ +#!/bin/sh + +test_description='Verify xref-cherry-picks handling + +Assume the following git repository. + + D*---E** release-B + / + C* E* release-D + / / + A---B---C---D---E master + +which contains the following cherry-picks. + + C -> C* + D -> D* + E -> E* -> E** + +1. Build the above repository using `git-cherry-pick -x` with the + sample post-cherry-pick hook enabled. Verify that the + xref-cherry-picks notes are populated correctly. + +2. Clear the notes and rebuild them by directly running + git-reverse-xref-trailers and verify the output. + +3. Run it again and check the output still agrees to verify duplicate + handling. + +4. Build a cloned repository using per-branch fetches with the sample + post-fetch hook enabled. Verify that the xref-cherry-picks notes + are populatec correctly. +' + +TEST_NO_CREATE_REPO=1 + +. ./test-lib.sh + +GIT_AUTHOR_EMAIL=bogus_email_address +export GIT_AUTHOR_EMAIL + +test_expect_success \ + 'Build repo with cherry-picks and verify xref-cherry-picks' \ + 'test_create_repo main && + cd main && + mkdir -p .git/hooks && + mv .git/hooks-disabled/post-cherry-pick.sample .git/hooks/post-cherry-pick && + + test_tick && + echo A >> testfile && + git update-index --add testfile && + git commit -am "A" && + echo B >> testfile && + git commit -am "B" && + echo C >> testfile && + git commit -am "C" && + echo D >> testfile && + git commit -am "D" && + echo E >> testfile && + git commit -am "E" && + + test_tick && + git checkout -b release-D master^ && + git cherry-pick -x master && + + test_tick && + git checkout -b release-B master^^^ && + git cherry-pick -x release-D^^ && + git cherry-pick -x release-D^ && + git cherry-pick -x release-D && + + cat > expect <<-EOF && +master E +Notes (xref-cherry-picks): + Cherry-picked-to: release-D + Cherry-picked-to: release-B + +master~1 D +Notes (xref-cherry-picks): + Cherry-picked-to: release-B~1 + +master~2 C +Notes (xref-cherry-picks): + Cherry-picked-to: release-B~2 + +master~3 B +master~4 A +EOF + + git log --pretty=oneline --notes=xref-cherry-picks master | git name-rev --name-only --stdin > actual && + test_cmp expect actual +' + +test_expect_success \ + 'Clear, rebuild and verify xref-cherry-picks' \ + 'git reverse-trailer-xrefs --xref-cherry-picks --all --clear && + git reverse-trailer-xrefs --xref-cherry-picks --all && + git log --pretty=oneline --notes=xref-cherry-picks master | git name-rev --name-only --stdin > actual && + test_cmp expect actual +' + +test_expect_success \ + 'Build it again to verify duplicate handling' \ + 'git reverse-trailer-xrefs --xref-cherry-picks --all && + git log --pretty=oneline --notes=xref-cherry-picks master | git name-rev --name-only --stdin > actual && + test_cmp expect actual +' + +test_expect_success \ + 'Build a clone through per-branch fetches and verify xref-cherry-picks' \ + 'cd .. && + test_create_repo clone && + cd clone && + mkdir -p .git/hooks && + mv .git/hooks-disabled/post-fetch.sample .git/hooks/post-fetch && + + git fetch -fu ../main master:master && + git fetch -fu ../main release-D:release-D && + git fetch -fu ../main release-B:release-B && + + git log --pretty=oneline --notes=xref-cherry-picks master | git name-rev --name-only --stdin > actual && + test_cmp ../main/expect actual +' + +test_done diff --git a/templates/hooks--post-cherry-pick.sample b/templates/hooks--post-cherry-pick.sample new file mode 100644 index 000000000..3af8b5d23 --- /dev/null +++ b/templates/hooks--post-cherry-pick.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to reverse map new cherry-picks. See +# git-reverse-trailer-xrefs(1) for details. +# +# To enable this hook, rename this file to "post-cherry-pick". + +git reverse-trailer-xrefs --xref-cherry-picks $1..$2 diff --git a/templates/hooks--post-fetch.sample b/templates/hooks--post-fetch.sample new file mode 100644 index 000000000..6b98a5c10 --- /dev/null +++ b/templates/hooks--post-fetch.sample @@ -0,0 +1,30 @@ +#!/bin/sh +# +# An example hook script to reverse map cherry-picks in newly fetched +# commits. See git-reverse-trailer-xrefs(1) for details. +# +# To enable this hook, rename this file to "post-fetch". + +z40=0000000000000000000000000000000000000000 + +while read ref old_sha remote new_sha +do + case $ref in + refs/heads/*) + ;; + *) + continue + esac + + if [ $new_sha == $z40 ] + then + continue + fi + + if [ $old_sha != $z40 ] + then + git reverse-trailer-xrefs --xref-cherry-picks $old_sha..$new_sha + else + git reverse-trailer-xrefs --xref-cherry-picks $new_sha + fi +done