Skip to content

Commit a6a0909

Browse files
jherlandgitster
authored andcommitted
git notes merge: Add another auto-resolving strategy: "cat_sort_uniq"
This new strategy is similar to "concatenate", but in addition to concatenating the two note candidates, this strategy sorts the resulting lines, and removes duplicate lines from the result. This is equivalent to applying the "cat | sort | uniq" shell pipeline to the two note candidates. This strategy is useful if the notes follow a line-based format where one wants to avoid duplicate lines in the merge result. Note that if either of the note candidates contain duplicate lines _prior_ to the merge, these will also be removed by this merge strategy. The patch also contains tests and documentation for the new strategy. Signed-off-by: Johan Herland <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 6cfd6a9 commit a6a0909

File tree

7 files changed

+248
-4
lines changed

7 files changed

+248
-4
lines changed

Documentation/git-notes.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ OPTIONS
155155
--strategy=<strategy>::
156156
When merging notes, resolve notes conflicts using the given
157157
strategy. The following strategies are recognized: "manual"
158-
(default), "ours", "theirs" and "union".
158+
(default), "ours", "theirs", "union" and "cat_sort_uniq".
159159
See the "NOTES MERGE STRATEGIES" section below for more
160160
information on each notes merge strategy.
161161

@@ -230,6 +230,16 @@ ref).
230230
"union" automatically resolves notes conflicts by concatenating the
231231
local and remote versions.
232232

233+
"cat_sort_uniq" is similar to "union", but in addition to concatenating
234+
the local and remote versions, this strategy also sorts the resulting
235+
lines, and removes duplicate lines from the result. This is equivalent
236+
to applying the "cat | sort | uniq" shell pipeline to the local and
237+
remote versions. This strategy is useful if the notes follow a line-based
238+
format where one wants to avoid duplicated lines in the merge result.
239+
Note that if either the local or remote version contain duplicate lines
240+
prior to the merge, these will also be removed by this notes merge
241+
strategy.
242+
233243

234244
EXAMPLES
235245
--------

builtin/notes.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v)
324324
return combine_notes_ignore;
325325
else if (!strcasecmp(v, "concatenate"))
326326
return combine_notes_concatenate;
327+
else if (!strcasecmp(v, "cat_sort_uniq"))
328+
return combine_notes_cat_sort_uniq;
327329
else
328330
return NULL;
329331
}
@@ -846,8 +848,8 @@ static int merge(int argc, const char **argv, const char *prefix)
846848
OPT__VERBOSITY(&verbosity),
847849
OPT_GROUP("Merge options"),
848850
OPT_STRING('s', "strategy", &strategy, "strategy",
849-
"resolve notes conflicts using the given "
850-
"strategy (manual/ours/theirs/union)"),
851+
"resolve notes conflicts using the given strategy "
852+
"(manual/ours/theirs/union/cat_sort_uniq)"),
851853
OPT_GROUP("Committing unmerged notes"),
852854
{ OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
853855
"finalize notes merge by committing unmerged notes",
@@ -899,6 +901,8 @@ static int merge(int argc, const char **argv, const char *prefix)
899901
o.strategy = NOTES_MERGE_RESOLVE_THEIRS;
900902
else if (!strcmp(strategy, "union"))
901903
o.strategy = NOTES_MERGE_RESOLVE_UNION;
904+
else if (!strcmp(strategy, "cat_sort_uniq"))
905+
o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
902906
else {
903907
error("Unknown -s/--strategy: %s", strategy);
904908
usage_with_options(git_notes_merge_usage, options);

notes-merge.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,13 @@ static int merge_one_change(struct notes_merge_options *o,
453453
die("failed to concatenate notes "
454454
"(combine_notes_concatenate)");
455455
return 0;
456+
case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ:
457+
OUTPUT(o, 2, "Concatenating unique lines in local and remote "
458+
"notes for %s", sha1_to_hex(p->obj));
459+
if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq))
460+
die("failed to concatenate notes "
461+
"(combine_notes_cat_sort_uniq)");
462+
return 0;
456463
}
457464
die("Unknown strategy (%i).", o->strategy);
458465
}

notes-merge.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ struct notes_merge_options {
1717
NOTES_MERGE_RESOLVE_MANUAL = 0,
1818
NOTES_MERGE_RESOLVE_OURS,
1919
NOTES_MERGE_RESOLVE_THEIRS,
20-
NOTES_MERGE_RESOLVE_UNION
20+
NOTES_MERGE_RESOLVE_UNION,
21+
NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ
2122
} strategy;
2223
unsigned has_worktree:1;
2324
};

notes.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,82 @@ int combine_notes_ignore(unsigned char *cur_sha1,
845845
return 0;
846846
}
847847

848+
static int string_list_add_note_lines(struct string_list *sort_uniq_list,
849+
const unsigned char *sha1)
850+
{
851+
char *data;
852+
unsigned long len;
853+
enum object_type t;
854+
struct strbuf buf = STRBUF_INIT;
855+
struct strbuf **lines = NULL;
856+
int i, list_index;
857+
858+
if (is_null_sha1(sha1))
859+
return 0;
860+
861+
/* read_sha1_file NUL-terminates */
862+
data = read_sha1_file(sha1, &t, &len);
863+
if (t != OBJ_BLOB || !data || !len) {
864+
free(data);
865+
return t != OBJ_BLOB || !data;
866+
}
867+
868+
strbuf_attach(&buf, data, len, len + 1);
869+
lines = strbuf_split(&buf, '\n');
870+
871+
for (i = 0; lines[i]; i++) {
872+
if (lines[i]->buf[lines[i]->len - 1] == '\n')
873+
strbuf_setlen(lines[i], lines[i]->len - 1);
874+
if (!lines[i]->len)
875+
continue; /* skip empty lines */
876+
list_index = string_list_find_insert_index(sort_uniq_list,
877+
lines[i]->buf, 0);
878+
if (list_index < 0)
879+
continue; /* skip duplicate lines */
880+
string_list_insert_at_index(sort_uniq_list, list_index,
881+
lines[i]->buf);
882+
}
883+
884+
strbuf_list_free(lines);
885+
strbuf_release(&buf);
886+
return 0;
887+
}
888+
889+
static int string_list_join_lines_helper(struct string_list_item *item,
890+
void *cb_data)
891+
{
892+
struct strbuf *buf = cb_data;
893+
strbuf_addstr(buf, item->string);
894+
strbuf_addch(buf, '\n');
895+
return 0;
896+
}
897+
898+
int combine_notes_cat_sort_uniq(unsigned char *cur_sha1,
899+
const unsigned char *new_sha1)
900+
{
901+
struct string_list sort_uniq_list = { NULL, 0, 0, 1 };
902+
struct strbuf buf = STRBUF_INIT;
903+
int ret = 1;
904+
905+
/* read both note blob objects into unique_lines */
906+
if (string_list_add_note_lines(&sort_uniq_list, cur_sha1))
907+
goto out;
908+
if (string_list_add_note_lines(&sort_uniq_list, new_sha1))
909+
goto out;
910+
911+
/* create a new blob object from sort_uniq_list */
912+
if (for_each_string_list(&sort_uniq_list,
913+
string_list_join_lines_helper, &buf))
914+
goto out;
915+
916+
ret = write_sha1_file(buf.buf, buf.len, blob_type, cur_sha1);
917+
918+
out:
919+
strbuf_release(&buf);
920+
string_list_clear(&sort_uniq_list, 0);
921+
return ret;
922+
}
923+
848924
static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
849925
int flag, void *cb)
850926
{

notes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ typedef int (*combine_notes_fn)(unsigned char *cur_sha1, const unsigned char *ne
2727
int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1);
2828
int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1);
2929
int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1);
30+
int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, const unsigned char *new_sha1);
3031

3132
/*
3233
* Notes tree object

t/t3309-notes-merge-auto-resolve.sh

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,4 +499,149 @@ test_expect_success 'merge z into y with "union" strategy => Non-conflicting 3-w
499499
verify_notes y union
500500
'
501501

502+
test_expect_success 'reset to pre-merge state (y)' '
503+
git update-ref refs/notes/y refs/notes/y^1 &&
504+
# Verify pre-merge state
505+
verify_notes y y
506+
'
507+
508+
cat <<EOF | sort >expect_notes_union2
509+
d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15
510+
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
511+
3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
512+
a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
513+
7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
514+
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
515+
851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
516+
357b6ca14c7afd59b7f8b8aaaa6b8b723771135b $commit_sha5
517+
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
518+
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
519+
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
520+
EOF
521+
522+
cat >expect_log_union2 <<EOF
523+
$commit_sha15 15th
524+
z notes on 15th commit
525+
526+
y notes on 15th commit
527+
528+
$commit_sha14 14th
529+
y notes on 14th commit
530+
531+
$commit_sha13 13th
532+
y notes on 13th commit
533+
534+
$commit_sha12 12th
535+
y notes on 12th commit
536+
537+
$commit_sha11 11th
538+
z notes on 11th commit
539+
540+
$commit_sha10 10th
541+
x notes on 10th commit
542+
543+
$commit_sha9 9th
544+
545+
$commit_sha8 8th
546+
z notes on 8th commit
547+
548+
$commit_sha7 7th
549+
550+
$commit_sha6 6th
551+
552+
$commit_sha5 5th
553+
z notes on 5th commit
554+
555+
y notes on 5th commit
556+
557+
$commit_sha4 4th
558+
y notes on 4th commit
559+
560+
$commit_sha3 3rd
561+
y notes on 3rd commit
562+
563+
$commit_sha2 2nd
564+
z notes on 2nd commit
565+
566+
$commit_sha1 1st
567+
568+
EOF
569+
570+
test_expect_success 'merge y into z with "union" strategy => Non-conflicting 3-way merge' '
571+
git config core.notesRef refs/notes/z &&
572+
git notes merge --strategy=union y &&
573+
verify_notes z union2
574+
'
575+
576+
test_expect_success 'reset to pre-merge state (z)' '
577+
git update-ref refs/notes/z refs/notes/z^1 &&
578+
# Verify pre-merge state
579+
verify_notes z z
580+
'
581+
582+
cat <<EOF | sort >expect_notes_cat_sort_uniq
583+
6be90240b5f54594203e25d9f2f64b7567175aee $commit_sha15
584+
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
585+
3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
586+
a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
587+
7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
588+
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
589+
851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
590+
660311d7f78dc53db12ac373a43fca7465381a7e $commit_sha5
591+
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
592+
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
593+
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
594+
EOF
595+
596+
cat >expect_log_cat_sort_uniq <<EOF
597+
$commit_sha15 15th
598+
y notes on 15th commit
599+
z notes on 15th commit
600+
601+
$commit_sha14 14th
602+
y notes on 14th commit
603+
604+
$commit_sha13 13th
605+
y notes on 13th commit
606+
607+
$commit_sha12 12th
608+
y notes on 12th commit
609+
610+
$commit_sha11 11th
611+
z notes on 11th commit
612+
613+
$commit_sha10 10th
614+
x notes on 10th commit
615+
616+
$commit_sha9 9th
617+
618+
$commit_sha8 8th
619+
z notes on 8th commit
620+
621+
$commit_sha7 7th
622+
623+
$commit_sha6 6th
624+
625+
$commit_sha5 5th
626+
y notes on 5th commit
627+
z notes on 5th commit
628+
629+
$commit_sha4 4th
630+
y notes on 4th commit
631+
632+
$commit_sha3 3rd
633+
y notes on 3rd commit
634+
635+
$commit_sha2 2nd
636+
z notes on 2nd commit
637+
638+
$commit_sha1 1st
639+
640+
EOF
641+
642+
test_expect_success 'merge y into z with "cat_sort_uniq" strategy => Non-conflicting 3-way merge' '
643+
git notes merge --strategy=cat_sort_uniq y &&
644+
verify_notes z cat_sort_uniq
645+
'
646+
502647
test_done

0 commit comments

Comments
 (0)