Merge branch 'jc/fsck-dropped-errors' into maint
authorJunio C Hamano <gitster@pobox.com>
Fri, 16 Oct 2015 21:32:50 +0000 (14:32 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 16 Oct 2015 21:32:50 +0000 (14:32 -0700)
There were some classes of errors that "git fsck" diagnosed to its
standard error that did not cause it to exit with non-zero status.

* jc/fsck-dropped-errors:
fsck: exit with non-zero when problems are found

1  2 
builtin/fsck.c
t/t1450-fsck.sh
diff --combined builtin/fsck.c
index 079470342fc926b97ae1c065d37926cf5a449101,63ab0bbb0c31e2ba3a6a08c5c78b84e290ebc128..b9a74f0cf6e169073994e466110fd1bde9febba7
@@@ -23,11 -23,8 +23,11 @@@ static int show_tags
  static int show_unreachable;
  static int include_reflogs = 1;
  static int check_full = 1;
 +static int connectivity_only;
  static int check_strict;
  static int keep_cache_objects;
 +static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT;
 +static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT;
  static struct object_id head_oid;
  static const char *head_points_at;
  static int errors_found;
@@@ -38,6 -35,7 +38,7 @@@ static int show_dangling = 1
  #define ERROR_OBJECT 01
  #define ERROR_REACHABLE 02
  #define ERROR_PACK 04
+ #define ERROR_REFS 010
  
  #ifdef NO_D_INO_IN_DIRENT
  #define SORT_DIRENT 0
  #define DIRENT_SORT_HINT(de) ((de)->d_ino)
  #endif
  
 -static void objreport(struct object *obj, const char *severity,
 -                      const char *err, va_list params)
 +static int fsck_config(const char *var, const char *value, void *cb)
  {
 -      fprintf(stderr, "%s in %s %s: ",
 -              severity, typename(obj->type), sha1_to_hex(obj->sha1));
 -      vfprintf(stderr, err, params);
 -      fputs("\n", stderr);
 +      if (strcmp(var, "fsck.skiplist") == 0) {
 +              const char *path;
 +              struct strbuf sb = STRBUF_INIT;
 +
 +              if (git_config_pathname(&path, var, value))
 +                      return 1;
 +              strbuf_addf(&sb, "skiplist=%s", path);
 +              free((char *)path);
 +              fsck_set_msg_types(&fsck_obj_options, sb.buf);
 +              strbuf_release(&sb);
 +              return 0;
 +      }
 +
 +      if (skip_prefix(var, "fsck.", &var)) {
 +              fsck_set_msg_type(&fsck_obj_options, var, value);
 +              return 0;
 +      }
 +
 +      return git_default_config(var, value, cb);
  }
  
 -__attribute__((format (printf, 2, 3)))
 -static int objerror(struct object *obj, const char *err, ...)
 +static void objreport(struct object *obj, const char *msg_type,
 +                      const char *err)
 +{
 +      fprintf(stderr, "%s in %s %s: %s\n",
 +              msg_type, typename(obj->type), sha1_to_hex(obj->sha1), err);
 +}
 +
 +static int objerror(struct object *obj, const char *err)
  {
 -      va_list params;
 -      va_start(params, err);
        errors_found |= ERROR_OBJECT;
 -      objreport(obj, "error", err, params);
 -      va_end(params);
 +      objreport(obj, "error", err);
        return -1;
  }
  
 -__attribute__((format (printf, 3, 4)))
 -static int fsck_error_func(struct object *obj, int type, const char *err, ...)
 +static int fsck_error_func(struct object *obj, int type, const char *message)
  {
 -      va_list params;
 -      va_start(params, err);
 -      objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params);
 -      va_end(params);
 +      objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message);
        return (type == FSCK_WARN) ? 0 : 1;
  }
  
  static struct object_array pending;
  
 -static int mark_object(struct object *obj, int type, void *data)
 +static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options)
  {
        struct object *parent = data;
  
  
  static void mark_object_reachable(struct object *obj)
  {
 -      mark_object(obj, OBJ_ANY, NULL);
 +      mark_object(obj, OBJ_ANY, NULL, NULL);
  }
  
  static int traverse_one_object(struct object *obj)
                if (parse_tree(tree) < 0)
                        return 1; /* error already displayed */
        }
 -      result = fsck_walk(obj, mark_object, obj);
 +      result = fsck_walk(obj, obj, &fsck_walk_options);
        if (tree)
                free_tree_buffer(tree);
        return result;
@@@ -174,7 -159,7 +175,7 @@@ static int traverse_reachable(void
        return !!result;
  }
  
 -static int mark_used(struct object *obj, int type, void *data)
 +static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options)
  {
        if (!obj)
                return 1;
@@@ -195,8 -180,6 +196,8 @@@ static void check_reachable_object(stru
        if (!(obj->flags & HAS_OBJ)) {
                if (has_sha1_pack(obj->sha1))
                        return; /* it is in pack - forget about it */
 +              if (connectivity_only && has_sha1_file(obj->sha1))
 +                      return;
                printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
                errors_found |= ERROR_REACHABLE;
                return;
@@@ -243,14 -226,13 +244,14 @@@ static void check_unreachable_object(st
                        printf("dangling %s %s\n", typename(obj->type),
                               sha1_to_hex(obj->sha1));
                if (write_lost_and_found) {
 -                      const char *filename = git_path("lost-found/%s/%s",
 +                      char *filename = git_pathdup("lost-found/%s/%s",
                                obj->type == OBJ_COMMIT ? "commit" : "other",
                                sha1_to_hex(obj->sha1));
                        FILE *f;
  
                        if (safe_create_leading_directories_const(filename)) {
                                error("Could not create lost-found");
 +                              free(filename);
                                return;
                        }
                        if (!(f = fopen(filename, "w")))
                        if (fclose(f))
                                die_errno("Could not finish '%s'",
                                          filename);
 +                      free(filename);
                }
                return;
        }
@@@ -316,9 -297,9 +317,9 @@@ static int fsck_obj(struct object *obj
                fprintf(stderr, "Checking %s %s\n",
                        typename(obj->type), sha1_to_hex(obj->sha1));
  
 -      if (fsck_walk(obj, mark_used, NULL))
 +      if (fsck_walk(obj, NULL, &fsck_obj_options))
                objerror(obj, "broken links");
 -      if (fsck_object(obj, NULL, 0, check_strict, fsck_error_func))
 +      if (fsck_object(obj, NULL, 0, &fsck_obj_options))
                return -1;
  
        if (obj->type == OBJ_TREE) {
@@@ -471,41 -452,35 +472,41 @@@ static void fsck_dir(int i, char *path
  
  static int default_refs;
  
 +static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1)
 +{
 +      struct object *obj;
 +
 +      if (!is_null_sha1(sha1)) {
 +              obj = lookup_object(sha1);
 +              if (obj) {
 +                      obj->used = 1;
 +                      mark_object_reachable(obj);
 +              } else {
 +                      error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1));
 +                      errors_found |= ERROR_REACHABLE;
 +              }
 +      }
 +}
 +
  static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
                const char *email, unsigned long timestamp, int tz,
                const char *message, void *cb_data)
  {
 -      struct object *obj;
 +      const char *refname = cb_data;
  
        if (verbose)
                fprintf(stderr, "Checking reflog %s->%s\n",
                        sha1_to_hex(osha1), sha1_to_hex(nsha1));
  
 -      if (!is_null_sha1(osha1)) {
 -              obj = lookup_object(osha1);
 -              if (obj) {
 -                      obj->used = 1;
 -                      mark_object_reachable(obj);
 -              }
 -      }
 -      obj = lookup_object(nsha1);
 -      if (obj) {
 -              obj->used = 1;
 -              mark_object_reachable(obj);
 -      }
 +      fsck_handle_reflog_sha1(refname, osha1);
 +      fsck_handle_reflog_sha1(refname, nsha1);
        return 0;
  }
  
  static int fsck_handle_reflog(const char *logname, const struct object_id *oid,
                              int flag, void *cb_data)
  {
 -      for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL);
 +      for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname);
        return 0;
  }
  
@@@ -521,8 -496,10 +522,10 @@@ static int fsck_handle_ref(const char *
                /* We'll continue with the rest despite the error.. */
                return 0;
        }
-       if (obj->type != OBJ_COMMIT && is_branch(refname))
+       if (obj->type != OBJ_COMMIT && is_branch(refname)) {
                error("%s: not a commit", refname);
+               errors_found |= ERROR_REFS;
+       }
        default_refs++;
        obj->used = 1;
        mark_object_reachable(obj);
@@@ -585,17 -562,23 +588,23 @@@ static int fsck_head_link(void
                fprintf(stderr, "Checking HEAD link\n");
  
        head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag);
-       if (!head_points_at)
+       if (!head_points_at) {
+               errors_found |= ERROR_REFS;
                return error("Invalid HEAD");
+       }
        if (!strcmp(head_points_at, "HEAD"))
                /* detached HEAD */
                null_is_error = 1;
-       else if (!starts_with(head_points_at, "refs/heads/"))
+       else if (!starts_with(head_points_at, "refs/heads/")) {
+               errors_found |= ERROR_REFS;
                return error("HEAD points to something strange (%s)",
                             head_points_at);
+       }
        if (is_null_oid(&head_oid)) {
-               if (null_is_error)
+               if (null_is_error) {
+                       errors_found |= ERROR_REFS;
                        return error("HEAD: detached HEAD points at nothing");
+               }
                fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
                        head_points_at + 11);
        }
@@@ -615,6 -598,7 +624,7 @@@ static int fsck_cache_tree(struct cache
                if (!obj) {
                        error("%s: invalid sha1 pointer in cache-tree",
                              sha1_to_hex(it->sha1));
+                       errors_found |= ERROR_REFS;
                        return 1;
                }
                obj->used = 1;
@@@ -641,7 -625,6 +651,7 @@@ static struct option fsck_opts[] = 
        OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
        OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
        OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")),
 +      OPT_BOOL(0, "connectivity-only", &connectivity_only, N_("check only connectivity")),
        OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")),
        OPT_BOOL(0, "lost-found", &write_lost_and_found,
                                N_("write dangling objects in .git/lost-found")),
@@@ -659,12 -642,6 +669,12 @@@ int cmd_fsck(int argc, const char **arg
  
        argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
  
 +      fsck_walk_options.walk = mark_object;
 +      fsck_obj_options.walk = mark_used;
 +      fsck_obj_options.error_func = fsck_error_func;
 +      if (check_strict)
 +              fsck_obj_options.strict = 1;
 +
        if (show_progress == -1)
                show_progress = isatty(2);
        if (verbose)
                include_reflogs = 0;
        }
  
 +      git_config(fsck_config, NULL);
 +
        fsck_head_link();
 -      fsck_object_dir(get_object_directory());
 +      if (!connectivity_only)
 +              fsck_object_dir(get_object_directory());
  
        prepare_alt_odb();
        for (alt = alt_odb_list; alt; alt = alt->next) {
diff --combined t/t1450-fsck.sh
index 956673b8a14d39ef6bece7629b2407b0c10a071a,0ad04da01606d678fb433d724a5a03ac04be2a37..dc09797021a6a5fc8eed6da4412164673ab484a1
@@@ -77,11 -77,31 +77,31 @@@ test_expect_success 'object with bad sh
  test_expect_success 'branch pointing to non-commit' '
        git rev-parse HEAD^{tree} >.git/refs/heads/invalid &&
        test_when_finished "git update-ref -d refs/heads/invalid" &&
-       git fsck 2>out &&
+       test_must_fail git fsck 2>out &&
        cat out &&
        grep "not a commit" out
  '
  
+ test_expect_success 'HEAD link pointing at a funny object' '
+       test_when_finished "mv .git/SAVED_HEAD .git/HEAD" &&
+       mv .git/HEAD .git/SAVED_HEAD &&
+       echo 0000000000000000000000000000000000000000 >.git/HEAD &&
+       # avoid corrupt/broken HEAD from interfering with repo discovery
+       test_must_fail env GIT_DIR=.git git fsck 2>out &&
+       cat out &&
+       grep "detached HEAD points" out
+ '
+ test_expect_success 'HEAD link pointing at a funny place' '
+       test_when_finished "mv .git/SAVED_HEAD .git/HEAD" &&
+       mv .git/HEAD .git/SAVED_HEAD &&
+       echo "ref: refs/funny/place" >.git/HEAD &&
+       # avoid corrupt/broken HEAD from interfering with repo discovery
+       test_must_fail env GIT_DIR=.git git fsck 2>out &&
+       cat out &&
+       grep "HEAD points to something strange" out
+ '
  test_expect_success 'email without @ is okay' '
        git cat-file commit HEAD >basis &&
        sed "s/@/AT/" basis >okay &&
@@@ -231,8 -251,8 +251,8 @@@ test_expect_success 'tag with incorrec
        git fsck --tags 2>out &&
  
        cat >expect <<-EOF &&
 -      warning in tag $tag: invalid '\''tag'\'' name: wrong name format
 -      warning in tag $tag: invalid format - expected '\''tagger'\'' line
 +      warning in tag $tag: badTagName: invalid '\''tag'\'' name: wrong name format
 +      warning in tag $tag: missingTaggerEntry: invalid format - expected '\''tagger'\'' line
        EOF
        test_cmp expect out
  '
@@@ -287,17 -307,6 +307,17 @@@ test_expect_success 'rev-list --verify-
        grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out
  '
  
 +test_expect_success 'force fsck to ignore double author' '
 +      git cat-file commit HEAD >basis &&
 +      sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors &&
 +      new=$(git hash-object -t commit -w --stdin <multiple-authors) &&
 +      test_when_finished "remove_object $new" &&
 +      git update-ref refs/heads/bogus "$new" &&
 +      test_when_finished "git update-ref -d refs/heads/bogus" &&
 +      test_must_fail git fsck &&
 +      git -c fsck.multipleAuthors=ignore fsck
 +'
 +
  _bz='\0'
  _bz5="$_bz$_bz$_bz$_bz$_bz"
  _bz20="$_bz5$_bz5$_bz5$_bz5"
@@@ -431,26 -440,4 +451,26 @@@ test_expect_success 'fsck notices ref p
        test_must_fail git -C missing fsck
  '
  
 +test_expect_success 'fsck --connectivity-only' '
 +      rm -rf connectivity-only &&
 +      git init connectivity-only &&
 +      (
 +              cd connectivity-only &&
 +              touch empty &&
 +              git add empty &&
 +              test_commit empty &&
 +              empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 &&
 +              rm -f $empty &&
 +              echo invalid >$empty &&
 +              test_must_fail git fsck --strict &&
 +              git fsck --strict --connectivity-only &&
 +              tree=$(git rev-parse HEAD:) &&
 +              suffix=${tree#??} &&
 +              tree=.git/objects/${tree%$suffix}/$suffix &&
 +              rm -f $tree &&
 +              echo invalid >$tree &&
 +              test_must_fail git fsck --strict --connectivity-only
 +      )
 +'
 +
  test_done