Git fork
at reftables-rust 642 lines 18 kB view raw
1/* 2 * Builtin "git replace" 3 * 4 * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org> 5 * 6 * Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com> 7 * and Carlos Rica <jasampler@gmail.com> that was itself based on 8 * git-tag.sh and mktag.c by Linus Torvalds. 9 */ 10#define USE_THE_REPOSITORY_VARIABLE 11#include "builtin.h" 12#include "config.h" 13#include "editor.h" 14#include "environment.h" 15#include "gettext.h" 16#include "hex.h" 17#include "refs.h" 18#include "parse-options.h" 19#include "path.h" 20#include "run-command.h" 21#include "object-file.h" 22#include "object-name.h" 23#include "odb.h" 24#include "replace-object.h" 25#include "tag.h" 26#include "wildmatch.h" 27 28static const char * const git_replace_usage[] = { 29 N_("git replace [-f] <object> <replacement>"), 30 N_("git replace [-f] --edit <object>"), 31 N_("git replace [-f] --graft <commit> [<parent>...]"), 32 "git replace [-f] --convert-graft-file", 33 N_("git replace -d <object>..."), 34 N_("git replace [--format=<format>] [-l [<pattern>]]"), 35 NULL 36}; 37 38enum replace_format { 39 REPLACE_FORMAT_SHORT, 40 REPLACE_FORMAT_MEDIUM, 41 REPLACE_FORMAT_LONG 42}; 43 44struct show_data { 45 struct repository *repo; 46 const char *pattern; 47 enum replace_format format; 48}; 49 50static int show_reference(const char *refname, 51 const char *referent UNUSED, 52 const struct object_id *oid, 53 int flag UNUSED, void *cb_data) 54{ 55 struct show_data *data = cb_data; 56 57 if (!wildmatch(data->pattern, refname, 0)) { 58 if (data->format == REPLACE_FORMAT_SHORT) 59 printf("%s\n", refname); 60 else if (data->format == REPLACE_FORMAT_MEDIUM) 61 printf("%s -> %s\n", refname, oid_to_hex(oid)); 62 else { /* data->format == REPLACE_FORMAT_LONG */ 63 struct object_id object; 64 enum object_type obj_type, repl_type; 65 66 if (repo_get_oid(data->repo, refname, &object)) 67 return error(_("failed to resolve '%s' as a valid ref"), refname); 68 69 obj_type = odb_read_object_info(data->repo->objects, &object, NULL); 70 repl_type = odb_read_object_info(data->repo->objects, oid, NULL); 71 72 printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type), 73 oid_to_hex(oid), type_name(repl_type)); 74 } 75 } 76 77 return 0; 78} 79 80static int list_replace_refs(const char *pattern, const char *format) 81{ 82 struct show_data data; 83 84 data.repo = the_repository; 85 if (!pattern) 86 pattern = "*"; 87 data.pattern = pattern; 88 89 if (format == NULL || *format == '\0' || !strcmp(format, "short")) 90 data.format = REPLACE_FORMAT_SHORT; 91 else if (!strcmp(format, "medium")) 92 data.format = REPLACE_FORMAT_MEDIUM; 93 else if (!strcmp(format, "long")) 94 data.format = REPLACE_FORMAT_LONG; 95 /* 96 * Please update _git_replace() in git-completion.bash when 97 * you add new format 98 */ 99 else 100 return error(_("invalid replace format '%s'\n" 101 "valid formats are 'short', 'medium' and 'long'"), 102 format); 103 104 refs_for_each_replace_ref(get_main_ref_store(the_repository), 105 show_reference, (void *)&data); 106 107 return 0; 108} 109 110typedef int (*each_replace_name_fn)(const char *name, const char *ref, 111 const struct object_id *oid); 112 113static int for_each_replace_name(const char **argv, each_replace_name_fn fn) 114{ 115 const char **p, *full_hex; 116 struct strbuf ref = STRBUF_INIT; 117 size_t base_len; 118 int had_error = 0; 119 struct object_id oid; 120 const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; 121 122 strbuf_addstr(&ref, git_replace_ref_base); 123 base_len = ref.len; 124 125 for (p = argv; *p; p++) { 126 if (repo_get_oid(the_repository, *p, &oid)) { 127 error("failed to resolve '%s' as a valid ref", *p); 128 had_error = 1; 129 continue; 130 } 131 132 strbuf_setlen(&ref, base_len); 133 strbuf_addstr(&ref, oid_to_hex(&oid)); 134 full_hex = ref.buf + base_len; 135 136 if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &oid)) { 137 error(_("replace ref '%s' not found"), full_hex); 138 had_error = 1; 139 continue; 140 } 141 if (fn(full_hex, ref.buf, &oid)) 142 had_error = 1; 143 } 144 strbuf_release(&ref); 145 return had_error; 146} 147 148static int delete_replace_ref(const char *name, const char *ref, 149 const struct object_id *oid) 150{ 151 if (refs_delete_ref(get_main_ref_store(the_repository), NULL, ref, oid, 0)) 152 return 1; 153 printf_ln(_("Deleted replace ref '%s'"), name); 154 return 0; 155} 156 157static int check_ref_valid(struct object_id *object, 158 struct object_id *prev, 159 struct strbuf *ref, 160 int force) 161{ 162 const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; 163 164 strbuf_reset(ref); 165 strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object)); 166 if (check_refname_format(ref->buf, 0)) 167 return error(_("'%s' is not a valid ref name"), ref->buf); 168 169 if (refs_read_ref(get_main_ref_store(the_repository), ref->buf, prev)) 170 oidclr(prev, the_repository->hash_algo); 171 else if (!force) 172 return error(_("replace ref '%s' already exists"), ref->buf); 173 return 0; 174} 175 176static int replace_object_oid(const char *object_ref, 177 struct object_id *object, 178 const char *replace_ref, 179 struct object_id *repl, 180 int force) 181{ 182 struct object_id prev; 183 enum object_type obj_type, repl_type; 184 struct strbuf ref = STRBUF_INIT; 185 struct ref_transaction *transaction; 186 struct strbuf err = STRBUF_INIT; 187 int res = 0; 188 189 obj_type = odb_read_object_info(the_repository->objects, object, NULL); 190 repl_type = odb_read_object_info(the_repository->objects, repl, NULL); 191 if (!force && obj_type != repl_type) 192 return error(_("Objects must be of the same type.\n" 193 "'%s' points to a replaced object of type '%s'\n" 194 "while '%s' points to a replacement object of " 195 "type '%s'."), 196 object_ref, type_name(obj_type), 197 replace_ref, type_name(repl_type)); 198 199 if (check_ref_valid(object, &prev, &ref, force)) { 200 strbuf_release(&ref); 201 return -1; 202 } 203 204 transaction = ref_store_transaction_begin(get_main_ref_store(the_repository), 205 0, &err); 206 if (!transaction || 207 ref_transaction_update(transaction, ref.buf, repl, &prev, 208 NULL, NULL, 0, NULL, &err) || 209 ref_transaction_commit(transaction, &err)) 210 res = error("%s", err.buf); 211 212 ref_transaction_free(transaction); 213 strbuf_release(&ref); 214 return res; 215} 216 217static int replace_object(const char *object_ref, const char *replace_ref, int force) 218{ 219 struct object_id object, repl; 220 221 if (repo_get_oid(the_repository, object_ref, &object)) 222 return error(_("failed to resolve '%s' as a valid ref"), 223 object_ref); 224 if (repo_get_oid(the_repository, replace_ref, &repl)) 225 return error(_("failed to resolve '%s' as a valid ref"), 226 replace_ref); 227 228 return replace_object_oid(object_ref, &object, replace_ref, &repl, force); 229} 230 231/* 232 * Write the contents of the object named by "sha1" to the file "filename". 233 * If "raw" is true, then the object's raw contents are printed according to 234 * "type". Otherwise, we pretty-print the contents for human editing. 235 */ 236static int export_object(const struct object_id *oid, enum object_type type, 237 int raw, const char *filename) 238{ 239 struct child_process cmd = CHILD_PROCESS_INIT; 240 int fd; 241 242 fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); 243 if (fd < 0) 244 return error_errno(_("unable to open %s for writing"), filename); 245 246 strvec_push(&cmd.args, "--no-replace-objects"); 247 strvec_push(&cmd.args, "cat-file"); 248 if (raw) 249 strvec_push(&cmd.args, type_name(type)); 250 else 251 strvec_push(&cmd.args, "-p"); 252 strvec_push(&cmd.args, oid_to_hex(oid)); 253 cmd.git_cmd = 1; 254 cmd.out = fd; 255 256 if (run_command(&cmd)) 257 return error(_("cat-file reported failure")); 258 return 0; 259} 260 261/* 262 * Read a previously-exported (and possibly edited) object back from "filename", 263 * interpreting it as "type", and writing the result to the object database. 264 * The sha1 of the written object is returned via sha1. 265 */ 266static int import_object(struct object_id *oid, enum object_type type, 267 int raw, const char *filename) 268{ 269 int fd; 270 271 fd = open(filename, O_RDONLY); 272 if (fd < 0) 273 return error_errno(_("unable to open %s for reading"), filename); 274 275 if (!raw && type == OBJ_TREE) { 276 struct child_process cmd = CHILD_PROCESS_INIT; 277 struct strbuf result = STRBUF_INIT; 278 279 strvec_push(&cmd.args, "mktree"); 280 cmd.git_cmd = 1; 281 cmd.in = fd; 282 cmd.out = -1; 283 284 if (start_command(&cmd)) { 285 close(fd); 286 return error(_("unable to spawn mktree")); 287 } 288 289 if (strbuf_read(&result, cmd.out, the_hash_algo->hexsz + 1) < 0) { 290 error_errno(_("unable to read from mktree")); 291 close(fd); 292 close(cmd.out); 293 return -1; 294 } 295 close(cmd.out); 296 297 if (finish_command(&cmd)) { 298 strbuf_release(&result); 299 return error(_("mktree reported failure")); 300 } 301 if (get_oid_hex(result.buf, oid) < 0) { 302 strbuf_release(&result); 303 return error(_("mktree did not return an object name")); 304 } 305 306 strbuf_release(&result); 307 } else { 308 struct stat st; 309 int flags = INDEX_FORMAT_CHECK | INDEX_WRITE_OBJECT; 310 311 if (fstat(fd, &st) < 0) { 312 error_errno(_("unable to fstat %s"), filename); 313 close(fd); 314 return -1; 315 } 316 if (index_fd(the_repository->index, oid, fd, &st, type, NULL, flags) < 0) 317 return error(_("unable to write object to database")); 318 /* index_fd close()s fd for us */ 319 } 320 321 /* 322 * No need to close(fd) here; both run-command and index-fd 323 * will have done it for us. 324 */ 325 return 0; 326} 327 328static int edit_and_replace(const char *object_ref, int force, int raw) 329{ 330 char *tmpfile; 331 enum object_type type; 332 struct object_id old_oid, new_oid, prev; 333 struct strbuf ref = STRBUF_INIT; 334 335 if (repo_get_oid(the_repository, object_ref, &old_oid) < 0) 336 return error(_("not a valid object name: '%s'"), object_ref); 337 338 type = odb_read_object_info(the_repository->objects, &old_oid, NULL); 339 if (type < 0) 340 return error(_("unable to get object type for %s"), 341 oid_to_hex(&old_oid)); 342 343 if (check_ref_valid(&old_oid, &prev, &ref, force)) { 344 strbuf_release(&ref); 345 return -1; 346 } 347 strbuf_release(&ref); 348 349 tmpfile = repo_git_path(the_repository, "REPLACE_EDITOBJ"); 350 if (export_object(&old_oid, type, raw, tmpfile)) { 351 free(tmpfile); 352 return -1; 353 } 354 if (launch_editor(tmpfile, NULL, NULL) < 0) { 355 free(tmpfile); 356 return error(_("editing object file failed")); 357 } 358 if (import_object(&new_oid, type, raw, tmpfile)) { 359 free(tmpfile); 360 return -1; 361 } 362 free(tmpfile); 363 364 if (oideq(&old_oid, &new_oid)) 365 return error(_("new object is the same as the old one: '%s'"), oid_to_hex(&old_oid)); 366 367 return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force); 368} 369 370static int replace_parents(struct strbuf *buf, int argc, const char **argv) 371{ 372 struct strbuf new_parents = STRBUF_INIT; 373 const char *parent_start, *parent_end; 374 int i; 375 const unsigned hexsz = the_hash_algo->hexsz; 376 377 /* find existing parents */ 378 parent_start = buf->buf; 379 parent_start += hexsz + 6; /* "tree " + "hex sha1" + "\n" */ 380 parent_end = parent_start; 381 382 while (starts_with(parent_end, "parent ")) 383 parent_end += hexsz + 8; /* "parent " + "hex sha1" + "\n" */ 384 385 /* prepare new parents */ 386 for (i = 0; i < argc; i++) { 387 struct object_id oid; 388 struct commit *commit; 389 390 if (repo_get_oid(the_repository, argv[i], &oid) < 0) { 391 strbuf_release(&new_parents); 392 return error(_("not a valid object name: '%s'"), 393 argv[i]); 394 } 395 commit = lookup_commit_reference(the_repository, &oid); 396 if (!commit) { 397 strbuf_release(&new_parents); 398 return error(_("could not parse %s as a commit"), argv[i]); 399 } 400 strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&commit->object.oid)); 401 } 402 403 /* replace existing parents with new ones */ 404 strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start, 405 new_parents.buf, new_parents.len); 406 407 strbuf_release(&new_parents); 408 return 0; 409} 410 411struct check_mergetag_data { 412 int argc; 413 const char **argv; 414}; 415 416static int check_one_mergetag(struct commit *commit UNUSED, 417 struct commit_extra_header *extra, 418 void *data) 419{ 420 struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data; 421 const char *ref = mergetag_data->argv[0]; 422 struct object_id tag_oid; 423 struct tag *tag; 424 int i; 425 426 hash_object_file(the_hash_algo, extra->value, extra->len, 427 OBJ_TAG, &tag_oid); 428 tag = lookup_tag(the_repository, &tag_oid); 429 if (!tag) 430 return error(_("bad mergetag in commit '%s'"), ref); 431 if (parse_tag_buffer(the_repository, tag, extra->value, extra->len)) 432 return error(_("malformed mergetag in commit '%s'"), ref); 433 434 /* iterate over new parents */ 435 for (i = 1; i < mergetag_data->argc; i++) { 436 struct object_id oid; 437 if (repo_get_oid(the_repository, mergetag_data->argv[i], &oid) < 0) 438 return error(_("not a valid object name: '%s'"), 439 mergetag_data->argv[i]); 440 if (oideq(get_tagged_oid(tag), &oid)) 441 return 0; /* found */ 442 } 443 444 return error(_("original commit '%s' contains mergetag '%s' that is " 445 "discarded; use --edit instead of --graft"), ref, 446 oid_to_hex(&tag_oid)); 447} 448 449static int check_mergetags(struct commit *commit, int argc, const char **argv) 450{ 451 struct check_mergetag_data mergetag_data; 452 453 mergetag_data.argc = argc; 454 mergetag_data.argv = argv; 455 return for_each_mergetag(check_one_mergetag, commit, &mergetag_data); 456} 457 458static int create_graft(int argc, const char **argv, int force, int gentle) 459{ 460 struct object_id old_oid, new_oid; 461 const char *old_ref = argv[0]; 462 struct commit *commit; 463 struct strbuf buf = STRBUF_INIT; 464 const char *buffer; 465 unsigned long size; 466 467 if (repo_get_oid(the_repository, old_ref, &old_oid) < 0) 468 return error(_("not a valid object name: '%s'"), old_ref); 469 commit = lookup_commit_reference(the_repository, &old_oid); 470 if (!commit) 471 return error(_("could not parse %s"), old_ref); 472 473 buffer = repo_get_commit_buffer(the_repository, commit, &size); 474 strbuf_add(&buf, buffer, size); 475 repo_unuse_commit_buffer(the_repository, commit, buffer); 476 477 if (replace_parents(&buf, argc - 1, &argv[1]) < 0) { 478 strbuf_release(&buf); 479 return -1; 480 } 481 482 if (remove_signature(&buf)) { 483 warning(_("the original commit '%s' has a gpg signature"), old_ref); 484 warning(_("the signature will be removed in the replacement commit!")); 485 } 486 487 if (check_mergetags(commit, argc, argv)) { 488 strbuf_release(&buf); 489 return -1; 490 } 491 492 if (odb_write_object(the_repository->objects, buf.buf, 493 buf.len, OBJ_COMMIT, &new_oid)) { 494 strbuf_release(&buf); 495 return error(_("could not write replacement commit for: '%s'"), 496 old_ref); 497 } 498 499 strbuf_release(&buf); 500 501 if (oideq(&commit->object.oid, &new_oid)) { 502 if (gentle) { 503 warning(_("graft for '%s' unnecessary"), 504 oid_to_hex(&commit->object.oid)); 505 return 0; 506 } 507 return error(_("new commit is the same as the old one: '%s'"), 508 oid_to_hex(&commit->object.oid)); 509 } 510 511 return replace_object_oid(old_ref, &commit->object.oid, 512 "replacement", &new_oid, force); 513} 514 515static int convert_graft_file(int force) 516{ 517 const char *graft_file = repo_get_graft_file(the_repository); 518 FILE *fp = fopen_or_warn(graft_file, "r"); 519 struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT; 520 struct strvec args = STRVEC_INIT; 521 522 if (!fp) 523 return -1; 524 525 no_graft_file_deprecated_advice = 1; 526 while (strbuf_getline(&buf, fp) != EOF) { 527 if (*buf.buf == '#') 528 continue; 529 530 strvec_split(&args, buf.buf); 531 if (args.nr && create_graft(args.nr, args.v, force, 1)) 532 strbuf_addf(&err, "\n\t%s", buf.buf); 533 strvec_clear(&args); 534 } 535 fclose(fp); 536 537 strbuf_release(&buf); 538 539 if (!err.len) 540 return unlink_or_warn(graft_file); 541 542 warning(_("could not convert the following graft(s):\n%s"), err.buf); 543 strbuf_release(&err); 544 545 return -1; 546} 547 548int cmd_replace(int argc, 549 const char **argv, 550 const char *prefix, 551 struct repository *repo UNUSED) 552{ 553 int force = 0; 554 int raw = 0; 555 const char *format = NULL; 556 enum { 557 MODE_UNSPECIFIED = 0, 558 MODE_LIST, 559 MODE_DELETE, 560 MODE_EDIT, 561 MODE_GRAFT, 562 MODE_CONVERT_GRAFT_FILE, 563 MODE_REPLACE 564 } cmdmode = MODE_UNSPECIFIED; 565 struct option options[] = { 566 OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST), 567 OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE), 568 OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT), 569 OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT), 570 OPT_CMDMODE(0, "convert-graft-file", &cmdmode, N_("convert existing graft file"), MODE_CONVERT_GRAFT_FILE), 571 OPT_BOOL_F('f', "force", &force, N_("replace the ref if it exists"), 572 PARSE_OPT_NOCOMPLETE), 573 OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")), 574 OPT_STRING(0, "format", &format, N_("format"), N_("use this format")), 575 OPT_END() 576 }; 577 578 disable_replace_refs(); 579 repo_config(the_repository, git_default_config, NULL); 580 581 argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); 582 583 if (!cmdmode) 584 cmdmode = argc ? MODE_REPLACE : MODE_LIST; 585 586 if (format && cmdmode != MODE_LIST) 587 usage_msg_opt(_("--format cannot be used when not listing"), 588 git_replace_usage, options); 589 590 if (force && 591 cmdmode != MODE_REPLACE && 592 cmdmode != MODE_EDIT && 593 cmdmode != MODE_GRAFT && 594 cmdmode != MODE_CONVERT_GRAFT_FILE) 595 usage_msg_opt(_("-f only makes sense when writing a replacement"), 596 git_replace_usage, options); 597 598 if (raw && cmdmode != MODE_EDIT) 599 usage_msg_opt(_("--raw only makes sense with --edit"), 600 git_replace_usage, options); 601 602 switch (cmdmode) { 603 case MODE_DELETE: 604 if (argc < 1) 605 usage_msg_opt(_("-d needs at least one argument"), 606 git_replace_usage, options); 607 return for_each_replace_name(argv, delete_replace_ref); 608 609 case MODE_REPLACE: 610 if (argc != 2) 611 usage_msg_opt(_("bad number of arguments"), 612 git_replace_usage, options); 613 return replace_object(argv[0], argv[1], force); 614 615 case MODE_EDIT: 616 if (argc != 1) 617 usage_msg_opt(_("-e needs exactly one argument"), 618 git_replace_usage, options); 619 return edit_and_replace(argv[0], force, raw); 620 621 case MODE_GRAFT: 622 if (argc < 1) 623 usage_msg_opt(_("-g needs at least one argument"), 624 git_replace_usage, options); 625 return create_graft(argc, argv, force, 0); 626 627 case MODE_CONVERT_GRAFT_FILE: 628 if (argc != 0) 629 usage_msg_opt(_("--convert-graft-file takes no argument"), 630 git_replace_usage, options); 631 return !!convert_graft_file(force); 632 633 case MODE_LIST: 634 if (argc > 1) 635 usage_msg_opt(_("only one pattern can be given with -l"), 636 git_replace_usage, options); 637 return list_replace_refs(argv[0], format); 638 639 default: 640 BUG("invalid cmdmode %d", (int)cmdmode); 641 } 642}