merge-ll: expose revision names to custom drivers

Custom merge drivers need access to the names of the revisions they are working on, so that the merge conflict markers they introduce can refer to those revisions. The placeholders '%S', '%X' and '%Y' are introduced to this end. Signed-off-by: Antonin Delpeuch <antonin@delpeuch.eu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Antonin Delpeuch committed Jan 24, 2024 at 20:09 UTC 81effe94682dbfed55171468074db85fa661cc21
3 files changed +30 -12
Documentation/gitattributes.txt
+5 -4
@@ -1137,11 +1137,11 @@ The `merge.*.name` variable gives the driver a human-readable
1137 name.
1138
1139 The `merge.*.driver` variable's value is used to construct a
1140 -command to run to merge ancestor's version (`%O`), current
1140 +command to run to common ancestor's version (`%O`), current
1141 version (`%A`) and the other branches' version (`%B`). These
1142 three tokens are replaced with the names of temporary files that
1143 hold the contents of these versions when the command line is
1144 -built. Additionally, %L will be replaced with the conflict marker
1144 +built. Additionally, `%L` will be replaced with the conflict marker
1145 size (see below).
1146
1147 The merge driver is expected to leave the result of the merge in
@@ -1159,8 +1159,9 @@ When left unspecified, the driver itself is used for both
1159 internal merge and the final merge.
1160
1161 The merge driver can learn the pathname in which the merged result
1162 -will be stored via placeholder `%P`.
1163 -
1162 +will be stored via placeholder `%P`. The conflict labels to be used
1163 +for the common ancestor, local head and other head can be passed by
1164 +using '%S', '%X' and '%Y` respectively.
1165
1166 `conflict-marker-size`
1167 ^^^^^^^^^^^^^^^^^^^^^^
merge-ll.c
+14 -3
@@ -185,9 +185,9 @@ static void create_temp(mmfile_t *src, char *path, size_t len)
185 static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
186 mmbuffer_t *result,
187 const char *path,
188 - mmfile_t *orig, const char *orig_name UNUSED,
189 - mmfile_t *src1, const char *name1 UNUSED,
190 - mmfile_t *src2, const char *name2 UNUSED,
188 + mmfile_t *orig, const char *orig_name,
189 + mmfile_t *src1, const char *name1,
190 + mmfile_t *src2, const char *name2,
191 const struct ll_merge_options *opts,
192 int marker_size)
193 {
@@ -222,6 +222,12 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
222 strbuf_addf(&cmd, "%d", marker_size);
223 else if (skip_prefix(format, "P", &format))
224 sq_quote_buf(&cmd, path);
225 + else if (skip_prefix(format, "S", &format))
226 + sq_quote_buf(&cmd, orig_name ? orig_name : "");
227 + else if (skip_prefix(format, "X", &format))
228 + sq_quote_buf(&cmd, name1 ? name1 : "");
229 + else if (skip_prefix(format, "Y", &format))
230 + sq_quote_buf(&cmd, name2 ? name2 : "");
231 else
232 strbuf_addch(&cmd, '%');
233 }
@@ -315,7 +321,12 @@ static int read_merge_config(const char *var, const char *value,
321 * %B - temporary file name for the other branches' version.
322 * %L - conflict marker length
323 * %P - the original path (safely quoted for the shell)
324 + * %S - the revision for the merge base
325 + * %X - the revision for our version
326 + * %Y - the revision for their version
327 *
328 + * If the file is not named indentically in all versions, then each
329 + * revision is joined with the corresponding path, separated by a colon.
330 * The external merge driver should write the results in the
331 * file named by %A, and signal that it has done with zero exit
332 * status.
t/t6406-merge-attr.sh
+11 -5
@@ -42,11 +42,15 @@ test_expect_success setup '
42 #!/bin/sh
43
44 orig="$1" ours="$2" theirs="$3" exit="$4" path=$5
45 + orig_name="$6" our_name="$7" their_name="$8"
46 (
47 echo "orig is $orig"
48 echo "ours is $ours"
49 echo "theirs is $theirs"
50 echo "path is $path"
51 + echo "orig_name is $orig_name"
52 + echo "our_name is $our_name"
53 + echo "their_name is $their_name"
54 echo "=== orig ==="
55 cat "$orig"
56 echo "=== ours ==="
@@ -121,7 +125,7 @@ test_expect_success 'custom merge backend' '
125
126 git reset --hard anchor &&
127 git config --replace-all \
124 - merge.custom.driver "./custom-merge %O %A %B 0 %P" &&
128 + merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" &&
129 git config --replace-all \
130 merge.custom.name "custom merge driver for testing" &&
131
@@ -132,7 +136,8 @@ test_expect_success 'custom merge backend' '
136 o=$(git unpack-file main^:text) &&
137 a=$(git unpack-file side^:text) &&
138 b=$(git unpack-file main:text) &&
135 - sh -c "./custom-merge $o $a $b 0 text" &&
139 + base_revid=$(git rev-parse --short main^) &&
140 + sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" &&
141 sed -e 1,3d $a >check-2 &&
142 cmp check-1 check-2 &&
143 rm -f $o $a $b
@@ -142,7 +147,7 @@ test_expect_success 'custom merge backend' '
147
148 git reset --hard anchor &&
149 git config --replace-all \
145 - merge.custom.driver "./custom-merge %O %A %B 1 %P" &&
150 + merge.custom.driver "./custom-merge %O %A %B 1 %P %S %X %Y" &&
151 git config --replace-all \
152 merge.custom.name "custom merge driver for testing" &&
153
@@ -159,7 +164,8 @@ test_expect_success 'custom merge backend' '
164 o=$(git unpack-file main^:text) &&
165 a=$(git unpack-file anchor:text) &&
166 b=$(git unpack-file main:text) &&
162 - sh -c "./custom-merge $o $a $b 0 text" &&
167 + base_revid=$(git rev-parse --short main^) &&
168 + sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" &&
169 sed -e 1,3d $a >check-2 &&
170 cmp check-1 check-2 &&
171 sed -e 1,3d -e 4q $a >check-3 &&
@@ -173,7 +179,7 @@ test_expect_success !WINDOWS 'custom merge driver that is killed with a signal'
179
180 git reset --hard anchor &&
181 git config --replace-all \
176 - merge.custom.driver "./custom-merge %O %A %B 0 %P" &&
182 + merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" &&
183 git config --replace-all \
184 merge.custom.name "custom merge driver for testing" &&
185