notes: implement helpers needed for note copying during rewrite
Implement helper functions to load the rewriting config, and to actually copy the notes. Also document the config. Secondly, also implement an undocumented --for-rewrite=<cmd> option to 'git notes copy' which is used like --stdin, but also puts the configuration for <cmd> into effect. It will be needed to support the copying in git-rebase. Signed-off-by: Thomas Rast <trast@student.ethz.ch> Acked-by: Johan Herland <johan@herland.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Thomas Rast committed
Mar 12, 2010 at 18:04 UTC
6956f858f6237d426fa422949033e3c558104802
8 files changed
+385
-8
Documentation/config.txt
+30
@@ -1305,6 +1305,36 @@ The effective value of "core.notesRef" (possibly overridden by
1305
GIT_NOTES_REF) is also implicitly added to the list of refs to be
1306
displayed.
1307
1308
+notes.rewrite.<command>::
1309
+ When rewriting commits with <command> (currently `amend` or
1310
+ `rebase`) and this variable is set to `true`, git
1311
+ automatically copies your notes from the original to the
1312
+ rewritten commit. Defaults to `true`, but see
1313
+ "notes.rewriteRef" below.
1314
++
1315
+This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
1316
+environment variable, which must be a colon separated list of refs or
1317
+globs.
1318
+
1319
+notes.rewriteMode::
1320
+ When copying notes during a rewrite (see the
1321
+ "notes.rewrite.<command>" option), determines what to do if
1322
+ the target commit already has a note. Must be one of
1323
+ `overwrite`, `concatenate`, or `ignore`. Defaults to
1324
+ `concatenate`.
1325
++
1326
+This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
1327
+environment variable.
1328
+
1329
+notes.rewriteRef::
1330
+ When copying notes during a rewrite, specifies the (fully
1331
+ qualified) ref whose notes should be copied. The ref may be a
1332
+ glob, in which case notes in all matching refs will be copied.
1333
+ You may also specify this configuration several times.
1334
++
1335
+Does not have a default value; you must configure this variable to
1336
+enable note rewriting.
1337
+
1338
pack.window::
1339
The size of the window used by linkgit:git-pack-objects[1] when no
1340
window size is given on the command line. Defaults to 10.
Documentation/git-notes.txt
+4
@@ -35,6 +35,10 @@ This command always manipulates the notes specified in "core.notesRef"
35
To change which notes are shown by 'git-log', see the
36
"notes.displayRef" configuration.
37
38
+See the description of "notes.rewrite.<command>" in
39
+linkgit:git-config[1] for a way of carrying your notes across commands
40
+that rewrite commits.
41
+
42
43
SUBCOMMANDS
44
-----------
Documentation/githooks.txt
+4
@@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent. If it is empty, the
335
preceding SP is also omitted. Currently, no commands pass any
336
'extra-info'.
337
338
+The hook always runs after the automatic note copying (see
339
+"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
340
+thus has access to these notes.
341
+
342
The following command-specific comments apply:
343
344
rebase::
builtin-notes.c
+131
-8
@@ -16,6 +16,7 @@
16
#include "exec_cmd.h"
17
#include "run-command.h"
18
#include "parse-options.h"
19
+#include "string-list.h"
20
21
static const char * const git_notes_usage[] = {
22
"git notes [list [<object>]]",
@@ -278,14 +279,121 @@ int commit_notes(struct notes_tree *t, const char *msg)
279
return 0;
280
}
281
281
-int notes_copy_from_stdin(int force)
282
+
283
+combine_notes_fn *parse_combine_notes_fn(const char *v)
284
+{
285
+ if (!strcasecmp(v, "overwrite"))
286
+ return combine_notes_overwrite;
287
+ else if (!strcasecmp(v, "ignore"))
288
+ return combine_notes_ignore;
289
+ else if (!strcasecmp(v, "concatenate"))
290
+ return combine_notes_concatenate;
291
+ else
292
+ return NULL;
293
+}
294
+
295
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
296
+{
297
+ struct notes_rewrite_cfg *c = cb;
298
+ if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
299
+ c->enabled = git_config_bool(k, v);
300
+ return 0;
301
+ } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
302
+ if (!v)
303
+ config_error_nonbool(k);
304
+ c->combine = parse_combine_notes_fn(v);
305
+ if (!c->combine) {
306
+ error("Bad notes.rewriteMode value: '%s'", v);
307
+ return 1;
308
+ }
309
+ return 0;
310
+ } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
311
+ /* note that a refs/ prefix is implied in the
312
+ * underlying for_each_glob_ref */
313
+ if (!prefixcmp(v, "refs/notes/"))
314
+ string_list_add_refs_by_glob(c->refs, v);
315
+ else
316
+ warning("Refusing to rewrite notes in %s"
317
+ " (outside of refs/notes/)", v);
318
+ return 0;
319
+ }
320
+
321
+ return 0;
322
+}
323
+
324
+
325
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
326
+{
327
+ struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
328
+ const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
329
+ const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
330
+ c->cmd = cmd;
331
+ c->enabled = 1;
332
+ c->combine = combine_notes_concatenate;
333
+ c->refs = xcalloc(1, sizeof(struct string_list));
334
+ c->refs->strdup_strings = 1;
335
+ c->refs_from_env = 0;
336
+ c->mode_from_env = 0;
337
+ if (rewrite_mode_env) {
338
+ c->mode_from_env = 1;
339
+ c->combine = parse_combine_notes_fn(rewrite_mode_env);
340
+ if (!c->combine)
341
+ error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
342
+ " value: '%s'", rewrite_mode_env);
343
+ }
344
+ if (rewrite_refs_env) {
345
+ c->refs_from_env = 1;
346
+ string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
347
+ }
348
+ git_config(notes_rewrite_config, c);
349
+ if (!c->enabled || !c->refs->nr) {
350
+ string_list_clear(c->refs, 0);
351
+ free(c->refs);
352
+ free(c);
353
+ return NULL;
354
+ }
355
+ c->trees = load_notes_trees(c->refs);
356
+ string_list_clear(c->refs, 0);
357
+ free(c->refs);
358
+ return c;
359
+}
360
+
361
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
362
+ const unsigned char *from_obj, const unsigned char *to_obj)
363
+{
364
+ int ret = 0;
365
+ int i;
366
+ for (i = 0; c->trees[i]; i++)
367
+ ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
368
+ return ret;
369
+}
370
+
371
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
372
+{
373
+ int i;
374
+ for (i = 0; c->trees[i]; i++) {
375
+ commit_notes(c->trees[i], "Notes added by 'git notes copy'");
376
+ free_notes(c->trees[i]);
377
+ }
378
+ free(c->trees);
379
+ free(c);
380
+}
381
+
382
+int notes_copy_from_stdin(int force, const char *rewrite_cmd)
383
{
384
struct strbuf buf = STRBUF_INIT;
385
+ struct notes_rewrite_cfg *c = NULL;
386
struct notes_tree *t;
387
int ret = 0;
388
287
- init_notes(NULL, NULL, NULL, 0);
288
- t = &default_notes_tree;
389
+ if (rewrite_cmd) {
390
+ c = init_copy_notes_for_rewrite(rewrite_cmd);
391
+ if (!c)
392
+ return 0;
393
+ } else {
394
+ init_notes(NULL, NULL, NULL, 0);
395
+ t = &default_notes_tree;
396
+ }
397
398
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
399
unsigned char from_obj[20], to_obj[20];
@@ -302,7 +410,11 @@ int notes_copy_from_stdin(int force)
410
if (get_sha1(split[1]->buf, to_obj))
411
die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
412
305
- err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
413
+ if (rewrite_cmd)
414
+ err = copy_note_for_rewrite(c, from_obj, to_obj);
415
+ else
416
+ err = copy_note(t, from_obj, to_obj, force,
417
+ combine_notes_overwrite);
418
419
if (err) {
420
error("Failed to copy notes from '%s' to '%s'",
@@ -313,8 +425,12 @@ int notes_copy_from_stdin(int force)
425
strbuf_list_free(split);
426
}
427
316
- commit_notes(t, "Notes added by 'git notes copy'");
317
- free_notes(t);
428
+ if (!rewrite_cmd) {
429
+ commit_notes(t, "Notes added by 'git notes copy'");
430
+ free_notes(t);
431
+ } else {
432
+ finish_copy_notes_for_rewrite(c);
433
+ }
434
return ret;
435
}
436
@@ -330,6 +446,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
446
remove = 0, prune = 0, force = 0, from_stdin = 0;
447
int given_object = 0, i = 1, retval = 0;
448
struct msg_arg msg = { 0, 0, STRBUF_INIT };
449
+ const char *rewrite_cmd = NULL;
450
struct option options[] = {
451
OPT_GROUP("Notes options"),
452
OPT_CALLBACK('m', "message", &msg, "MSG",
@@ -342,6 +459,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
459
"reuse specified note object", parse_reuse_arg),
460
OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
461
OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
462
+ OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
463
+ "load rewriting config for <command> (implies --stdin)"),
464
OPT_END()
465
};
466
@@ -390,6 +509,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
509
usage_with_options(git_notes_usage, options);
510
}
511
512
+ if (!copy && rewrite_cmd) {
513
+ error("cannot use --for-rewrite with %s subcommand.", argv[0]);
514
+ usage_with_options(git_notes_usage, options);
515
+ }
516
if (!copy && from_stdin) {
517
error("cannot use --stdin with %s subcommand.", argv[0]);
518
usage_with_options(git_notes_usage, options);
@@ -397,12 +520,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
520
521
if (copy) {
522
const char *from_ref;
400
- if (from_stdin) {
523
+ if (from_stdin || rewrite_cmd) {
524
if (argc > 1) {
525
error("too many parameters");
526
usage_with_options(git_notes_usage, options);
527
} else {
405
- return notes_copy_from_stdin(force);
528
+ return notes_copy_from_stdin(force, rewrite_cmd);
529
}
530
}
531
if (argc < 3) {
builtin.h
+17
@@ -20,6 +20,23 @@ extern int commit_tree(const char *msg, unsigned char *tree,
20
struct commit_list *parents, unsigned char *ret,
21
const char *author);
22
extern int commit_notes(struct notes_tree *t, const char *msg);
23
+
24
+struct notes_rewrite_cfg {
25
+ struct notes_tree **trees;
26
+ const char *cmd;
27
+ int enabled;
28
+ combine_notes_fn *combine;
29
+ struct string_list *refs;
30
+ int refs_from_env;
31
+ int mode_from_env;
32
+};
33
+
34
+combine_notes_fn *parse_combine_notes_fn(const char *v);
35
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
36
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
37
+ const unsigned char *from_obj, const unsigned char *to_obj);
38
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
39
+
40
extern int check_pager_config(const char *cmd);
41
42
extern int cmd_add(int argc, const char **argv, const char *prefix);
cache.h
+2
@@ -386,6 +386,8 @@ static inline enum object_type object_type(unsigned int mode)
386
#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
387
#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
388
#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
389
+#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
390
+#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
391
392
extern int is_bare_repository_cfg;
393
extern int is_bare_repository(void);
t/t3301-notes.sh
+195
@@ -810,4 +810,199 @@ test_expect_success 'git notes copy --stdin' '
810
test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
811
'
812
813
+cat > expect << EOF
814
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
815
+Author: A U Thor <author@example.com>
816
+Date: Thu Apr 7 15:27:13 2005 -0700
817
+
818
+ 15th
819
+
820
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
821
+Author: A U Thor <author@example.com>
822
+Date: Thu Apr 7 15:26:13 2005 -0700
823
+
824
+ 14th
825
+EOF
826
+
827
+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
828
+ test_commit 14th &&
829
+ test_commit 15th &&
830
+ (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
831
+ echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
832
+ git notes copy --for-rewrite=foo &&
833
+ git log -2 > output &&
834
+ test_cmp expect output
835
+'
836
+
837
+cat > expect << EOF
838
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
839
+Author: A U Thor <author@example.com>
840
+Date: Thu Apr 7 15:27:13 2005 -0700
841
+
842
+ 15th
843
+
844
+Notes (other):
845
+ yet another note
846
+$whitespace
847
+ yet another note
848
+
849
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
850
+Author: A U Thor <author@example.com>
851
+Date: Thu Apr 7 15:26:13 2005 -0700
852
+
853
+ 14th
854
+
855
+Notes (other):
856
+ other note
857
+$whitespace
858
+ yet another note
859
+EOF
860
+
861
+test_expect_success 'git notes copy --for-rewrite (enabled)' '
862
+ git config notes.rewriteMode overwrite &&
863
+ git config notes.rewriteRef "refs/notes/*" &&
864
+ (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
865
+ echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
866
+ git notes copy --for-rewrite=foo &&
867
+ git log -2 > output &&
868
+ test_cmp expect output
869
+'
870
+
871
+test_expect_success 'git notes copy --for-rewrite (disabled)' '
872
+ git config notes.rewrite.bar false &&
873
+ echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
874
+ git notes copy --for-rewrite=bar &&
875
+ git log -2 > output &&
876
+ test_cmp expect output
877
+'
878
+
879
+cat > expect << EOF
880
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
881
+Author: A U Thor <author@example.com>
882
+Date: Thu Apr 7 15:27:13 2005 -0700
883
+
884
+ 15th
885
+
886
+Notes (other):
887
+ a fresh note
888
+EOF
889
+
890
+test_expect_success 'git notes copy --for-rewrite (overwrite)' '
891
+ git notes add -f -m"a fresh note" HEAD^ &&
892
+ echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
893
+ git notes copy --for-rewrite=foo &&
894
+ git log -1 > output &&
895
+ test_cmp expect output
896
+'
897
+
898
+test_expect_success 'git notes copy --for-rewrite (ignore)' '
899
+ git config notes.rewriteMode ignore &&
900
+ echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
901
+ git notes copy --for-rewrite=foo &&
902
+ git log -1 > output &&
903
+ test_cmp expect output
904
+'
905
+
906
+cat > expect << EOF
907
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
908
+Author: A U Thor <author@example.com>
909
+Date: Thu Apr 7 15:27:13 2005 -0700
910
+
911
+ 15th
912
+
913
+Notes (other):
914
+ a fresh note
915
+ another fresh note
916
+EOF
917
+
918
+test_expect_success 'git notes copy --for-rewrite (append)' '
919
+ git notes add -f -m"another fresh note" HEAD^ &&
920
+ git config notes.rewriteMode concatenate &&
921
+ echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
922
+ git notes copy --for-rewrite=foo &&
923
+ git log -1 > output &&
924
+ test_cmp expect output
925
+'
926
+
927
+cat > expect << EOF
928
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
929
+Author: A U Thor <author@example.com>
930
+Date: Thu Apr 7 15:27:13 2005 -0700
931
+
932
+ 15th
933
+
934
+Notes (other):
935
+ a fresh note
936
+ another fresh note
937
+ append 1
938
+ append 2
939
+EOF
940
+
941
+test_expect_success 'git notes copy --for-rewrite (append two to one)' '
942
+ git notes add -f -m"append 1" HEAD^ &&
943
+ git notes add -f -m"append 2" HEAD^^ &&
944
+ (echo $(git rev-parse HEAD^) $(git rev-parse HEAD);
945
+ echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |
946
+ git notes copy --for-rewrite=foo &&
947
+ git log -1 > output &&
948
+ test_cmp expect output
949
+'
950
+
951
+test_expect_success 'git notes copy --for-rewrite (append empty)' '
952
+ git notes remove HEAD^ &&
953
+ echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
954
+ git notes copy --for-rewrite=foo &&
955
+ git log -1 > output &&
956
+ test_cmp expect output
957
+'
958
+
959
+cat > expect << EOF
960
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
961
+Author: A U Thor <author@example.com>
962
+Date: Thu Apr 7 15:27:13 2005 -0700
963
+
964
+ 15th
965
+
966
+Notes (other):
967
+ replacement note 1
968
+EOF
969
+
970
+test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
971
+ git notes add -f -m"replacement note 1" HEAD^ &&
972
+ echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
973
+ GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&
974
+ git log -1 > output &&
975
+ test_cmp expect output
976
+'
977
+
978
+cat > expect << EOF
979
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
980
+Author: A U Thor <author@example.com>
981
+Date: Thu Apr 7 15:27:13 2005 -0700
982
+
983
+ 15th
984
+
985
+Notes (other):
986
+ replacement note 2
987
+EOF
988
+
989
+test_expect_success 'GIT_NOTES_REWRITE_REF works' '
990
+ git config notes.rewriteMode overwrite &&
991
+ git notes add -f -m"replacement note 2" HEAD^ &&
992
+ git config --unset-all notes.rewriteRef &&
993
+ echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
994
+ GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \
995
+ git notes copy --for-rewrite=foo &&
996
+ git log -1 > output &&
997
+ test_cmp expect output
998
+'
999
+
1000
+test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '
1001
+ git config notes.rewriteRef refs/notes/other &&
1002
+ git notes add -f -m"replacement note 3" HEAD^ &&
1003
+ echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
1004
+ GIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo &&
1005
+ git log -1 > output &&
1006
+ test_cmp expect output
1007
+'
1008
test_done
t/test-lib.sh
+2
@@ -56,6 +56,8 @@ unset SHA1_FILE_DIRECTORIES
56
unset SHA1_FILE_DIRECTORY
57
unset GIT_NOTES_REF
58
unset GIT_NOTES_DISPLAY_REF
59
+unset GIT_NOTES_REWRITE_REF
60
+unset GIT_NOTES_REWRITE_MODE
61
GIT_MERGE_VERBOSITY=5
62
export GIT_MERGE_VERBOSITY
63
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME