Skip to content

Commit

Permalink
diff: do not use null sha1 as a sentinel value
Browse files Browse the repository at this point in the history
The diff code represents paths using the diff_filespec
struct. This struct has a sha1 to represent the sha1 of the
content at that path, as well as a sha1_valid member which
indicates whether its sha1 field is actually useful. If
sha1_valid is not true, then the filespec represents a
working tree file (e.g., for the no-index case, or for when
the index is not up-to-date).

The diff_filespec is only used internally, though. At the
interfaces to the diff subsystem, callers feed the sha1
directly, and we create a diff_filespec from it. It's at
that point that we look at the sha1 and decide whether it is
valid or not; callers may pass the null sha1 as a sentinel
value to indicate that it is not.

We should not typically see the null sha1 coming from any
other source (e.g., in the index itself, or from a tree).
However, a corrupt tree might have a null sha1, which would
cause "diff --patch" to accidentally diff the working tree
version of a file instead of treating it as a blob.

This patch extends the edges of the diff interface to accept
a "sha1_valid" flag whenever we accept a sha1, and to use
that flag when creating a filespec. In some cases, this
means passing the flag through several layers, making the
code change larger than would be desirable.

One alternative would be to simply die() upon seeing
corrupted trees with null sha1s. However, this fix more
directly addresses the problem (while bogus sha1s in a tree
are probably a bad thing, it is really the sentinel
confusion sending us down the wrong code path that is what
makes it devastating). And it means that git is more capable
of examining and debugging these corrupted trees. For
example, you can still "diff --raw" such a tree to find out
when the bogus entry was introduced; you just cannot do a
"--patch" diff (just as you could not with any other
corrupted tree, as we do not have any content to diff).

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
peff authored and gitster committed Jul 29, 2012
1 parent d0f1ea6 commit e545010
Show file tree
Hide file tree
Showing 14 changed files with 135 additions and 32 deletions.
2 changes: 1 addition & 1 deletion builtin.h
Expand Up @@ -42,7 +42,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);

extern int check_pager_config(const char *cmd);

extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size);
extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size);

extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
Expand Down
9 changes: 5 additions & 4 deletions builtin/blame.c
Expand Up @@ -96,14 +96,15 @@ struct origin {
int textconv_object(const char *path,
unsigned mode,
const unsigned char *sha1,
int sha1_valid,
char **buf,
unsigned long *buf_size)
{
struct diff_filespec *df;
struct userdiff_driver *textconv;

df = alloc_filespec(path);
fill_filespec(df, sha1, mode);
fill_filespec(df, sha1, sha1_valid, mode);
textconv = get_textconv(df);
if (!textconv) {
free_filespec(df);
Expand All @@ -128,7 +129,7 @@ static void fill_origin_blob(struct diff_options *opt,

num_read_blob++;
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size))
textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size))
;
else
file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
Expand Down Expand Up @@ -2114,7 +2115,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
switch (st.st_mode & S_IFMT) {
case S_IFREG:
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len))
textconv_object(read_from, mode, null_sha1, 0, &buf_ptr, &buf_len))
strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
die_errno("cannot open or read '%s'", read_from);
Expand Down Expand Up @@ -2506,7 +2507,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
die("no such path %s in %s", path, final_commit_name);

if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf,
textconv_object(path, o->mode, o->blob_sha1, 1, (char **) &sb.final_buf,
&sb.final_buf_size))
;
else
Expand Down
2 changes: 1 addition & 1 deletion builtin/cat-file.c
Expand Up @@ -143,7 +143,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
die("git cat-file --textconv %s: <object> must be <sha1:path>",
obj_name);

if (!textconv_object(obj_context.path, obj_context.mode, sha1, &buf, &size))
if (!textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size))
die("git cat-file --textconv: unable to run textconv on %s",
obj_name);
break;
Expand Down
8 changes: 6 additions & 2 deletions builtin/diff.c
Expand Up @@ -29,6 +29,8 @@ static void stuff_change(struct diff_options *opt,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
int old_sha1_valid,
int new_sha1_valid,
const char *old_name,
const char *new_name)
{
Expand All @@ -54,8 +56,8 @@ static void stuff_change(struct diff_options *opt,

one = alloc_filespec(old_name);
two = alloc_filespec(new_name);
fill_filespec(one, old_sha1, old_mode);
fill_filespec(two, new_sha1, new_mode);
fill_filespec(one, old_sha1, old_sha1_valid, old_mode);
fill_filespec(two, new_sha1, new_sha1_valid, new_mode);

diff_queue(&diff_queued_diff, one, two);
}
Expand Down Expand Up @@ -84,6 +86,7 @@ static int builtin_diff_b_f(struct rev_info *revs,
stuff_change(&revs->diffopt,
blob[0].mode, canon_mode(st.st_mode),
blob[0].sha1, null_sha1,
1, 0,
path, path);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
Expand All @@ -108,6 +111,7 @@ static int builtin_diff_blobs(struct rev_info *revs,
stuff_change(&revs->diffopt,
blob[0].mode, blob[1].mode,
blob[0].sha1, blob[1].sha1,
1, 1,
blob[0].name, blob[1].name);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
Expand Down
4 changes: 2 additions & 2 deletions combine-diff.c
Expand Up @@ -111,7 +111,7 @@ static char *grab_blob(const unsigned char *sha1, unsigned int mode,
return xcalloc(1, 1);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(path);
fill_filespec(df, sha1, mode);
fill_filespec(df, sha1, 1, mode);
*size = fill_textconv(textconv, df, &blob);
free_filespec(df);
} else {
Expand Down Expand Up @@ -823,7 +823,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
&result_size, NULL, NULL);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(elem->path);
fill_filespec(df, null_sha1, st.st_mode);
fill_filespec(df, null_sha1, 0, st.st_mode);
result_size = fill_textconv(textconv, df, &result);
free_filespec(df);
} else if (0 <= (fd = open(elem->path, O_RDONLY))) {
Expand Down
20 changes: 12 additions & 8 deletions diff-lib.c
Expand Up @@ -206,7 +206,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
if (silent_on_removed)
continue;
diff_addremove(&revs->diffopt, '-', ce->ce_mode,
ce->sha1, ce->name, 0);
ce->sha1, !is_null_sha1(ce->sha1),
ce->name, 0);
continue;
}
changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
Expand All @@ -220,6 +221,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
newmode = ce_mode_from_stat(ce, st.st_mode);
diff_change(&revs->diffopt, oldmode, newmode,
ce->sha1, (changed ? null_sha1 : ce->sha1),
!is_null_sha1(ce->sha1), (changed ? 0 : !is_null_sha1(ce->sha1)),
ce->name, 0, dirty_submodule);

}
Expand All @@ -236,11 +238,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
static void diff_index_show_file(struct rev_info *revs,
const char *prefix,
struct cache_entry *ce,
const unsigned char *sha1, unsigned int mode,
const unsigned char *sha1, int sha1_valid,
unsigned int mode,
unsigned dirty_submodule)
{
diff_addremove(&revs->diffopt, prefix[0], mode,
sha1, ce->name, dirty_submodule);
sha1, sha1_valid, ce->name, dirty_submodule);
}

static int get_stat_data(struct cache_entry *ce,
Expand Down Expand Up @@ -295,7 +298,7 @@ static void show_new_file(struct rev_info *revs,
&dirty_submodule, &revs->diffopt) < 0)
return;

diff_index_show_file(revs, "+", new, sha1, mode, dirty_submodule);
diff_index_show_file(revs, "+", new, sha1, !is_null_sha1(sha1), mode, dirty_submodule);
}

static int show_modified(struct rev_info *revs,
Expand All @@ -312,7 +315,7 @@ static int show_modified(struct rev_info *revs,
&dirty_submodule, &revs->diffopt) < 0) {
if (report_missing)
diff_index_show_file(revs, "-", old,
old->sha1, old->ce_mode, 0);
old->sha1, 1, old->ce_mode, 0);
return -1;
}

Expand Down Expand Up @@ -347,7 +350,8 @@ static int show_modified(struct rev_info *revs,
return 0;

diff_change(&revs->diffopt, oldmode, mode,
old->sha1, sha1, old->name, 0, dirty_submodule);
old->sha1, sha1, 1, !is_null_sha1(sha1),
old->name, 0, dirty_submodule);
return 0;
}

Expand Down Expand Up @@ -380,7 +384,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
struct diff_filepair *pair;
pair = diff_unmerge(&revs->diffopt, idx->name);
if (tree)
fill_filespec(pair->one, tree->sha1, tree->ce_mode);
fill_filespec(pair->one, tree->sha1, 1, tree->ce_mode);
return;
}

Expand All @@ -396,7 +400,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
* Something removed from the tree?
*/
if (!idx) {
diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode, 0);
diff_index_show_file(revs, "-", tree, tree->sha1, 1, tree->ce_mode, 0);
return;
}

Expand Down
4 changes: 2 additions & 2 deletions diff-no-index.c
Expand Up @@ -141,8 +141,8 @@ static int queue_diff(struct diff_options *o,
name2 = "/dev/null";
d1 = alloc_filespec(name1);
d2 = alloc_filespec(name2);
fill_filespec(d1, null_sha1, mode1);
fill_filespec(d2, null_sha1, mode2);
fill_filespec(d1, null_sha1, 0, mode1);
fill_filespec(d2, null_sha1, 0, mode2);

diff_queue(&diff_queued_diff, d1, d2);
return 0;
Expand Down
16 changes: 10 additions & 6 deletions diff.c
Expand Up @@ -2443,12 +2443,12 @@ void free_filespec(struct diff_filespec *spec)
}

void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
unsigned short mode)
int sha1_valid, unsigned short mode)
{
if (mode) {
spec->mode = canon_mode(mode);
hashcpy(spec->sha1, sha1);
spec->sha1_valid = !is_null_sha1(sha1);
spec->sha1_valid = sha1_valid;
}
}

Expand Down Expand Up @@ -4589,6 +4589,7 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
int sha1_valid,
const char *concatpath, unsigned dirty_submodule)
{
struct diff_filespec *one, *two;
Expand Down Expand Up @@ -4620,9 +4621,9 @@ void diff_addremove(struct diff_options *options,
two = alloc_filespec(concatpath);

if (addremove != '+')
fill_filespec(one, sha1, mode);
fill_filespec(one, sha1, sha1_valid, mode);
if (addremove != '-') {
fill_filespec(two, sha1, mode);
fill_filespec(two, sha1, sha1_valid, mode);
two->dirty_submodule = dirty_submodule;
}

Expand All @@ -4635,6 +4636,7 @@ void diff_change(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
int old_sha1_valid, int new_sha1_valid,
const char *concatpath,
unsigned old_dirty_submodule, unsigned new_dirty_submodule)
{
Expand All @@ -4649,6 +4651,8 @@ void diff_change(struct diff_options *options,
const unsigned char *tmp_c;
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
tmp = old_sha1_valid; old_sha1_valid = new_sha1_valid;
new_sha1_valid = tmp;
tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
new_dirty_submodule = tmp;
}
Expand All @@ -4659,8 +4663,8 @@ void diff_change(struct diff_options *options,

one = alloc_filespec(concatpath);
two = alloc_filespec(concatpath);
fill_filespec(one, old_sha1, old_mode);
fill_filespec(two, new_sha1, new_mode);
fill_filespec(one, old_sha1, old_sha1_valid, old_mode);
fill_filespec(two, new_sha1, new_sha1_valid, new_mode);
one->dirty_submodule = old_dirty_submodule;
two->dirty_submodule = new_dirty_submodule;

Expand Down
5 changes: 5 additions & 0 deletions diff.h
Expand Up @@ -19,12 +19,14 @@ typedef void (*change_fn_t)(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
int old_sha1_valid, int new_sha1_valid,
const char *fullpath,
unsigned old_dirty_submodule, unsigned new_dirty_submodule);

typedef void (*add_remove_fn_t)(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
int sha1_valid,
const char *fullpath, unsigned dirty_submodule);

typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
Expand Down Expand Up @@ -209,12 +211,15 @@ extern void diff_addremove(struct diff_options *,
int addremove,
unsigned mode,
const unsigned char *sha1,
int sha1_valid,
const char *fullpath, unsigned dirty_submodule);

extern void diff_change(struct diff_options *,
unsigned mode1, unsigned mode2,
const unsigned char *sha1,
const unsigned char *sha2,
int sha1_valid,
int sha2_valid,
const char *fullpath,
unsigned dirty_submodule1, unsigned dirty_submodule2);

Expand Down
2 changes: 1 addition & 1 deletion diffcore-rename.c
Expand Up @@ -48,7 +48,7 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
memmove(rename_dst + first + 1, rename_dst + first,
(rename_dst_nr - first - 1) * sizeof(*rename_dst));
rename_dst[first].two = alloc_filespec(two->path);
fill_filespec(rename_dst[first].two, two->sha1, two->mode);
fill_filespec(rename_dst[first].two, two->sha1, two->sha1_valid, two->mode);
rename_dst[first].pair = NULL;
return &(rename_dst[first]);
}
Expand Down
2 changes: 1 addition & 1 deletion diffcore.h
Expand Up @@ -54,7 +54,7 @@ struct diff_filespec {
extern struct diff_filespec *alloc_filespec(const char *);
extern void free_filespec(struct diff_filespec *);
extern void fill_filespec(struct diff_filespec *, const unsigned char *,
unsigned short);
int, unsigned short);

extern int diff_populate_filespec(struct diff_filespec *, int);
extern void diff_free_filespec_data(struct diff_filespec *);
Expand Down
2 changes: 2 additions & 0 deletions revision.c
Expand Up @@ -332,6 +332,7 @@ static int tree_difference = REV_TREE_SAME;
static void file_add_remove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
int sha1_valid,
const char *fullpath, unsigned dirty_submodule)
{
int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
Expand All @@ -345,6 +346,7 @@ static void file_change(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
int old_sha1_valid, int new_sha1_valid,
const char *fullpath,
unsigned old_dirty_submodule, unsigned new_dirty_submodule)
{
Expand Down

0 comments on commit e545010

Please sign in to comment.