Git fork

Merge branch 'ps/reflog-migrate-fixes' into maint-2.51

"git refs migrate" to migrate the reflog entries from a refs
backend to another had a handful of bugs squashed.

* ps/reflog-migrate-fixes:
refs: fix invalid old object IDs when migrating reflogs
refs: stop unsetting REF_HAVE_OLD for log-only updates
refs/files: detect race when generating reflog entry for HEAD
refs: fix identity for migrated reflogs
ident: fix type of string length parameter
builtin/reflog: implement subcommand to write new entries
refs: export `ref_transaction_update_reflog()`
builtin/reflog: improve grouping of subcommands
Documentation/git-reflog: convert to use synopsis type

+413 -117
+52 -44
Documentation/git-reflog.adoc
··· 8 8 9 9 SYNOPSIS 10 10 -------- 11 - [verse] 12 - 'git reflog' [show] [<log-options>] [<ref>] 13 - 'git reflog list' 14 - 'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>] 11 + [synopsis] 12 + git reflog [show] [<log-options>] [<ref>] 13 + git reflog list 14 + git reflog exists <ref> 15 + git reflog write <ref> <old-oid> <new-oid> <message> 16 + git reflog delete [--rewrite] [--updateref] 17 + [--dry-run | -n] [--verbose] <ref>@{<specifier>}... 18 + git reflog drop [--all [--single-worktree] | <refs>...] 19 + git reflog expire [--expire=<time>] [--expire-unreachable=<time>] 15 20 [--rewrite] [--updateref] [--stale-fix] 16 21 [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...] 17 - 'git reflog delete' [--rewrite] [--updateref] 18 - [--dry-run | -n] [--verbose] <ref>@{<specifier>}... 19 - 'git reflog drop' [--all [--single-worktree] | <refs>...] 20 - 'git reflog exists' <ref> 21 22 22 23 DESCRIPTION 23 24 ----------- ··· 43 44 44 45 The "list" subcommand lists all refs which have a corresponding reflog. 45 46 46 - The "expire" subcommand prunes older reflog entries. Entries older 47 - than `expire` time, or entries older than `expire-unreachable` time 48 - and not reachable from the current tip, are removed from the reflog. 49 - This is typically not used directly by end users -- instead, see 50 - linkgit:git-gc[1]. 47 + The "exists" subcommand checks whether a ref has a reflog. It exits 48 + with zero status if the reflog exists, and non-zero status if it does 49 + not. 50 + 51 + The "write" subcommand writes a single entry to the reflog of a given 52 + reference. This new entry is appended to the reflog and will thus become 53 + the most recent entry. The reference name must be fully qualified. Both the old 54 + and new object IDs must not be abbreviated and must point to existing objects. 55 + The reflog message gets normalized. 51 56 52 57 The "delete" subcommand deletes single entries from the reflog, but 53 58 not the reflog itself. Its argument must be an _exact_ entry (e.g. "`git ··· 58 63 references. This is in contrast to "expire" and "delete", both of which 59 64 can be used to delete reflog entries, but not the reflog itself. 60 65 61 - The "exists" subcommand checks whether a ref has a reflog. It exits 62 - with zero status if the reflog exists, and non-zero status if it does 63 - not. 66 + The "expire" subcommand prunes older reflog entries. Entries older 67 + than `expire` time, or entries older than `expire-unreachable` time 68 + and not reachable from the current tip, are removed from the reflog. 69 + This is typically not used directly by end users -- instead, see 70 + linkgit:git-gc[1]. 64 71 65 72 OPTIONS 66 73 ------- ··· 71 78 `git reflog show` accepts any of the options accepted by `git log`. 72 79 73 80 81 + Options for `delete` 82 + ~~~~~~~~~~~~~~~~~~~~ 83 + 84 + `git reflog delete` accepts options `--updateref`, `--rewrite`, `-n`, 85 + `--dry-run`, and `--verbose`, with the same meanings as when they are 86 + used with `expire`. 87 + 88 + Options for `drop` 89 + ~~~~~~~~~~~~~~~~~~ 90 + 91 + `--all`:: 92 + Drop the reflogs of all references from all worktrees. 93 + 94 + `--single-worktree`:: 95 + By default when `--all` is specified, reflogs from all working 96 + trees are dropped. This option limits the processing to reflogs 97 + from the current working tree only. 98 + 99 + 74 100 Options for `expire` 75 101 ~~~~~~~~~~~~~~~~~~~~ 76 102 77 - --all:: 103 + `--all`:: 78 104 Process the reflogs of all references. 79 105 80 - --single-worktree:: 106 + `--single-worktree`:: 81 107 By default when `--all` is specified, reflogs from all working 82 108 trees are processed. This option limits the processing to reflogs 83 109 from the current working tree only. 84 110 85 - --expire=<time>:: 111 + `--expire=<time>`:: 86 112 Prune entries older than the specified time. If this option is 87 113 not specified, the expiration time is taken from the 88 114 configuration setting `gc.reflogExpire`, which in turn ··· 90 116 of their age; `--expire=never` turns off pruning of reachable 91 117 entries (but see `--expire-unreachable`). 92 118 93 - --expire-unreachable=<time>:: 119 + `--expire-unreachable=<time>`:: 94 120 Prune entries older than `<time>` that are not reachable from 95 121 the current tip of the branch. If this option is not 96 122 specified, the expiration time is taken from the configuration ··· 100 126 turns off early pruning of unreachable entries (but see 101 127 `--expire`). 102 128 103 - --updateref:: 129 + `--updateref`:: 104 130 Update the reference to the value of the top reflog entry (i.e. 105 131 <ref>@\{0\}) if the previous top entry was pruned. (This 106 132 option is ignored for symbolic references.) 107 133 108 - --rewrite:: 134 + `--rewrite`:: 109 135 If a reflog entry's predecessor is pruned, adjust its "old" 110 136 SHA-1 to be equal to the "new" SHA-1 field of the entry that 111 137 now precedes it. 112 138 113 - --stale-fix:: 139 + `--stale-fix`:: 114 140 Prune any reflog entries that point to "broken commits". A 115 141 broken commit is a commit that is not reachable from any of 116 142 the reference tips and that refers, directly or indirectly, to ··· 121 147 corruption caused by garbage collecting using older versions of Git, 122 148 which didn't protect objects referred to by reflogs. 123 149 124 - -n:: 125 - --dry-run:: 150 + `-n`:: 151 + `--dry-run`:: 126 152 Do not actually prune any entries; just show what would have 127 153 been pruned. 128 154 129 - --verbose:: 155 + `--verbose`:: 130 156 Print extra information on screen. 131 157 132 - 133 - Options for `delete` 134 - ~~~~~~~~~~~~~~~~~~~~ 135 - 136 - `git reflog delete` accepts options `--updateref`, `--rewrite`, `-n`, 137 - `--dry-run`, and `--verbose`, with the same meanings as when they are 138 - used with `expire`. 139 - 140 - Options for `drop` 141 - ~~~~~~~~~~~~~~~~~~ 142 - 143 - --all:: 144 - Drop the reflogs of all references from all worktrees. 145 - 146 - --single-worktree:: 147 - By default when `--all` is specified, reflogs from all working 148 - trees are dropped. This option limits the processing to reflogs 149 - from the current working tree only. 150 158 151 159 GIT 152 160 ---
+84 -19
builtin/reflog.c
··· 3 3 #include "builtin.h" 4 4 #include "config.h" 5 5 #include "gettext.h" 6 + #include "hex.h" 7 + #include "odb.h" 6 8 #include "revision.h" 7 9 #include "reachable.h" 8 10 #include "wildmatch.h" ··· 17 19 #define BUILTIN_REFLOG_LIST_USAGE \ 18 20 N_("git reflog list") 19 21 20 - #define BUILTIN_REFLOG_EXPIRE_USAGE \ 21 - N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ 22 - " [--rewrite] [--updateref] [--stale-fix]\n" \ 23 - " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") 22 + #define BUILTIN_REFLOG_EXISTS_USAGE \ 23 + N_("git reflog exists <ref>") 24 + 25 + #define BUILTIN_REFLOG_WRITE_USAGE \ 26 + N_("git reflog write <ref> <old-oid> <new-oid> <message>") 24 27 25 28 #define BUILTIN_REFLOG_DELETE_USAGE \ 26 29 N_("git reflog delete [--rewrite] [--updateref]\n" \ 27 30 " [--dry-run | -n] [--verbose] <ref>@{<specifier>}...") 28 31 29 - #define BUILTIN_REFLOG_EXISTS_USAGE \ 30 - N_("git reflog exists <ref>") 31 - 32 32 #define BUILTIN_REFLOG_DROP_USAGE \ 33 33 N_("git reflog drop [--all [--single-worktree] | <refs>...]") 34 + 35 + #define BUILTIN_REFLOG_EXPIRE_USAGE \ 36 + N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ 37 + " [--rewrite] [--updateref] [--stale-fix]\n" \ 38 + " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") 34 39 35 40 static const char *const reflog_show_usage[] = { 36 41 BUILTIN_REFLOG_SHOW_USAGE, ··· 42 47 NULL, 43 48 }; 44 49 45 - static const char *const reflog_expire_usage[] = { 46 - BUILTIN_REFLOG_EXPIRE_USAGE, 47 - NULL 50 + static const char *const reflog_exists_usage[] = { 51 + BUILTIN_REFLOG_EXISTS_USAGE, 52 + NULL, 53 + }; 54 + 55 + static const char *const reflog_write_usage[] = { 56 + BUILTIN_REFLOG_WRITE_USAGE, 57 + NULL, 48 58 }; 49 59 50 60 static const char *const reflog_delete_usage[] = { ··· 52 62 NULL 53 63 }; 54 64 55 - static const char *const reflog_exists_usage[] = { 56 - BUILTIN_REFLOG_EXISTS_USAGE, 57 - NULL, 58 - }; 59 - 60 65 static const char *const reflog_drop_usage[] = { 61 66 BUILTIN_REFLOG_DROP_USAGE, 62 67 NULL, 68 + }; 69 + 70 + static const char *const reflog_expire_usage[] = { 71 + BUILTIN_REFLOG_EXPIRE_USAGE, 72 + NULL 63 73 }; 64 74 65 75 static const char *const reflog_usage[] = { 66 76 BUILTIN_REFLOG_SHOW_USAGE, 67 77 BUILTIN_REFLOG_LIST_USAGE, 68 - BUILTIN_REFLOG_EXPIRE_USAGE, 78 + BUILTIN_REFLOG_EXISTS_USAGE, 79 + BUILTIN_REFLOG_WRITE_USAGE, 69 80 BUILTIN_REFLOG_DELETE_USAGE, 70 81 BUILTIN_REFLOG_DROP_USAGE, 71 - BUILTIN_REFLOG_EXISTS_USAGE, 82 + BUILTIN_REFLOG_EXPIRE_USAGE, 72 83 NULL 73 84 }; 74 85 ··· 395 406 return ret; 396 407 } 397 408 409 + static int cmd_reflog_write(int argc, const char **argv, const char *prefix, 410 + struct repository *repo) 411 + { 412 + const struct option options[] = { 413 + OPT_END() 414 + }; 415 + struct object_id old_oid, new_oid; 416 + struct strbuf err = STRBUF_INIT; 417 + struct ref_transaction *tx; 418 + const char *ref, *message; 419 + int ret; 420 + 421 + argc = parse_options(argc, argv, prefix, options, reflog_write_usage, 0); 422 + if (argc != 4) 423 + usage_with_options(reflog_write_usage, options); 424 + 425 + ref = argv[0]; 426 + if (!is_root_ref(ref) && check_refname_format(ref, 0)) 427 + die(_("invalid reference name: %s"), ref); 428 + 429 + ret = get_oid_hex_algop(argv[1], &old_oid, repo->hash_algo); 430 + if (ret) 431 + die(_("invalid old object ID: '%s'"), argv[1]); 432 + if (!is_null_oid(&old_oid) && !odb_has_object(repo->objects, &old_oid, 0)) 433 + die(_("old object '%s' does not exist"), argv[1]); 434 + 435 + ret = get_oid_hex_algop(argv[2], &new_oid, repo->hash_algo); 436 + if (ret) 437 + die(_("invalid new object ID: '%s'"), argv[2]); 438 + if (!is_null_oid(&new_oid) && !odb_has_object(repo->objects, &new_oid, 0)) 439 + die(_("new object '%s' does not exist"), argv[2]); 440 + 441 + message = argv[3]; 442 + 443 + tx = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err); 444 + if (!tx) 445 + die(_("cannot start transaction: %s"), err.buf); 446 + 447 + ret = ref_transaction_update_reflog(tx, ref, &new_oid, &old_oid, 448 + git_committer_info(0), 449 + message, 0, &err); 450 + if (ret) 451 + die(_("cannot queue reflog update: %s"), err.buf); 452 + 453 + ret = ref_transaction_commit(tx, &err); 454 + if (ret) 455 + die(_("cannot commit reflog update: %s"), err.buf); 456 + 457 + ref_transaction_free(tx); 458 + strbuf_release(&err); 459 + return 0; 460 + } 461 + 398 462 /* 399 463 * main "reflog" 400 464 */ ··· 407 471 struct option options[] = { 408 472 OPT_SUBCOMMAND("show", &fn, cmd_reflog_show), 409 473 OPT_SUBCOMMAND("list", &fn, cmd_reflog_list), 410 - OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), 474 + OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), 475 + OPT_SUBCOMMAND("write", &fn, cmd_reflog_write), 411 476 OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), 412 - OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), 413 477 OPT_SUBCOMMAND("drop", &fn, cmd_reflog_drop), 478 + OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), 414 479 OPT_END() 415 480 }; 416 481
+1 -1
ident.c
··· 272 272 * can still be NULL if the input line only has the name/email part 273 273 * (e.g. reading from a reflog entry). 274 274 */ 275 - int split_ident_line(struct ident_split *split, const char *line, int len) 275 + int split_ident_line(struct ident_split *split, const char *line, size_t len) 276 276 { 277 277 const char *cp; 278 278 size_t span;
+1 -1
ident.h
··· 35 35 * Signals an success with 0, but time part of the result may be NULL 36 36 * if the input lacks timestamp and zone 37 37 */ 38 - int split_ident_line(struct ident_split *, const char *, int); 38 + int split_ident_line(struct ident_split *, const char *, size_t); 39 39 40 40 /* 41 41 * Given a commit or tag object buffer and the commit or tag headers, replaces
+33 -27
refs.c
··· 1362 1362 return 0; 1363 1363 } 1364 1364 1365 - /* 1366 - * Similar to`ref_transaction_update`, but this function is only for adding 1367 - * a reflog update. Supports providing custom committer information. The index 1368 - * field can be utiltized to order updates as desired. When not used, the 1369 - * updates default to being ordered by refname. 1370 - */ 1371 - static int ref_transaction_update_reflog(struct ref_transaction *transaction, 1372 - const char *refname, 1373 - const struct object_id *new_oid, 1374 - const struct object_id *old_oid, 1375 - const char *committer_info, 1376 - unsigned int flags, 1377 - const char *msg, 1378 - uint64_t index, 1379 - struct strbuf *err) 1365 + int ref_transaction_update_reflog(struct ref_transaction *transaction, 1366 + const char *refname, 1367 + const struct object_id *new_oid, 1368 + const struct object_id *old_oid, 1369 + const char *committer_info, 1370 + const char *msg, 1371 + uint64_t index, 1372 + struct strbuf *err) 1380 1373 { 1381 1374 struct ref_update *update; 1375 + unsigned int flags; 1382 1376 1383 1377 assert(err); 1384 1378 1385 - flags |= REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF; 1379 + flags = REF_HAVE_OLD | REF_HAVE_NEW | REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF | 1380 + REF_LOG_USE_PROVIDED_OIDS; 1386 1381 1387 1382 if (!transaction_refname_valid(refname, new_oid, flags, err)) 1388 1383 return -1; ··· 1390 1385 update = ref_transaction_add_update(transaction, refname, flags, 1391 1386 new_oid, old_oid, NULL, NULL, 1392 1387 committer_info, msg); 1393 - /* 1394 - * While we do set the old_oid value, we unset the flag to skip 1395 - * old_oid verification which only makes sense for refs. 1396 - */ 1397 - update->flags &= ~REF_HAVE_OLD; 1398 1388 update->index = index; 1399 1389 1400 1390 /* ··· 2951 2941 struct ref_store *old_refs; 2952 2942 struct ref_transaction *transaction; 2953 2943 struct strbuf *errbuf; 2954 - struct strbuf sb; 2944 + struct strbuf sb, name, mail; 2955 2945 }; 2956 2946 2957 2947 static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, ··· 2990 2980 struct ref_store *old_refs; 2991 2981 struct ref_transaction *transaction; 2992 2982 struct strbuf *errbuf; 2993 - struct strbuf *sb; 2983 + struct strbuf *sb, *name, *mail; 2994 2984 }; 2995 2985 2996 2986 static int migrate_one_reflog_entry(struct object_id *old_oid, ··· 3000 2990 const char *msg, void *cb_data) 3001 2991 { 3002 2992 struct reflog_migration_data *data = cb_data; 2993 + struct ident_split ident; 3003 2994 const char *date; 3004 2995 int ret; 3005 2996 2997 + if (split_ident_line(&ident, committer, strlen(committer)) < 0) 2998 + return -1; 2999 + 3000 + strbuf_reset(data->name); 3001 + strbuf_add(data->name, ident.name_begin, ident.name_end - ident.name_begin); 3002 + strbuf_reset(data->mail); 3003 + strbuf_add(data->mail, ident.mail_begin, ident.mail_end - ident.mail_begin); 3004 + 3006 3005 date = show_date(timestamp, tz, DATE_MODE(NORMAL)); 3007 3006 strbuf_reset(data->sb); 3008 - /* committer contains name and email */ 3009 - strbuf_addstr(data->sb, fmt_ident("", committer, WANT_BLANK_IDENT, date, 0)); 3007 + strbuf_addstr(data->sb, fmt_ident(data->name->buf, data->mail->buf, WANT_BLANK_IDENT, date, 0)); 3010 3008 3011 3009 ret = ref_transaction_update_reflog(data->transaction, data->refname, 3012 3010 new_oid, old_oid, data->sb->buf, 3013 - REF_HAVE_NEW | REF_HAVE_OLD, msg, 3014 - data->index++, data->errbuf); 3011 + msg, data->index++, data->errbuf); 3015 3012 return ret; 3016 3013 } 3017 3014 ··· 3024 3021 .transaction = migration_data->transaction, 3025 3022 .errbuf = migration_data->errbuf, 3026 3023 .sb = &migration_data->sb, 3024 + .name = &migration_data->name, 3025 + .mail = &migration_data->mail, 3027 3026 }; 3028 3027 3029 3028 return refs_for_each_reflog_ent(migration_data->old_refs, refname, ··· 3122 3121 struct strbuf new_gitdir = STRBUF_INIT; 3123 3122 struct migration_data data = { 3124 3123 .sb = STRBUF_INIT, 3124 + .name = STRBUF_INIT, 3125 + .mail = STRBUF_INIT, 3125 3126 }; 3126 3127 int did_migrate_refs = 0; 3127 3128 int ret; ··· 3297 3298 ref_transaction_free(transaction); 3298 3299 strbuf_release(&new_gitdir); 3299 3300 strbuf_release(&data.sb); 3301 + strbuf_release(&data.name); 3302 + strbuf_release(&data.mail); 3300 3303 return ret; 3301 3304 } 3302 3305 3303 3306 int ref_update_expects_existing_old_ref(struct ref_update *update) 3304 3307 { 3308 + if (update->flags & REF_LOG_ONLY) 3309 + return 0; 3310 + 3305 3311 return (update->flags & REF_HAVE_OLD) && 3306 3312 (!is_null_oid(&update->old_oid) || update->old_target); 3307 3313 }
+23 -1
refs.h
··· 760 760 #define REF_SKIP_CREATE_REFLOG (1 << 12) 761 761 762 762 /* 763 + * When writing a REF_LOG_ONLY record, use the old and new object IDs provided 764 + * in the update instead of resolving the old object ID. The caller must also 765 + * set both REF_HAVE_OLD and REF_HAVE_NEW. 766 + */ 767 + #define REF_LOG_USE_PROVIDED_OIDS (1 << 13) 768 + 769 + /* 763 770 * Bitmask of all of the flags that are allowed to be passed in to 764 771 * ref_transaction_update() and friends: 765 772 */ 766 773 #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \ 767 774 (REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \ 768 - REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG) 775 + REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG | REF_LOG_USE_PROVIDED_OIDS) 769 776 770 777 /* 771 778 * Add a reference update to transaction. `new_oid` is the value that ··· 793 800 const char *old_target, 794 801 unsigned int flags, const char *msg, 795 802 struct strbuf *err); 803 + 804 + /* 805 + * Similar to `ref_transaction_update`, but this function is only for adding 806 + * a reflog update. Supports providing custom committer information. The index 807 + * field can be utiltized to order updates as desired. When set to zero, the 808 + * updates default to being ordered by refname. 809 + */ 810 + int ref_transaction_update_reflog(struct ref_transaction *transaction, 811 + const char *refname, 812 + const struct object_id *new_oid, 813 + const struct object_id *old_oid, 814 + const char *committer_info, 815 + const char *msg, 816 + uint64_t index, 817 + struct strbuf *err); 796 818 797 819 /* 798 820 * Add a reference creation to transaction. new_oid is the value that
+58 -7
refs/files-backend.c
··· 68 68 */ 69 69 #define REF_DELETED_RMDIR (1 << 9) 70 70 71 + /* 72 + * Used to indicate that the reflog-only update has been created via 73 + * `split_head_update()`. 74 + */ 75 + #define REF_LOG_VIA_SPLIT (1 << 14) 76 + 71 77 struct ref_lock { 72 78 char *ref_name; 73 79 struct lock_file lk; ··· 2421 2427 2422 2428 new_update = ref_transaction_add_update( 2423 2429 transaction, "HEAD", 2424 - update->flags | REF_LOG_ONLY | REF_NO_DEREF, 2430 + update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT, 2425 2431 &update->new_oid, &update->old_oid, 2426 2432 NULL, NULL, update->committer_info, update->msg); 2433 + new_update->parent_update = update; 2427 2434 2428 2435 /* 2429 2436 * Add "HEAD". This insertion is O(N) in the transaction ··· 2494 2501 * done when new_update is processed. 2495 2502 */ 2496 2503 update->flags |= REF_LOG_ONLY | REF_NO_DEREF; 2497 - update->flags &= ~REF_HAVE_OLD; 2498 2504 2499 2505 return 0; 2500 2506 } ··· 2509 2515 struct object_id *oid, 2510 2516 struct strbuf *err) 2511 2517 { 2512 - if (!(update->flags & REF_HAVE_OLD) || 2513 - oideq(oid, &update->old_oid)) 2518 + if (update->flags & REF_LOG_ONLY || 2519 + !(update->flags & REF_HAVE_OLD) || 2520 + oideq(oid, &update->old_oid)) 2514 2521 return 0; 2515 2522 2516 2523 if (is_null_oid(&update->old_oid)) { ··· 2601 2608 2602 2609 update->backend_data = lock; 2603 2610 2604 - if (update->type & REF_ISSYMREF) { 2611 + if (update->flags & REF_LOG_VIA_SPLIT) { 2612 + struct ref_lock *parent_lock; 2613 + 2614 + if (!update->parent_update) 2615 + BUG("split update without a parent"); 2616 + 2617 + parent_lock = update->parent_update->backend_data; 2618 + 2619 + /* 2620 + * Check that "HEAD" didn't racily change since we have looked 2621 + * it up. If it did we must refuse to write the reflog entry. 2622 + * 2623 + * Note that this does not catch all races: if "HEAD" was 2624 + * racily changed to point to one of the refs part of the 2625 + * transaction then we would miss writing the split reflog 2626 + * entry for "HEAD". 2627 + */ 2628 + if (!(update->type & REF_ISSYMREF) || 2629 + strcmp(update->parent_update->refname, referent.buf)) { 2630 + strbuf_addstr(err, "HEAD has been racily updated"); 2631 + ret = REF_TRANSACTION_ERROR_GENERIC; 2632 + goto out; 2633 + } 2634 + 2635 + if (update->flags & REF_HAVE_OLD) { 2636 + oidcpy(&lock->old_oid, &update->old_oid); 2637 + } else { 2638 + oidcpy(&lock->old_oid, &parent_lock->old_oid); 2639 + } 2640 + } else if (update->type & REF_ISSYMREF) { 2605 2641 if (update->flags & REF_NO_DEREF) { 2606 2642 /* 2607 2643 * We won't be reading the referent as part of ··· 2977 3013 struct ref_lock *lock, 2978 3014 struct strbuf *err) 2979 3015 { 3016 + struct object_id *old_oid = &lock->old_oid; 3017 + 3018 + if (update->flags & REF_LOG_USE_PROVIDED_OIDS) { 3019 + if (!(update->flags & REF_HAVE_OLD) || 3020 + !(update->flags & REF_HAVE_NEW) || 3021 + !(update->flags & REF_LOG_ONLY)) { 3022 + strbuf_addf(err, _("trying to write reflog for '%s'" 3023 + "with incomplete values"), update->refname); 3024 + return REF_TRANSACTION_ERROR_GENERIC; 3025 + } 3026 + 3027 + old_oid = &update->old_oid; 3028 + } 3029 + 2980 3030 if (update->new_target) { 2981 3031 /* 2982 3032 * We want to get the resolved OID for the target, to ensure ··· 2994 3044 } 2995 3045 } 2996 3046 2997 - if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid, 3047 + if (files_log_ref_write(refs, lock->ref_name, old_oid, 2998 3048 &update->new_oid, update->committer_info, 2999 3049 update->msg, update->flags, err)) { 3000 3050 char *old_msg = strbuf_detach(err, NULL); ··· 3062 3112 for (i = 0; i < transaction->nr; i++) { 3063 3113 struct ref_update *update = transaction->updates[i]; 3064 3114 3065 - if ((update->flags & REF_HAVE_OLD) && 3115 + if (!(update->flags & REF_LOG_ONLY) && 3116 + (update->flags & REF_HAVE_OLD) && 3066 3117 !is_null_oid(&update->old_oid)) 3067 3118 BUG("initial ref transaction with old_sha1 set"); 3068 3119
+2 -1
refs/refs-internal.h
··· 662 662 663 663 /* 664 664 * Check if the ref must exist, this means that the old_oid or 665 - * old_target is non NULL. 665 + * old_target is non NULL. Log-only updates never require the old state to 666 + * match. 666 667 */ 667 668 int ref_update_expects_existing_old_ref(struct ref_update *update); 668 669
+17 -9
refs/reftable-backend.c
··· 1102 1102 if (ret) 1103 1103 return REF_TRANSACTION_ERROR_GENERIC; 1104 1104 1105 + if (u->flags & REF_LOG_USE_PROVIDED_OIDS) { 1106 + if (!(u->flags & REF_HAVE_OLD) || 1107 + !(u->flags & REF_HAVE_NEW) || 1108 + !(u->flags & REF_LOG_ONLY)) { 1109 + strbuf_addf(err, _("trying to write reflog for '%s'" 1110 + "with incomplete values"), u->refname); 1111 + return REF_TRANSACTION_ERROR_GENERIC; 1112 + } 1113 + 1114 + if (queue_transaction_update(refs, tx_data, u, &u->old_oid, err)) 1115 + return REF_TRANSACTION_ERROR_GENERIC; 1116 + return 0; 1117 + } 1118 + 1105 1119 /* Verify that the new object ID is valid. */ 1106 1120 if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) && 1107 1121 !(u->flags & REF_SKIP_OID_VERIFICATION) && ··· 1186 1200 if (ret > 0) { 1187 1201 /* The reference does not exist, but we expected it to. */ 1188 1202 strbuf_addf(err, _("cannot lock ref '%s': " 1189 - 1190 - 1191 1203 "unable to resolve reference '%s'"), 1192 1204 ref_update_original_update_refname(u), u->refname); 1193 1205 return REF_TRANSACTION_ERROR_NONEXISTENT_REF; ··· 1241 1253 1242 1254 new_update->parent_update = u; 1243 1255 1244 - /* 1245 - * Change the symbolic ref update to log only. Also, it 1246 - * doesn't need to check its old OID value, as that will be 1247 - * done when new_update is processed. 1248 - */ 1256 + /* Change the symbolic ref update to log only. */ 1249 1257 u->flags |= REF_LOG_ONLY | REF_NO_DEREF; 1250 - u->flags &= ~REF_HAVE_OLD; 1251 1258 } 1252 1259 } 1253 1260 ··· 1271 1278 ret = ref_update_check_old_target(referent->buf, u, err); 1272 1279 if (ret) 1273 1280 return ret; 1274 - } else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) { 1281 + } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD && 1282 + !oideq(&current_oid, &u->old_oid)) { 1275 1283 if (is_null_oid(&u->old_oid)) { 1276 1284 strbuf_addf(err, _("cannot lock ref '%s': " 1277 1285 "reference already exists"),
+1
t/meson.build
··· 204 204 't1418-reflog-exists.sh', 205 205 't1419-exclude-refs.sh', 206 206 't1420-lost-found.sh', 207 + 't1421-reflog-write.sh', 207 208 't1430-bad-ref-name.sh', 208 209 't1450-fsck.sh', 209 210 't1451-fsck-buffer.sh',
+126
t/t1421-reflog-write.sh
··· 1 + #!/bin/sh 2 + 3 + test_description='Manually write reflog entries' 4 + 5 + . ./test-lib.sh 6 + 7 + SIGNATURE="C O Mitter <committer@example.com> 1112911993 -0700" 8 + 9 + test_reflog_matches () { 10 + repo="$1" && 11 + refname="$2" && 12 + cat >actual && 13 + test-tool -C "$repo" ref-store main for-each-reflog-ent "$refname" >expected && 14 + test_cmp expected actual 15 + } 16 + 17 + test_expect_success 'invalid number of arguments' ' 18 + test_when_finished "rm -rf repo" && 19 + git init repo && 20 + ( 21 + cd repo && 22 + for args in "" "1" "1 2" "1 2 3" "1 2 3 4 5" 23 + do 24 + test_must_fail git reflog write $args 2>err && 25 + test_grep "usage: git reflog write" err || return 1 26 + done 27 + ) 28 + ' 29 + 30 + test_expect_success 'invalid refname' ' 31 + test_when_finished "rm -rf repo" && 32 + git init repo && 33 + ( 34 + cd repo && 35 + test_must_fail git reflog write "refs/heads/ invalid" $ZERO_OID $ZERO_OID first 2>err && 36 + test_grep "invalid reference name: " err 37 + ) 38 + ' 39 + 40 + test_expect_success 'unqualified refname is rejected' ' 41 + test_when_finished "rm -rf repo" && 42 + git init repo && 43 + ( 44 + cd repo && 45 + test_must_fail git reflog write unqualified $ZERO_OID $ZERO_OID first 2>err && 46 + test_grep "invalid reference name: " err 47 + ) 48 + ' 49 + 50 + test_expect_success 'nonexistent object IDs' ' 51 + test_when_finished "rm -rf repo" && 52 + git init repo && 53 + ( 54 + cd repo && 55 + test_must_fail git reflog write refs/heads/something $(test_oid deadbeef) $ZERO_OID old-object-id 2>err && 56 + test_grep "old object .* does not exist" err && 57 + test_must_fail git reflog write refs/heads/something $ZERO_OID $(test_oid deadbeef) new-object-id 2>err && 58 + test_grep "new object .* does not exist" err 59 + ) 60 + ' 61 + 62 + test_expect_success 'abbreviated object IDs' ' 63 + test_when_finished "rm -rf repo" && 64 + git init repo && 65 + ( 66 + cd repo && 67 + test_commit initial && 68 + abbreviated_oid=$(git rev-parse HEAD | test_copy_bytes 8) && 69 + test_must_fail git reflog write refs/heads/something $abbreviated_oid $ZERO_OID old-object-id 2>err && 70 + test_grep "invalid old object ID" err && 71 + test_must_fail git reflog write refs/heads/something $ZERO_OID $abbreviated_oid new-object-id 2>err && 72 + test_grep "invalid new object ID" err 73 + ) 74 + ' 75 + 76 + test_expect_success 'reflog message gets normalized' ' 77 + test_when_finished "rm -rf repo" && 78 + git init repo && 79 + ( 80 + cd repo && 81 + test_commit initial && 82 + COMMIT_OID=$(git rev-parse HEAD) && 83 + git reflog write HEAD $COMMIT_OID $COMMIT_OID "$(printf "message\nwith\nnewlines")" && 84 + git reflog show -1 --format=%gs HEAD >actual && 85 + echo "message with newlines" >expected && 86 + test_cmp expected actual 87 + ) 88 + ' 89 + 90 + test_expect_success 'simple writes' ' 91 + test_when_finished "rm -rf repo" && 92 + git init repo && 93 + ( 94 + cd repo && 95 + test_commit initial && 96 + COMMIT_OID=$(git rev-parse HEAD) && 97 + 98 + git reflog write refs/heads/something $ZERO_OID $COMMIT_OID first && 99 + test_reflog_matches . refs/heads/something <<-EOF && 100 + $ZERO_OID $COMMIT_OID $SIGNATURE first 101 + EOF 102 + 103 + git reflog write refs/heads/something $COMMIT_OID $COMMIT_OID second && 104 + test_reflog_matches . refs/heads/something <<-EOF 105 + $ZERO_OID $COMMIT_OID $SIGNATURE first 106 + $COMMIT_OID $COMMIT_OID $SIGNATURE second 107 + EOF 108 + ) 109 + ' 110 + 111 + test_expect_success 'can write to root ref' ' 112 + test_when_finished "rm -rf repo" && 113 + git init repo && 114 + ( 115 + cd repo && 116 + test_commit initial && 117 + COMMIT_OID=$(git rev-parse HEAD) && 118 + 119 + git reflog write ROOT_REF_HEAD $ZERO_OID $COMMIT_OID first && 120 + test_reflog_matches . ROOT_REF_HEAD <<-EOF 121 + $ZERO_OID $COMMIT_OID $SIGNATURE first 122 + EOF 123 + ) 124 + ' 125 + 126 + test_done
+15 -7
t/t1460-refs-migrate.sh
··· 7 7 8 8 . ./test-lib.sh 9 9 10 + print_all_reflog_entries () { 11 + repo=$1 && 12 + test-tool -C "$repo" ref-store main for-each-reflog >reflogs && 13 + while read reflog 14 + do 15 + echo "REFLOG: $reflog" && 16 + test-tool -C "$repo" ref-store main for-each-reflog-ent "$reflog" || 17 + return 1 18 + done <reflogs 19 + } 20 + 10 21 # Migrate the provided repository from one format to the other and 11 22 # verify that the references and logs are migrated over correctly. 12 23 # Usage: test_migration <repo> <format> [<skip_reflog_verify> [<options...>]] ··· 28 39 --format='%(refname) %(objectname) %(symref)' >expect && 29 40 if ! $skip_reflog_verify 30 41 then 31 - git -C "$repo" reflog --all >expect_logs && 32 - git -C "$repo" reflog list >expect_log_list 42 + print_all_reflog_entries "$repo" >expect_logs 33 43 fi && 34 44 35 45 git -C "$repo" refs migrate --ref-format="$format" "$@" && ··· 39 49 test_cmp expect actual && 40 50 if ! $skip_reflog_verify 41 51 then 42 - git -C "$repo" reflog --all >actual_logs && 43 - git -C "$repo" reflog list >actual_log_list && 44 - test_cmp expect_logs actual_logs && 45 - test_cmp expect_log_list actual_log_list 52 + print_all_reflog_entries "$repo" >actual_logs && 53 + test_cmp expect_logs actual_logs 46 54 fi && 47 55 48 56 git -C "$repo" rev-parse --show-ref-format >actual && ··· 273 281 test_commit -C repo second && 274 282 printf "update refs/heads/ref-%d HEAD\n" $(test_seq 3000) >stdin && 275 283 git -C repo update-ref --stdin <stdin && 276 - test_migration repo reftable 284 + test_migration repo reftable true 277 285 ' 278 286 279 287 test_expect_success 'migrating from files format deletes backend files' '