cat-file: add mailmap subcommand to --batch-command

git-cat-file(1)'s --batch-command works with the --use-mailmap option, but this option needs to be set when the process is created. This means we cannot change this option mid-operation. At GitLab, Gitaly keeps interacting with a long-lived git-cat-file process and it would be useful if --batch-command supported toggling mailmap dynamically on an existing process. Add a `mailmap` subcommand to --batch-command that takes a boolean argument (usual ways you can specify a boolean value like 'yes', 'true', etc., are supported). Mailmap data is loaded lazily and kept in memory, while a helper centralizes the one-time load path used both at startup and from the batch-command handler. Extend tests to cover runtime toggling, startup option interactions (`--mailmap`/`--no-mailmap`), accepted boolean forms, and invalid values. Signed-off-by: Siddharth Asthana <siddharthasthana31@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Siddharth Asthana committed Apr 16, 2026 at 09:02 UTC 6da647d8522641f932528de0167413719a38a2c0
3 files changed +143 -4
Documentation/git-cat-file.adoc
+5
@@ -174,6 +174,11 @@ flush::
174 since the beginning or since the last flush was issued. When `--buffer`
175 is used, no output will come until a `flush` is issued. When `--buffer`
176 is not used, commands are flushed each time without issuing `flush`.
177 +
178 +`mailmap (<bool>)`::
179 + Enable or disable mailmap for subsequent commands. The `<bool>`
180 + argument accepts the same boolean values as linkgit:git-config[1].
181 + The mailmap data is read upon the first use and only once.
182 --
183 +
184
builtin/cat-file.c
+33 -4
@@ -57,6 +57,20 @@ static int use_mailmap;
57
58 static char *replace_idents_using_mailmap(char *, size_t *);
59
60 +/*
61 + * The mailmap is initialized with .strdup_strings set to 0,
62 + * but read_mailmap() sets the bit to 1 (this is true even when
63 + * not a single mailmap entry is read), so it can be used for
64 + * lazy loading.
65 + */
66 +static void load_mailmap(void)
67 +{
68 + if (mailmap.strdup_strings)
69 + return;
70 +
71 + read_mailmap(the_repository, &mailmap);
72 +}
73 +
74 static char *replace_idents_using_mailmap(char *object_buf, size_t *size)
75 {
76 struct strbuf sb = STRBUF_INIT;
@@ -692,6 +706,20 @@ static void parse_cmd_info(struct batch_options *opt,
706 batch_one_object(line, output, opt, data);
707 }
708
709 +static void parse_cmd_mailmap(struct batch_options *opt UNUSED,
710 + const char *line,
711 + struct strbuf *output UNUSED,
712 + struct expand_data *data UNUSED)
713 +{
714 + use_mailmap = git_parse_maybe_bool(line);
715 +
716 + if (use_mailmap < 0)
717 + die(_("mailmap: invalid boolean '%s'"), line);
718 +
719 + if (use_mailmap)
720 + load_mailmap();
721 +}
722 +
723 static void dispatch_calls(struct batch_options *opt,
724 struct strbuf *output,
725 struct expand_data *data,
@@ -725,9 +753,10 @@ static const struct parse_cmd {
753 parse_cmd_fn_t fn;
754 unsigned takes_args;
755 } commands[] = {
728 - { "contents", parse_cmd_contents, 1},
729 - { "info", parse_cmd_info, 1},
730 - { "flush", NULL, 0},
756 + { "contents", parse_cmd_contents, 1 },
757 + { "info", parse_cmd_info, 1 },
758 + { "flush", NULL, 0 },
759 + { "mailmap", parse_cmd_mailmap, 1 },
760 };
761
762 static void batch_objects_command(struct batch_options *opt,
@@ -1128,7 +1157,7 @@ int cmd_cat_file(int argc,
1157 opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's');
1158
1159 if (use_mailmap)
1131 - read_mailmap(the_repository, &mailmap);
1160 + load_mailmap();
1161
1162 switch (batch.objects_filter.choice) {
1163 case LOFC_DISABLED:
t/t4203-mailmap.sh
+105
@@ -1133,6 +1133,111 @@ test_expect_success 'git cat-file --batch-command returns correct size with --us
1133 test_cmp expect actual
1134 '
1135
1136 +test_expect_success 'git cat-file --batch-command mailmap yes enables mailmap mid-stream' '
1137 + test_when_finished "rm .mailmap" &&
1138 + cat >.mailmap <<-\EOF &&
1139 + C O Mitter <committer@example.com> Orig <orig@example.com>
1140 + EOF
1141 + commit_sha=$(git rev-parse HEAD) &&
1142 + git cat-file commit HEAD >commit_no_mailmap.out &&
1143 + git cat-file --use-mailmap commit HEAD >commit_mailmap.out &&
1144 + size_no_mailmap=$(wc -c <commit_no_mailmap.out) &&
1145 + size_mailmap=$(wc -c <commit_mailmap.out) &&
1146 + printf "info HEAD\nmailmap yes\ninfo HEAD\n" | git cat-file --batch-command >actual &&
1147 + echo $commit_sha commit $size_no_mailmap >expect &&
1148 + echo $commit_sha commit $size_mailmap >>expect &&
1149 + test_cmp expect actual
1150 +'
1151 +
1152 +test_expect_success 'git cat-file --batch-command mailmap no disables mailmap mid-stream' '
1153 + test_when_finished "rm .mailmap" &&
1154 + cat >.mailmap <<-\EOF &&
1155 + C O Mitter <committer@example.com> Orig <orig@example.com>
1156 + EOF
1157 + commit_sha=$(git rev-parse HEAD) &&
1158 + git cat-file commit HEAD >commit_no_mailmap.out &&
1159 + git cat-file --use-mailmap commit HEAD >commit_mailmap.out &&
1160 + size_no_mailmap=$(wc -c <commit_no_mailmap.out) &&
1161 + size_mailmap=$(wc -c <commit_mailmap.out) &&
1162 + printf "mailmap yes\ninfo HEAD\nmailmap no\ninfo HEAD\n" | git cat-file --batch-command >actual &&
1163 + echo $commit_sha commit $size_mailmap >expect &&
1164 + echo $commit_sha commit $size_no_mailmap >>expect &&
1165 + test_cmp expect actual
1166 +'
1167 +
1168 +test_expect_success 'git cat-file --batch-command mailmap works in --buffer mode' '
1169 + test_when_finished "rm .mailmap" &&
1170 + cat >.mailmap <<-\EOF &&
1171 + C O Mitter <committer@example.com> Orig <orig@example.com>
1172 + EOF
1173 + commit_sha=$(git rev-parse HEAD) &&
1174 + git cat-file commit HEAD >commit_no_mailmap.out &&
1175 + git cat-file --use-mailmap commit HEAD >commit_mailmap.out &&
1176 + size_no_mailmap=$(wc -c <commit_no_mailmap.out) &&
1177 + size_mailmap=$(wc -c <commit_mailmap.out) &&
1178 + printf "mailmap yes\ninfo HEAD\nmailmap no\ninfo HEAD\nflush\n" | git cat-file --batch-command --buffer >actual &&
1179 + echo $commit_sha commit $size_mailmap >expect &&
1180 + echo $commit_sha commit $size_no_mailmap >>expect &&
1181 + test_cmp expect actual
1182 +'
1183 +
1184 +test_expect_success 'git cat-file --batch-command mailmap no overrides startup --mailmap' '
1185 + test_when_finished "rm .mailmap" &&
1186 + cat >.mailmap <<-\EOF &&
1187 + C O Mitter <committer@example.com> Orig <orig@example.com>
1188 + EOF
1189 + commit_sha=$(git rev-parse HEAD) &&
1190 + git cat-file --use-mailmap commit HEAD >commit_mailmap.out &&
1191 + size_mailmap=$(wc -c <commit_mailmap.out) &&
1192 + git cat-file commit HEAD >commit_no_mailmap.out &&
1193 + size_no_mailmap=$(wc -c <commit_no_mailmap.out) &&
1194 + printf "info HEAD\nmailmap no\ninfo HEAD\n" | \
1195 + git cat-file --mailmap --batch-command >actual &&
1196 + echo $commit_sha commit $size_mailmap >expect &&
1197 + echo $commit_sha commit $size_no_mailmap >>expect &&
1198 + test_cmp expect actual
1199 +'
1200 +
1201 +test_expect_success 'git cat-file --batch-command mailmap yes overrides startup --no-mailmap' '
1202 + test_when_finished "rm .mailmap" &&
1203 + cat >.mailmap <<-\EOF &&
1204 + C O Mitter <committer@example.com> Orig <orig@example.com>
1205 + EOF
1206 + commit_sha=$(git rev-parse HEAD) &&
1207 + git cat-file commit HEAD >commit_no_mailmap.out &&
1208 + size_no_mailmap=$(wc -c <commit_no_mailmap.out) &&
1209 + git cat-file --use-mailmap commit HEAD >commit_mailmap.out &&
1210 + size_mailmap=$(wc -c <commit_mailmap.out) &&
1211 + printf "info HEAD\nmailmap yes\ninfo HEAD\n" | \
1212 + git cat-file --no-mailmap --batch-command >actual &&
1213 + echo $commit_sha commit $size_no_mailmap >expect &&
1214 + echo $commit_sha commit $size_mailmap >>expect &&
1215 + test_cmp expect actual
1216 +'
1217 +
1218 +test_expect_success 'git cat-file --batch-command mailmap accepts true/false' '
1219 + test_when_finished "rm .mailmap" &&
1220 + cat >.mailmap <<-\EOF &&
1221 + C O Mitter <committer@example.com> Orig <orig@example.com>
1222 + EOF
1223 + commit_sha=$(git rev-parse HEAD) &&
1224 + git cat-file commit HEAD >commit_no_mailmap.out &&
1225 + size_no_mailmap=$(wc -c <commit_no_mailmap.out) &&
1226 + git cat-file --use-mailmap commit HEAD >commit_mailmap.out &&
1227 + size_mailmap=$(wc -c <commit_mailmap.out) &&
1228 + printf "mailmap true\ninfo HEAD\nmailmap false\ninfo HEAD\n" | \
1229 + git cat-file --batch-command >actual &&
1230 + echo $commit_sha commit $size_mailmap >expect &&
1231 + echo $commit_sha commit $size_no_mailmap >>expect &&
1232 + test_cmp expect actual
1233 +'
1234 +
1235 +test_expect_success 'git cat-file --batch-command mailmap rejects invalid boolean' '
1236 + echo "mailmap maybe" >in &&
1237 + test_must_fail git cat-file --batch-command <in 2>err &&
1238 + test_grep "mailmap: invalid boolean .*maybe" err
1239 +'
1240 +
1241 test_expect_success 'git cat-file --mailmap works with different author and committer' '
1242 test_when_finished "rm .mailmap" &&
1243 cat >.mailmap <<-\EOF &&