Skip to content

Commit dc38dda

Browse files
committed
Merge branch 'sj/ref-contents-check' into seen
"git fsck" learned to issue warnings on "curiously formatted" ref contents that have always been taken valid but something Git wouldn't have written itself (e.g., missing terminating end-of-line after the full object name). * sj/ref-contents-check: ref: add symlink ref content check for files backend ref: add symref content check for files backend ref: add more strict checks for regular refs ref: port git-fsck(1) regular refs check for files backend ref: initialize "fsck_ref_report" with zero
2 parents f33dd2b + c2ba5b4 commit dc38dda

File tree

6 files changed

+552
-5
lines changed

6 files changed

+552
-5
lines changed

Documentation/fsck-msgids.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@
1919
`badParentSha1`::
2020
(ERROR) A commit object has a bad parent sha1.
2121

22+
`badRefContent`::
23+
(ERROR) A ref has bad content.
24+
2225
`badRefFiletype`::
2326
(ERROR) A ref has a bad file type.
2427

2528
`badRefName`::
2629
(ERROR) A ref has an invalid format.
2730

31+
`badReferentFiletype`::
32+
(ERROR) The referent of a symref has a bad file type.
33+
34+
`badReferentName`::
35+
(ERROR) The referent name of a symref is invalid.
36+
2837
`badTagName`::
2938
(INFO) A tag has an invalid format.
3039

@@ -46,6 +55,9 @@
4655
`emptyName`::
4756
(WARN) A path contains an empty name.
4857

58+
`escapeReferent`::
59+
(ERROR) The referent of a symref is outside the "ref" directory.
60+
4961
`extraHeaderEntry`::
5062
(IGNORE) Extra headers found after `tagger`.
5163

@@ -170,6 +182,19 @@
170182
`nullSha1`::
171183
(WARN) Tree contains entries pointing to a null sha1.
172184

185+
`refMissingNewline`::
186+
(INFO) A ref does not end with newline. This will be
187+
considered an error in the future.
188+
189+
`symlinkRef`::
190+
(INFO) A symref uses the symbolic link. This kind of symref may
191+
be considered ERROR in the future when totally dropping the
192+
symlink support.
193+
194+
`trailingRefContent`::
195+
(INFO) A ref has trailing content. This will be
196+
considered an error in the future.
197+
173198
`treeNotSorted`::
174199
(ERROR) A tree is not properly sorted.
175200

fsck.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,17 @@ enum fsck_msg_type {
3131
FUNC(BAD_NAME, ERROR) \
3232
FUNC(BAD_OBJECT_SHA1, ERROR) \
3333
FUNC(BAD_PARENT_SHA1, ERROR) \
34+
FUNC(BAD_REF_CONTENT, ERROR) \
3435
FUNC(BAD_REF_FILETYPE, ERROR) \
3536
FUNC(BAD_REF_NAME, ERROR) \
37+
FUNC(BAD_REFERENT_FILETYPE, ERROR) \
38+
FUNC(BAD_REFERENT_NAME, ERROR) \
3639
FUNC(BAD_TIMEZONE, ERROR) \
3740
FUNC(BAD_TREE, ERROR) \
3841
FUNC(BAD_TREE_SHA1, ERROR) \
3942
FUNC(BAD_TYPE, ERROR) \
4043
FUNC(DUPLICATE_ENTRIES, ERROR) \
44+
FUNC(ESCAPE_REFERENT, ERROR) \
4145
FUNC(MISSING_AUTHOR, ERROR) \
4246
FUNC(MISSING_COMMITTER, ERROR) \
4347
FUNC(MISSING_EMAIL, ERROR) \
@@ -84,6 +88,9 @@ enum fsck_msg_type {
8488
FUNC(MAILMAP_SYMLINK, INFO) \
8589
FUNC(BAD_TAG_NAME, INFO) \
8690
FUNC(MISSING_TAGGER_ENTRY, INFO) \
91+
FUNC(REF_MISSING_NEWLINE, INFO) \
92+
FUNC(SYMLINK_REF, INFO) \
93+
FUNC(TRAILING_REF_CONTENT, INFO) \
8794
/* ignored (elevated when requested) */ \
8895
FUNC(EXTRA_HEADER_ENTRY, IGNORE)
8996

refs.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,7 @@ static int refs_read_special_head(struct ref_store *ref_store,
17881788
}
17891789

17901790
result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
1791-
oid, referent, type, failure_errno);
1791+
oid, referent, type, NULL, failure_errno);
17921792

17931793
done:
17941794
strbuf_release(&full_path);

refs/files-backend.c

Lines changed: 184 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#define USE_THE_REPOSITORY_VARIABLE
22

33
#include "../git-compat-util.h"
4+
#include "../abspath.h"
45
#include "../config.h"
56
#include "../copy.h"
67
#include "../environment.h"
@@ -568,7 +569,7 @@ static int read_ref_internal(struct ref_store *ref_store, const char *refname,
568569
buf = sb_contents.buf;
569570

570571
ret = parse_loose_ref_contents(ref_store->repo->hash_algo, buf,
571-
oid, referent, type, &myerr);
572+
oid, referent, type, NULL, &myerr);
572573

573574
out:
574575
if (ret && !myerr)
@@ -605,7 +606,7 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn
605606
int parse_loose_ref_contents(const struct git_hash_algo *algop,
606607
const char *buf, struct object_id *oid,
607608
struct strbuf *referent, unsigned int *type,
608-
int *failure_errno)
609+
const char **trailing, int *failure_errno)
609610
{
610611
const char *p;
611612
if (skip_prefix(buf, "ref:", &buf)) {
@@ -627,6 +628,10 @@ int parse_loose_ref_contents(const struct git_hash_algo *algop,
627628
*failure_errno = EINVAL;
628629
return -1;
629630
}
631+
632+
if (trailing)
633+
*trailing = p;
634+
630635
return 0;
631636
}
632637

@@ -3504,6 +3509,181 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
35043509
const char *refs_check_dir,
35053510
struct dir_iterator *iter);
35063511

3512+
/*
3513+
* Check the symref "referent" and "referent_path". For textual symref,
3514+
* "referent" would be the content after "refs:". For symlink ref,
3515+
* "referent" would be the relative path agaignst "gitdir" which should
3516+
* be the same as the textual symref literally.
3517+
*/
3518+
static int files_fsck_symref_target(struct fsck_options *o,
3519+
struct fsck_ref_report *report,
3520+
struct strbuf *referent,
3521+
struct strbuf *referent_path,
3522+
unsigned int symbolic_link)
3523+
{
3524+
size_t len = referent->len - 1;
3525+
struct stat st;
3526+
int ret = 0;
3527+
3528+
if (!starts_with(referent->buf, "refs/")) {
3529+
ret = fsck_report_ref(o, report,
3530+
FSCK_MSG_ESCAPE_REFERENT,
3531+
"points to ref outside the refs directory");
3532+
goto out;
3533+
}
3534+
3535+
if (!symbolic_link && referent->buf[referent->len - 1] != '\n') {
3536+
ret = fsck_report_ref(o, report,
3537+
FSCK_MSG_REF_MISSING_NEWLINE,
3538+
"missing newline");
3539+
len++;
3540+
}
3541+
3542+
if (!symbolic_link)
3543+
strbuf_rtrim(referent);
3544+
3545+
if (check_refname_format(referent->buf, 0)) {
3546+
ret = fsck_report_ref(o, report,
3547+
FSCK_MSG_BAD_REFERENT_NAME,
3548+
"points to refname with invalid format");
3549+
goto out;
3550+
}
3551+
3552+
if (!symbolic_link && len != referent->len) {
3553+
ret = fsck_report_ref(o, report,
3554+
FSCK_MSG_TRAILING_REF_CONTENT,
3555+
"trailing garbage in ref");
3556+
}
3557+
3558+
/*
3559+
* Dangling symrefs are common and so we don't report them.
3560+
*/
3561+
if (lstat(referent_path->buf, &st)) {
3562+
if (errno != ENOENT) {
3563+
ret = error_errno(_("unable to stat '%s'"),
3564+
referent_path->buf);
3565+
}
3566+
goto out;
3567+
}
3568+
3569+
/*
3570+
* We cannot distinguish whether "refs/heads/a" is a directory or not by
3571+
* using "check_refname_format(referent->buf, 0)". Instead, we need to
3572+
* check the file type of the target.
3573+
*/
3574+
if (S_ISDIR(st.st_mode)) {
3575+
ret = fsck_report_ref(o, report,
3576+
FSCK_MSG_BAD_REFERENT_FILETYPE,
3577+
"points to the directory");
3578+
goto out;
3579+
}
3580+
3581+
out:
3582+
return ret;
3583+
}
3584+
3585+
static int files_fsck_refs_content(struct ref_store *ref_store,
3586+
struct fsck_options *o,
3587+
const char *refs_check_dir,
3588+
struct dir_iterator *iter)
3589+
{
3590+
struct strbuf referent_path = STRBUF_INIT;
3591+
struct strbuf ref_content = STRBUF_INIT;
3592+
struct strbuf abs_gitdir = STRBUF_INIT;
3593+
struct strbuf referent = STRBUF_INIT;
3594+
struct strbuf refname = STRBUF_INIT;
3595+
struct fsck_ref_report report = {0};
3596+
const char *trailing = NULL;
3597+
unsigned int type = 0;
3598+
int failure_errno = 0;
3599+
struct object_id oid;
3600+
int ret = 0;
3601+
3602+
strbuf_addf(&refname, "%s/%s", refs_check_dir, iter->relative_path);
3603+
report.path = refname.buf;
3604+
3605+
if (S_ISLNK(iter->st.st_mode)) {
3606+
const char* relative_referent_path;
3607+
3608+
ret = fsck_report_ref(o, &report,
3609+
FSCK_MSG_SYMLINK_REF,
3610+
"use deprecated symbolic link for symref");
3611+
3612+
strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
3613+
strbuf_normalize_path(&abs_gitdir);
3614+
if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
3615+
strbuf_addch(&abs_gitdir, '/');
3616+
3617+
strbuf_add_real_path(&referent_path, iter->path.buf);
3618+
3619+
if (!skip_prefix(referent_path.buf,
3620+
abs_gitdir.buf,
3621+
&relative_referent_path)) {
3622+
ret = fsck_report_ref(o, &report,
3623+
FSCK_MSG_ESCAPE_REFERENT,
3624+
"point to target outside gitdir");
3625+
goto cleanup;
3626+
}
3627+
3628+
strbuf_addstr(&referent, relative_referent_path);
3629+
ret = files_fsck_symref_target(o, &report,
3630+
&referent, &referent_path, 1);
3631+
3632+
goto cleanup;
3633+
}
3634+
3635+
if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
3636+
ret = error_errno(_("unable to read ref '%s/%s'"),
3637+
refs_check_dir, iter->relative_path);
3638+
goto cleanup;
3639+
}
3640+
3641+
if (parse_loose_ref_contents(ref_store->repo->hash_algo,
3642+
ref_content.buf, &oid, &referent,
3643+
&type, &trailing, &failure_errno)) {
3644+
ret = fsck_report_ref(o, &report,
3645+
FSCK_MSG_BAD_REF_CONTENT,
3646+
"invalid ref content");
3647+
goto cleanup;
3648+
}
3649+
3650+
if (!(type & REF_ISSYMREF)) {
3651+
if (!*trailing) {
3652+
ret = fsck_report_ref(o, &report,
3653+
FSCK_MSG_REF_MISSING_NEWLINE,
3654+
"missing newline");
3655+
goto cleanup;
3656+
}
3657+
3658+
if (*trailing != '\n' || *(trailing + 1)) {
3659+
ret = fsck_report_ref(o, &report,
3660+
FSCK_MSG_TRAILING_REF_CONTENT,
3661+
"trailing garbage in ref");
3662+
goto cleanup;
3663+
}
3664+
} else {
3665+
strbuf_addf(&referent_path, "%s/%s",
3666+
ref_store->gitdir, referent.buf);
3667+
/*
3668+
* the referent may contain the spaces and the newline, need to
3669+
* trim for path.
3670+
*/
3671+
strbuf_rtrim(&referent_path);
3672+
ret = files_fsck_symref_target(o, &report,
3673+
&referent,
3674+
&referent_path,
3675+
0);
3676+
}
3677+
3678+
cleanup:
3679+
strbuf_release(&refname);
3680+
strbuf_release(&ref_content);
3681+
strbuf_release(&referent);
3682+
strbuf_release(&referent_path);
3683+
strbuf_release(&abs_gitdir);
3684+
return ret;
3685+
}
3686+
35073687
static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
35083688
struct fsck_options *o,
35093689
const char *refs_check_dir,
@@ -3520,7 +3700,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
35203700
goto cleanup;
35213701

35223702
if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
3523-
struct fsck_ref_report report = { .path = NULL };
3703+
struct fsck_ref_report report = { 0 };
35243704

35253705
strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
35263706
report.path = sb.buf;
@@ -3586,6 +3766,7 @@ static int files_fsck_refs(struct ref_store *ref_store,
35863766
{
35873767
files_fsck_refs_fn fsck_refs_fn[]= {
35883768
files_fsck_refs_name,
3769+
files_fsck_refs_content,
35893770
NULL,
35903771
};
35913772

refs/refs-internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ struct ref_store {
715715
int parse_loose_ref_contents(const struct git_hash_algo *algop,
716716
const char *buf, struct object_id *oid,
717717
struct strbuf *referent, unsigned int *type,
718-
int *failure_errno);
718+
const char **trailing, int *failure_errno);
719719

720720
/*
721721
* Fill in the generic part of refs and add it to our collection of

0 commit comments

Comments
 (0)