fsck: optionally show more helpful info for broken links

When reporting broken links between commits/trees/blobs, it would be quite helpful at times if the user would be told how the object is supposed to be reachable. With the new --name-objects option, git-fsck will try to do exactly that: name the objects in a way that shows how they are reachable. For example, when some reflog got corrupted and a blob is missing that should not be, the user might want to remove the corresponding reflog entry. This option helps them find that entry: `git fsck` will now report something like this: broken link from tree b5eb6ff... (refs/stash@{<date>}~37:) to blob ec5cf80... Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Jul 17, 2016 at 13:00 UTC 90cf590f53f2939a47ca7b397e270e8228699829
4 files changed +85 -9
Documentation/git-fsck.txt
+8 -1
@@ -11,7 +11,8 @@ SYNOPSIS
11 [verse]
12 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
13 [--[no-]full] [--strict] [--verbose] [--lost-found]
14 - [--[no-]dangling] [--[no-]progress] [--connectivity-only] [<object>*]
14 + [--[no-]dangling] [--[no-]progress] [--connectivity-only]
15 + [--[no-]name-objects] [<object>*]
16
17 DESCRIPTION
18 -----------
@@ -82,6 +83,12 @@ index file, all SHA-1 references in `refs` namespace, and all reflogs
83 a blob, the contents are written into the file, rather than
84 its object name.
85
86 +--name-objects::
87 + When displaying names of reachable objects, in addition to the
88 + SHA-1 also display a name that describes *how* they are reachable,
89 + compatible with linkgit:git-rev-parse[1], e.g.
90 + `HEAD@{1234567890}~25^2:src/`.
91 +
92 --[no-]progress::
93 Progress status is reported on the standard error stream by
94 default when it is attached to a terminal, unless
builtin/fsck.c
+38 -4
@@ -13,6 +13,7 @@
13 #include "dir.h"
14 #include "progress.h"
15 #include "streaming.h"
16 +#include "decorate.h"
17
18 #define REACHABLE 0x0001
19 #define SEEN 0x0002
@@ -35,6 +36,7 @@ static int write_lost_and_found;
36 static int verbose;
37 static int show_progress = -1;
38 static int show_dangling = 1;
39 +static int name_objects;
40 #define ERROR_OBJECT 01
41 #define ERROR_REACHABLE 02
42 #define ERROR_PACK 04
@@ -42,7 +44,16 @@ static int show_dangling = 1;
44
45 static const char *describe_object(struct object *obj)
46 {
45 - return oid_to_hex(&obj->oid);
47 + static struct strbuf buf = STRBUF_INIT;
48 + char *name = name_objects ?
49 + lookup_decoration(fsck_walk_options.object_names, obj) : NULL;
50 +
51 + strbuf_reset(&buf);
52 + strbuf_addstr(&buf, oid_to_hex(&obj->oid));
53 + if (name)
54 + strbuf_addf(&buf, " (%s)", name);
55 +
56 + return buf.buf;
57 }
58
59 static int fsck_config(const char *var, const char *value, void *cb)
@@ -378,13 +389,18 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
389
390 static int default_refs;
391
381 -static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1)
392 +static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1,
393 + unsigned long timestamp)
394 {
395 struct object *obj;
396
397 if (!is_null_sha1(sha1)) {
398 obj = lookup_object(sha1);
399 if (obj) {
400 + if (timestamp && name_objects)
401 + add_decoration(fsck_walk_options.object_names,
402 + obj,
403 + xstrfmt("%s@{%ld}", refname, timestamp));
404 obj->used = 1;
405 mark_object_reachable(obj);
406 } else {
@@ -404,8 +420,8 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
420 fprintf(stderr, "Checking reflog %s->%s\n",
421 sha1_to_hex(osha1), sha1_to_hex(nsha1));
422
407 - fsck_handle_reflog_sha1(refname, osha1);
408 - fsck_handle_reflog_sha1(refname, nsha1);
423 + fsck_handle_reflog_sha1(refname, osha1, 0);
424 + fsck_handle_reflog_sha1(refname, nsha1, timestamp);
425 return 0;
426 }
427
@@ -434,6 +450,9 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid,
450 }
451 default_refs++;
452 obj->used = 1;
453 + if (name_objects)
454 + add_decoration(fsck_walk_options.object_names,
455 + obj, xstrdup(refname));
456 mark_object_reachable(obj);
457
458 return 0;
@@ -549,6 +568,9 @@ static int fsck_cache_tree(struct cache_tree *it)
568 return 1;
569 }
570 obj->used = 1;
571 + if (name_objects)
572 + add_decoration(fsck_walk_options.object_names,
573 + obj, xstrdup(":"));
574 mark_object_reachable(obj);
575 if (obj->type != OBJ_TREE)
576 err |= objerror(obj, "non-tree in cache-tree");
@@ -577,6 +599,7 @@ static struct option fsck_opts[] = {
599 OPT_BOOL(0, "lost-found", &write_lost_and_found,
600 N_("write dangling objects in .git/lost-found")),
601 OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
602 + OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
603 OPT_END(),
604 };
605
@@ -606,6 +629,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
629 include_reflogs = 0;
630 }
631
632 + if (name_objects)
633 + fsck_walk_options.object_names =
634 + xcalloc(1, sizeof(struct decoration));
635 +
636 git_config(fsck_config, NULL);
637
638 fsck_head_link();
@@ -661,6 +688,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
688 continue;
689
690 obj->used = 1;
691 + if (name_objects)
692 + add_decoration(fsck_walk_options.object_names,
693 + obj, xstrdup(arg));
694 mark_object_reachable(obj);
695 heads++;
696 continue;
@@ -693,6 +723,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
723 continue;
724 obj = &blob->object;
725 obj->used = 1;
726 + if (name_objects)
727 + add_decoration(fsck_walk_options.object_names,
728 + obj,
729 + xstrfmt(":%s", active_cache[i]->name));
730 mark_object_reachable(obj);
731 }
732 if (active_cache_tree)
fsck.c
+17 -4
@@ -323,6 +323,19 @@ static void put_object_name(struct fsck_options *options, struct object *obj,
323 va_end(ap);
324 }
325
326 +static const char *describe_object(struct fsck_options *o, struct object *obj)
327 +{
328 + static struct strbuf buf = STRBUF_INIT;
329 + char *name;
330 +
331 + strbuf_reset(&buf);
332 + strbuf_addstr(&buf, oid_to_hex(&obj->oid));
333 + if (o->object_names && (name = lookup_decoration(o->object_names, obj)))
334 + strbuf_addf(&buf, " (%s)", name);
335 +
336 + return buf.buf;
337 +}
338 +
339 static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *options)
340 {
341 struct tree_desc desc;
@@ -358,7 +371,7 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op
371 }
372 else {
373 result = error("in tree %s: entry %s has bad mode %.6o",
361 - oid_to_hex(&tree->object.oid), entry.path, entry.mode);
374 + describe_object(options, &tree->object), entry.path, entry.mode);
375 }
376 if (result < 0)
377 return result;
@@ -454,7 +467,7 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
467 case OBJ_TAG:
468 return fsck_walk_tag((struct tag *)obj, data, options);
469 default:
457 - error("Unknown object type for %s", oid_to_hex(&obj->oid));
470 + error("Unknown object type for %s", describe_object(options, obj));
471 return -1;
472 }
473 }
@@ -901,9 +914,9 @@ int fsck_error_function(struct fsck_options *o,
914 struct object *obj, int msg_type, const char *message)
915 {
916 if (msg_type == FSCK_WARN) {
904 - warning("object %s: %s", oid_to_hex(&obj->oid), message);
917 + warning("object %s: %s", describe_object(o, obj), message);
918 return 0;
919 }
907 - error("object %s: %s", oid_to_hex(&obj->oid), message);
920 + error("object %s: %s", describe_object(o, obj), message);
921 return 1;
922 }
t/t1450-fsck.sh
+22
@@ -523,4 +523,26 @@ test_expect_success 'fsck --connectivity-only' '
523 )
524 '
525
526 +remove_loose_object () {
527 + sha1="$(git rev-parse "$1")" &&
528 + remainder=${sha1#??} &&
529 + firsttwo=${sha1%$remainder} &&
530 + rm .git/objects/$firsttwo/$remainder
531 +}
532 +
533 +test_expect_success 'fsck --name-objects' '
534 + rm -rf name-objects &&
535 + git init name-objects &&
536 + (
537 + cd name-objects &&
538 + test_commit julius caesar.t &&
539 + test_commit augustus &&
540 + test_commit caesar &&
541 + remove_loose_object $(git rev-parse julius:caesar.t) &&
542 + test_must_fail git fsck --name-objects >out &&
543 + tree=$(git rev-parse --verify julius:) &&
544 + grep "$tree (\(refs/heads/master\|HEAD\)@{[0-9]*}:" out
545 + )
546 +'
547 +
548 test_done