replace: add --graft option

The usage string for this option is: git replace [-f] --graft <commit> [<parent>...] First we create a new commit that is the same as <commit> except that its parents are [<parents>...] Then we create a replace ref that replace <commit> with the commit we just created. With this new option, it should be straightforward to convert grafts to replace refs. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Christian Couder committed Jul 19, 2014 at 17:01 UTC 4228e8bc989c6cac1c47faece720faa06531281f
1 file changed +73 -1
builtin/replace.c
+73 -1
@@ -17,6 +17,7 @@
17 static const char * const git_replace_usage[] = {
18 N_("git replace [-f] <object> <replacement>"),
19 N_("git replace [-f] --edit <object>"),
20 + N_("git replace [-f] --graft <commit> [<parent>...]"),
21 N_("git replace -d <object>..."),
22 N_("git replace [--format=<format>] [-l [<pattern>]]"),
23 NULL
@@ -294,6 +295,66 @@ static int edit_and_replace(const char *object_ref, int force)
295 return replace_object_sha1(object_ref, old, "replacement", new, force);
296 }
297
298 +static void replace_parents(struct strbuf *buf, int argc, const char **argv)
299 +{
300 + struct strbuf new_parents = STRBUF_INIT;
301 + const char *parent_start, *parent_end;
302 + int i;
303 +
304 + /* find existing parents */
305 + parent_start = buf->buf;
306 + parent_start += 46; /* "tree " + "hex sha1" + "\n" */
307 + parent_end = parent_start;
308 +
309 + while (starts_with(parent_end, "parent "))
310 + parent_end += 48; /* "parent " + "hex sha1" + "\n" */
311 +
312 + /* prepare new parents */
313 + for (i = 0; i < argc; i++) {
314 + unsigned char sha1[20];
315 + if (get_sha1(argv[i], sha1) < 0)
316 + die(_("Not a valid object name: '%s'"), argv[i]);
317 + lookup_commit_or_die(sha1, argv[i]);
318 + strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1));
319 + }
320 +
321 + /* replace existing parents with new ones */
322 + strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start,
323 + new_parents.buf, new_parents.len);
324 +
325 + strbuf_release(&new_parents);
326 +}
327 +
328 +static int create_graft(int argc, const char **argv, int force)
329 +{
330 + unsigned char old[20], new[20];
331 + const char *old_ref = argv[0];
332 + struct commit *commit;
333 + struct strbuf buf = STRBUF_INIT;
334 + const char *buffer;
335 + unsigned long size;
336 +
337 + if (get_sha1(old_ref, old) < 0)
338 + die(_("Not a valid object name: '%s'"), old_ref);
339 + commit = lookup_commit_or_die(old, old_ref);
340 +
341 + buffer = get_commit_buffer(commit, &size);
342 + strbuf_add(&buf, buffer, size);
343 + unuse_commit_buffer(commit, buffer);
344 +
345 + replace_parents(&buf, argc - 1, &argv[1]);
346 +
347 + if (write_sha1_file(buf.buf, buf.len, commit_type, new))
348 + die(_("could not write replacement commit for: '%s'"), old_ref);
349 +
350 + strbuf_release(&buf);
351 +
352 + if (!hashcmp(old, new))
353 + return error("new commit is the same as the old one: '%s'", sha1_to_hex(old));
354 +
355 + return replace_object_sha1(old_ref, old, "replacement", new, force);
356 +}
357 +
358 int cmd_replace(int argc, const char **argv, const char *prefix)
359 {
360 int force = 0;
@@ -303,12 +364,14 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
364 MODE_LIST,
365 MODE_DELETE,
366 MODE_EDIT,
367 + MODE_GRAFT,
368 MODE_REPLACE
369 } cmdmode = MODE_UNSPECIFIED;
370 struct option options[] = {
371 OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
372 OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
373 OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
374 + OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
375 OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")),
376 OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
377 OPT_END()
@@ -325,7 +388,10 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
388 usage_msg_opt("--format cannot be used when not listing",
389 git_replace_usage, options);
390
328 - if (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT)
391 + if (force &&
392 + cmdmode != MODE_REPLACE &&
393 + cmdmode != MODE_EDIT &&
394 + cmdmode != MODE_GRAFT)
395 usage_msg_opt("-f only makes sense when writing a replacement",
396 git_replace_usage, options);
397
@@ -348,6 +414,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
414 git_replace_usage, options);
415 return edit_and_replace(argv[0], force);
416
417 + case MODE_GRAFT:
418 + if (argc < 1)
419 + usage_msg_opt("-g needs at least one argument",
420 + git_replace_usage, options);
421 + return create_graft(argc, argv, force);
422 +
423 case MODE_LIST:
424 if (argc > 1)
425 usage_msg_opt("only one pattern can be given with -l",