Merge branch 'jk/diff-blob' into maint
The result from "git diff" that compares two blobs, e.g. "git diff $commit1:$path $commit2:$path", used to be shown with the full object name as given on the command line, but it is more natural to use the $path in the output and use it to look up .gitattributes. * jk/diff-blob: diff: use blob path for blob/file diffs diff: use pending "path" if it is available diff: use the word "path" instead of "name" for blobs diff: pass whole pending entry in blobinfo handle_revision_arg: record paths for pending objects handle_revision_arg: record modes for "a..b" endpoints t4063: add tests of direct blob diffs get_sha1_with_context: dynamically allocate oc->path get_sha1_with_context: always initialize oc->symlink_path sha1_name: consistently refer to object_context as "oc" handle_revision_arg: add handle_dotdot() helper handle_revision_arg: hoist ".." check out of range parsing handle_revision_arg: stop using "dotdot" as a generic pointer handle_revision_arg: simplify commit reference lookups handle_revision_arg: reset "dotdot" consistently
This commit is contained in:
commit
7809876866
@ -61,7 +61,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
|
|||||||
if (unknown_type)
|
if (unknown_type)
|
||||||
flags |= LOOKUP_UNKNOWN_OBJECT;
|
flags |= LOOKUP_UNKNOWN_OBJECT;
|
||||||
|
|
||||||
if (get_sha1_with_context(obj_name, 0, oid.hash, &obj_context))
|
if (get_sha1_with_context(obj_name, GET_SHA1_RECORD_PATH,
|
||||||
|
oid.hash, &obj_context))
|
||||||
die("Not a valid object name %s", obj_name);
|
die("Not a valid object name %s", obj_name);
|
||||||
|
|
||||||
if (!path)
|
if (!path)
|
||||||
@ -166,6 +167,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
|
|||||||
|
|
||||||
write_or_die(1, buf, size);
|
write_or_die(1, buf, size);
|
||||||
free(buf);
|
free(buf);
|
||||||
|
free(obj_context.path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,23 +20,22 @@
|
|||||||
#define DIFF_NO_INDEX_EXPLICIT 1
|
#define DIFF_NO_INDEX_EXPLICIT 1
|
||||||
#define DIFF_NO_INDEX_IMPLICIT 2
|
#define DIFF_NO_INDEX_IMPLICIT 2
|
||||||
|
|
||||||
struct blobinfo {
|
|
||||||
struct object_id oid;
|
|
||||||
const char *name;
|
|
||||||
unsigned mode;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char builtin_diff_usage[] =
|
static const char builtin_diff_usage[] =
|
||||||
"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]";
|
"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]";
|
||||||
|
|
||||||
|
static const char *blob_path(struct object_array_entry *entry)
|
||||||
|
{
|
||||||
|
return entry->path ? entry->path : entry->name;
|
||||||
|
}
|
||||||
|
|
||||||
static void stuff_change(struct diff_options *opt,
|
static void stuff_change(struct diff_options *opt,
|
||||||
unsigned old_mode, unsigned new_mode,
|
unsigned old_mode, unsigned new_mode,
|
||||||
const struct object_id *old_oid,
|
const struct object_id *old_oid,
|
||||||
const struct object_id *new_oid,
|
const struct object_id *new_oid,
|
||||||
int old_oid_valid,
|
int old_oid_valid,
|
||||||
int new_oid_valid,
|
int new_oid_valid,
|
||||||
const char *old_name,
|
const char *old_path,
|
||||||
const char *new_name)
|
const char *new_path)
|
||||||
{
|
{
|
||||||
struct diff_filespec *one, *two;
|
struct diff_filespec *one, *two;
|
||||||
|
|
||||||
@ -47,16 +46,16 @@ static void stuff_change(struct diff_options *opt,
|
|||||||
if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
|
if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
|
||||||
SWAP(old_mode, new_mode);
|
SWAP(old_mode, new_mode);
|
||||||
SWAP(old_oid, new_oid);
|
SWAP(old_oid, new_oid);
|
||||||
SWAP(old_name, new_name);
|
SWAP(old_path, new_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opt->prefix &&
|
if (opt->prefix &&
|
||||||
(strncmp(old_name, opt->prefix, opt->prefix_length) ||
|
(strncmp(old_path, opt->prefix, opt->prefix_length) ||
|
||||||
strncmp(new_name, opt->prefix, opt->prefix_length)))
|
strncmp(new_path, opt->prefix, opt->prefix_length)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
one = alloc_filespec(old_name);
|
one = alloc_filespec(old_path);
|
||||||
two = alloc_filespec(new_name);
|
two = alloc_filespec(new_path);
|
||||||
fill_filespec(one, old_oid->hash, old_oid_valid, old_mode);
|
fill_filespec(one, old_oid->hash, old_oid_valid, old_mode);
|
||||||
fill_filespec(two, new_oid->hash, new_oid_valid, new_mode);
|
fill_filespec(two, new_oid->hash, new_oid_valid, new_mode);
|
||||||
|
|
||||||
@ -65,7 +64,7 @@ static void stuff_change(struct diff_options *opt,
|
|||||||
|
|
||||||
static int builtin_diff_b_f(struct rev_info *revs,
|
static int builtin_diff_b_f(struct rev_info *revs,
|
||||||
int argc, const char **argv,
|
int argc, const char **argv,
|
||||||
struct blobinfo *blob)
|
struct object_array_entry **blob)
|
||||||
{
|
{
|
||||||
/* Blob vs file in the working tree*/
|
/* Blob vs file in the working tree*/
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@ -84,14 +83,15 @@ static int builtin_diff_b_f(struct rev_info *revs,
|
|||||||
|
|
||||||
diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
|
diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
|
||||||
|
|
||||||
if (blob[0].mode == S_IFINVALID)
|
if (blob[0]->mode == S_IFINVALID)
|
||||||
blob[0].mode = canon_mode(st.st_mode);
|
blob[0]->mode = canon_mode(st.st_mode);
|
||||||
|
|
||||||
stuff_change(&revs->diffopt,
|
stuff_change(&revs->diffopt,
|
||||||
blob[0].mode, canon_mode(st.st_mode),
|
blob[0]->mode, canon_mode(st.st_mode),
|
||||||
&blob[0].oid, &null_oid,
|
&blob[0]->item->oid, &null_oid,
|
||||||
1, 0,
|
1, 0,
|
||||||
path, path);
|
blob[0]->path ? blob[0]->path : path,
|
||||||
|
path);
|
||||||
diffcore_std(&revs->diffopt);
|
diffcore_std(&revs->diffopt);
|
||||||
diff_flush(&revs->diffopt);
|
diff_flush(&revs->diffopt);
|
||||||
return 0;
|
return 0;
|
||||||
@ -99,24 +99,24 @@ static int builtin_diff_b_f(struct rev_info *revs,
|
|||||||
|
|
||||||
static int builtin_diff_blobs(struct rev_info *revs,
|
static int builtin_diff_blobs(struct rev_info *revs,
|
||||||
int argc, const char **argv,
|
int argc, const char **argv,
|
||||||
struct blobinfo *blob)
|
struct object_array_entry **blob)
|
||||||
{
|
{
|
||||||
unsigned mode = canon_mode(S_IFREG | 0644);
|
unsigned mode = canon_mode(S_IFREG | 0644);
|
||||||
|
|
||||||
if (argc > 1)
|
if (argc > 1)
|
||||||
usage(builtin_diff_usage);
|
usage(builtin_diff_usage);
|
||||||
|
|
||||||
if (blob[0].mode == S_IFINVALID)
|
if (blob[0]->mode == S_IFINVALID)
|
||||||
blob[0].mode = mode;
|
blob[0]->mode = mode;
|
||||||
|
|
||||||
if (blob[1].mode == S_IFINVALID)
|
if (blob[1]->mode == S_IFINVALID)
|
||||||
blob[1].mode = mode;
|
blob[1]->mode = mode;
|
||||||
|
|
||||||
stuff_change(&revs->diffopt,
|
stuff_change(&revs->diffopt,
|
||||||
blob[0].mode, blob[1].mode,
|
blob[0]->mode, blob[1]->mode,
|
||||||
&blob[0].oid, &blob[1].oid,
|
&blob[0]->item->oid, &blob[1]->item->oid,
|
||||||
1, 1,
|
1, 1,
|
||||||
blob[0].name, blob[1].name);
|
blob_path(blob[0]), blob_path(blob[1]));
|
||||||
diffcore_std(&revs->diffopt);
|
diffcore_std(&revs->diffopt);
|
||||||
diff_flush(&revs->diffopt);
|
diff_flush(&revs->diffopt);
|
||||||
return 0;
|
return 0;
|
||||||
@ -259,7 +259,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
|
|||||||
struct rev_info rev;
|
struct rev_info rev;
|
||||||
struct object_array ent = OBJECT_ARRAY_INIT;
|
struct object_array ent = OBJECT_ARRAY_INIT;
|
||||||
int blobs = 0, paths = 0;
|
int blobs = 0, paths = 0;
|
||||||
struct blobinfo blob[2];
|
struct object_array_entry *blob[2];
|
||||||
int nongit = 0, no_index = 0;
|
int nongit = 0, no_index = 0;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
@ -408,9 +408,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
|
|||||||
} else if (obj->type == OBJ_BLOB) {
|
} else if (obj->type == OBJ_BLOB) {
|
||||||
if (2 <= blobs)
|
if (2 <= blobs)
|
||||||
die(_("more than two blobs given: '%s'"), name);
|
die(_("more than two blobs given: '%s'"), name);
|
||||||
hashcpy(blob[blobs].oid.hash, obj->oid.hash);
|
blob[blobs] = entry;
|
||||||
blob[blobs].name = name;
|
|
||||||
blob[blobs].mode = entry->mode;
|
|
||||||
blobs++;
|
blobs++;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -1190,7 +1190,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_sha1_with_context(arg, 0, oid.hash, &oc)) {
|
if (get_sha1_with_context(arg, GET_SHA1_RECORD_PATH,
|
||||||
|
oid.hash, &oc)) {
|
||||||
if (seen_dashdash)
|
if (seen_dashdash)
|
||||||
die(_("unable to resolve revision: %s"), arg);
|
die(_("unable to resolve revision: %s"), arg);
|
||||||
break;
|
break;
|
||||||
@ -1200,6 +1201,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
|
|||||||
if (!seen_dashdash)
|
if (!seen_dashdash)
|
||||||
verify_non_filename(prefix, arg);
|
verify_non_filename(prefix, arg);
|
||||||
add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
|
add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
|
||||||
|
free(oc.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -483,16 +483,20 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c
|
|||||||
!DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV))
|
!DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV))
|
||||||
return stream_blob_to_fd(1, oid, NULL, 0);
|
return stream_blob_to_fd(1, oid, NULL, 0);
|
||||||
|
|
||||||
if (get_sha1_with_context(obj_name, 0, oidc.hash, &obj_context))
|
if (get_sha1_with_context(obj_name, GET_SHA1_RECORD_PATH,
|
||||||
|
oidc.hash, &obj_context))
|
||||||
die(_("Not a valid object name %s"), obj_name);
|
die(_("Not a valid object name %s"), obj_name);
|
||||||
if (!obj_context.path[0] ||
|
if (!obj_context.path ||
|
||||||
!textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size))
|
!textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) {
|
||||||
|
free(obj_context.path);
|
||||||
return stream_blob_to_fd(1, oid, NULL, 0);
|
return stream_blob_to_fd(1, oid, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (!buf)
|
if (!buf)
|
||||||
die(_("git show %s: bad file"), obj_name);
|
die(_("git show %s: bad file"), obj_name);
|
||||||
|
|
||||||
write_or_die(1, buf, size);
|
write_or_die(1, buf, size);
|
||||||
|
free(obj_context.path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
cache.h
10
cache.h
@ -1333,13 +1333,18 @@ static inline int hex2chr(const char *s)
|
|||||||
|
|
||||||
struct object_context {
|
struct object_context {
|
||||||
unsigned char tree[20];
|
unsigned char tree[20];
|
||||||
char path[PATH_MAX];
|
|
||||||
unsigned mode;
|
unsigned mode;
|
||||||
/*
|
/*
|
||||||
* symlink_path is only used by get_tree_entry_follow_symlinks,
|
* symlink_path is only used by get_tree_entry_follow_symlinks,
|
||||||
* and only for symlinks that point outside the repository.
|
* and only for symlinks that point outside the repository.
|
||||||
*/
|
*/
|
||||||
struct strbuf symlink_path;
|
struct strbuf symlink_path;
|
||||||
|
/*
|
||||||
|
* If GET_SHA1_RECORD_PATH is set, this will record path (if any)
|
||||||
|
* found when resolving the name. The caller is responsible for
|
||||||
|
* releasing the memory.
|
||||||
|
*/
|
||||||
|
char *path;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define GET_SHA1_QUIETLY 01
|
#define GET_SHA1_QUIETLY 01
|
||||||
@ -1349,6 +1354,7 @@ struct object_context {
|
|||||||
#define GET_SHA1_TREEISH 020
|
#define GET_SHA1_TREEISH 020
|
||||||
#define GET_SHA1_BLOB 040
|
#define GET_SHA1_BLOB 040
|
||||||
#define GET_SHA1_FOLLOW_SYMLINKS 0100
|
#define GET_SHA1_FOLLOW_SYMLINKS 0100
|
||||||
|
#define GET_SHA1_RECORD_PATH 0200
|
||||||
#define GET_SHA1_ONLY_TO_DIE 04000
|
#define GET_SHA1_ONLY_TO_DIE 04000
|
||||||
|
|
||||||
#define GET_SHA1_DISAMBIGUATORS \
|
#define GET_SHA1_DISAMBIGUATORS \
|
||||||
@ -1363,7 +1369,7 @@ extern int get_sha1_tree(const char *str, unsigned char *sha1);
|
|||||||
extern int get_sha1_treeish(const char *str, unsigned char *sha1);
|
extern int get_sha1_treeish(const char *str, unsigned char *sha1);
|
||||||
extern int get_sha1_blob(const char *str, unsigned char *sha1);
|
extern int get_sha1_blob(const char *str, unsigned char *sha1);
|
||||||
extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
|
extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
|
||||||
extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc);
|
extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *oc);
|
||||||
|
|
||||||
extern int get_oid(const char *str, struct object_id *oid);
|
extern int get_oid(const char *str, struct object_id *oid);
|
||||||
|
|
||||||
|
243
revision.c
243
revision.c
@ -1429,134 +1429,168 @@ static void prepare_show_merge(struct rev_info *revs)
|
|||||||
revs->limited = 1;
|
revs->limited = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int dotdot_missing(const char *arg, char *dotdot,
|
||||||
|
struct rev_info *revs, int symmetric)
|
||||||
|
{
|
||||||
|
if (revs->ignore_missing)
|
||||||
|
return 0;
|
||||||
|
/* de-munge so we report the full argument */
|
||||||
|
*dotdot = '.';
|
||||||
|
die(symmetric
|
||||||
|
? "Invalid symmetric difference expression %s"
|
||||||
|
: "Invalid revision range %s", arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_dotdot_1(const char *arg, char *dotdot,
|
||||||
|
struct rev_info *revs, int flags,
|
||||||
|
int cant_be_filename,
|
||||||
|
struct object_context *a_oc,
|
||||||
|
struct object_context *b_oc)
|
||||||
|
{
|
||||||
|
const char *a_name, *b_name;
|
||||||
|
struct object_id a_oid, b_oid;
|
||||||
|
struct object *a_obj, *b_obj;
|
||||||
|
unsigned int a_flags, b_flags;
|
||||||
|
int symmetric = 0;
|
||||||
|
unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
|
||||||
|
unsigned int oc_flags = GET_SHA1_COMMITTISH | GET_SHA1_RECORD_PATH;
|
||||||
|
|
||||||
|
a_name = arg;
|
||||||
|
if (!*a_name)
|
||||||
|
a_name = "HEAD";
|
||||||
|
|
||||||
|
b_name = dotdot + 2;
|
||||||
|
if (*b_name == '.') {
|
||||||
|
symmetric = 1;
|
||||||
|
b_name++;
|
||||||
|
}
|
||||||
|
if (!*b_name)
|
||||||
|
b_name = "HEAD";
|
||||||
|
|
||||||
|
if (get_sha1_with_context(a_name, oc_flags, a_oid.hash, a_oc) ||
|
||||||
|
get_sha1_with_context(b_name, oc_flags, b_oid.hash, b_oc))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!cant_be_filename) {
|
||||||
|
*dotdot = '.';
|
||||||
|
verify_non_filename(revs->prefix, arg);
|
||||||
|
*dotdot = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
a_obj = parse_object(a_oid.hash);
|
||||||
|
b_obj = parse_object(b_oid.hash);
|
||||||
|
if (!a_obj || !b_obj)
|
||||||
|
return dotdot_missing(arg, dotdot, revs, symmetric);
|
||||||
|
|
||||||
|
if (!symmetric) {
|
||||||
|
/* just A..B */
|
||||||
|
b_flags = flags;
|
||||||
|
a_flags = flags_exclude;
|
||||||
|
} else {
|
||||||
|
/* A...B -- find merge bases between the two */
|
||||||
|
struct commit *a, *b;
|
||||||
|
struct commit_list *exclude;
|
||||||
|
|
||||||
|
a = lookup_commit_reference(a_obj->oid.hash);
|
||||||
|
b = lookup_commit_reference(b_obj->oid.hash);
|
||||||
|
if (!a || !b)
|
||||||
|
return dotdot_missing(arg, dotdot, revs, symmetric);
|
||||||
|
|
||||||
|
exclude = get_merge_bases(a, b);
|
||||||
|
add_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE,
|
||||||
|
flags_exclude);
|
||||||
|
add_pending_commit_list(revs, exclude, flags_exclude);
|
||||||
|
free_commit_list(exclude);
|
||||||
|
|
||||||
|
b_flags = flags;
|
||||||
|
a_flags = flags | SYMMETRIC_LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
a_obj->flags |= a_flags;
|
||||||
|
b_obj->flags |= b_flags;
|
||||||
|
add_rev_cmdline(revs, a_obj, a_name, REV_CMD_LEFT, a_flags);
|
||||||
|
add_rev_cmdline(revs, b_obj, b_name, REV_CMD_RIGHT, b_flags);
|
||||||
|
add_pending_object_with_path(revs, a_obj, a_name, a_oc->mode, a_oc->path);
|
||||||
|
add_pending_object_with_path(revs, b_obj, b_name, b_oc->mode, b_oc->path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_dotdot(const char *arg,
|
||||||
|
struct rev_info *revs, int flags,
|
||||||
|
int cant_be_filename)
|
||||||
|
{
|
||||||
|
struct object_context a_oc, b_oc;
|
||||||
|
char *dotdot = strstr(arg, "..");
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!dotdot)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
memset(&a_oc, 0, sizeof(a_oc));
|
||||||
|
memset(&b_oc, 0, sizeof(b_oc));
|
||||||
|
|
||||||
|
*dotdot = '\0';
|
||||||
|
ret = handle_dotdot_1(arg, dotdot, revs, flags, cant_be_filename,
|
||||||
|
&a_oc, &b_oc);
|
||||||
|
*dotdot = '.';
|
||||||
|
|
||||||
|
free(a_oc.path);
|
||||||
|
free(b_oc.path);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
|
int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
|
||||||
{
|
{
|
||||||
struct object_context oc;
|
struct object_context oc;
|
||||||
char *dotdot;
|
char *mark;
|
||||||
struct object *object;
|
struct object *object;
|
||||||
unsigned char sha1[20];
|
unsigned char sha1[20];
|
||||||
int local_flags;
|
int local_flags;
|
||||||
const char *arg = arg_;
|
const char *arg = arg_;
|
||||||
int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
|
int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
|
||||||
unsigned get_sha1_flags = 0;
|
unsigned get_sha1_flags = GET_SHA1_RECORD_PATH;
|
||||||
|
|
||||||
flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
|
flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
|
||||||
|
|
||||||
dotdot = strstr(arg, "..");
|
if (!cant_be_filename && !strcmp(arg, "..")) {
|
||||||
if (dotdot) {
|
/*
|
||||||
unsigned char from_sha1[20];
|
* Just ".."? That is not a range but the
|
||||||
const char *next = dotdot + 2;
|
* pathspec for the parent directory.
|
||||||
const char *this = arg;
|
*/
|
||||||
int symmetric = *next == '.';
|
return -1;
|
||||||
unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
|
|
||||||
static const char head_by_default[] = "HEAD";
|
|
||||||
unsigned int a_flags;
|
|
||||||
|
|
||||||
*dotdot = 0;
|
|
||||||
next += symmetric;
|
|
||||||
|
|
||||||
if (!*next)
|
|
||||||
next = head_by_default;
|
|
||||||
if (dotdot == arg)
|
|
||||||
this = head_by_default;
|
|
||||||
if (this == head_by_default && next == head_by_default &&
|
|
||||||
!symmetric) {
|
|
||||||
/*
|
|
||||||
* Just ".."? That is not a range but the
|
|
||||||
* pathspec for the parent directory.
|
|
||||||
*/
|
|
||||||
if (!cant_be_filename) {
|
|
||||||
*dotdot = '.';
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!get_sha1_committish(this, from_sha1) &&
|
|
||||||
!get_sha1_committish(next, sha1)) {
|
|
||||||
struct object *a_obj, *b_obj;
|
|
||||||
|
|
||||||
if (!cant_be_filename) {
|
|
||||||
*dotdot = '.';
|
|
||||||
verify_non_filename(revs->prefix, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
a_obj = parse_object(from_sha1);
|
|
||||||
b_obj = parse_object(sha1);
|
|
||||||
if (!a_obj || !b_obj) {
|
|
||||||
missing:
|
|
||||||
if (revs->ignore_missing)
|
|
||||||
return 0;
|
|
||||||
die(symmetric
|
|
||||||
? "Invalid symmetric difference expression %s"
|
|
||||||
: "Invalid revision range %s", arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!symmetric) {
|
|
||||||
/* just A..B */
|
|
||||||
a_flags = flags_exclude;
|
|
||||||
} else {
|
|
||||||
/* A...B -- find merge bases between the two */
|
|
||||||
struct commit *a, *b;
|
|
||||||
struct commit_list *exclude;
|
|
||||||
|
|
||||||
a = (a_obj->type == OBJ_COMMIT
|
|
||||||
? (struct commit *)a_obj
|
|
||||||
: lookup_commit_reference(a_obj->oid.hash));
|
|
||||||
b = (b_obj->type == OBJ_COMMIT
|
|
||||||
? (struct commit *)b_obj
|
|
||||||
: lookup_commit_reference(b_obj->oid.hash));
|
|
||||||
if (!a || !b)
|
|
||||||
goto missing;
|
|
||||||
exclude = get_merge_bases(a, b);
|
|
||||||
add_rev_cmdline_list(revs, exclude,
|
|
||||||
REV_CMD_MERGE_BASE,
|
|
||||||
flags_exclude);
|
|
||||||
add_pending_commit_list(revs, exclude,
|
|
||||||
flags_exclude);
|
|
||||||
free_commit_list(exclude);
|
|
||||||
|
|
||||||
a_flags = flags | SYMMETRIC_LEFT;
|
|
||||||
}
|
|
||||||
|
|
||||||
a_obj->flags |= a_flags;
|
|
||||||
b_obj->flags |= flags;
|
|
||||||
add_rev_cmdline(revs, a_obj, this,
|
|
||||||
REV_CMD_LEFT, a_flags);
|
|
||||||
add_rev_cmdline(revs, b_obj, next,
|
|
||||||
REV_CMD_RIGHT, flags);
|
|
||||||
add_pending_object(revs, a_obj, this);
|
|
||||||
add_pending_object(revs, b_obj, next);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*dotdot = '.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dotdot = strstr(arg, "^@");
|
if (!handle_dotdot(arg, revs, flags, revarg_opt))
|
||||||
if (dotdot && !dotdot[2]) {
|
return 0;
|
||||||
*dotdot = 0;
|
|
||||||
|
mark = strstr(arg, "^@");
|
||||||
|
if (mark && !mark[2]) {
|
||||||
|
*mark = 0;
|
||||||
if (add_parents_only(revs, arg, flags, 0))
|
if (add_parents_only(revs, arg, flags, 0))
|
||||||
return 0;
|
return 0;
|
||||||
*dotdot = '^';
|
*mark = '^';
|
||||||
}
|
}
|
||||||
dotdot = strstr(arg, "^!");
|
mark = strstr(arg, "^!");
|
||||||
if (dotdot && !dotdot[2]) {
|
if (mark && !mark[2]) {
|
||||||
*dotdot = 0;
|
*mark = 0;
|
||||||
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0))
|
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0))
|
||||||
*dotdot = '^';
|
*mark = '^';
|
||||||
}
|
}
|
||||||
dotdot = strstr(arg, "^-");
|
mark = strstr(arg, "^-");
|
||||||
if (dotdot) {
|
if (mark) {
|
||||||
int exclude_parent = 1;
|
int exclude_parent = 1;
|
||||||
|
|
||||||
if (dotdot[2]) {
|
if (mark[2]) {
|
||||||
char *end;
|
char *end;
|
||||||
exclude_parent = strtoul(dotdot + 2, &end, 10);
|
exclude_parent = strtoul(mark + 2, &end, 10);
|
||||||
if (*end != '\0' || !exclude_parent)
|
if (*end != '\0' || !exclude_parent)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
*dotdot = 0;
|
*mark = 0;
|
||||||
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent))
|
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent))
|
||||||
*dotdot = '^';
|
*mark = '^';
|
||||||
}
|
}
|
||||||
|
|
||||||
local_flags = 0;
|
local_flags = 0;
|
||||||
@ -1566,7 +1600,7 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (revarg_opt & REVARG_COMMITTISH)
|
if (revarg_opt & REVARG_COMMITTISH)
|
||||||
get_sha1_flags = GET_SHA1_COMMITTISH;
|
get_sha1_flags |= GET_SHA1_COMMITTISH;
|
||||||
|
|
||||||
if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc))
|
if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc))
|
||||||
return revs->ignore_missing ? 0 : -1;
|
return revs->ignore_missing ? 0 : -1;
|
||||||
@ -1574,7 +1608,8 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
|
|||||||
verify_non_filename(revs->prefix, arg);
|
verify_non_filename(revs->prefix, arg);
|
||||||
object = get_reference(revs, arg, sha1, flags ^ local_flags);
|
object = get_reference(revs, arg, sha1, flags ^ local_flags);
|
||||||
add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
|
add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
|
||||||
add_pending_object_with_mode(revs, object, arg, oc.mode);
|
add_pending_object_with_path(revs, object, arg, oc.mode, oc.path);
|
||||||
|
free(oc.path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
sha1_name.c
11
sha1_name.c
@ -1511,6 +1511,7 @@ static int get_sha1_with_context_1(const char *name,
|
|||||||
|
|
||||||
memset(oc, 0, sizeof(*oc));
|
memset(oc, 0, sizeof(*oc));
|
||||||
oc->mode = S_IFINVALID;
|
oc->mode = S_IFINVALID;
|
||||||
|
strbuf_init(&oc->symlink_path, 0);
|
||||||
ret = get_sha1_1(name, namelen, sha1, flags);
|
ret = get_sha1_1(name, namelen, sha1, flags);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
return ret;
|
return ret;
|
||||||
@ -1549,7 +1550,8 @@ static int get_sha1_with_context_1(const char *name,
|
|||||||
namelen = strlen(cp);
|
namelen = strlen(cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
strlcpy(oc->path, cp, sizeof(oc->path));
|
if (flags & GET_SHA1_RECORD_PATH)
|
||||||
|
oc->path = xstrdup(cp);
|
||||||
|
|
||||||
if (!active_cache)
|
if (!active_cache)
|
||||||
read_cache();
|
read_cache();
|
||||||
@ -1612,7 +1614,8 @@ static int get_sha1_with_context_1(const char *name,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hashcpy(oc->tree, tree_sha1);
|
hashcpy(oc->tree, tree_sha1);
|
||||||
strlcpy(oc->path, filename, sizeof(oc->path));
|
if (flags & GET_SHA1_RECORD_PATH)
|
||||||
|
oc->path = xstrdup(filename);
|
||||||
|
|
||||||
free(new_filename);
|
free(new_filename);
|
||||||
return ret;
|
return ret;
|
||||||
@ -1638,9 +1641,9 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix)
|
|||||||
get_sha1_with_context_1(name, GET_SHA1_ONLY_TO_DIE, prefix, sha1, &oc);
|
get_sha1_with_context_1(name, GET_SHA1_ONLY_TO_DIE, prefix, sha1, &oc);
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
|
int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *oc)
|
||||||
{
|
{
|
||||||
if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
|
if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
|
||||||
die("BUG: incompatible flags for get_sha1_with_context");
|
die("BUG: incompatible flags for get_sha1_with_context");
|
||||||
return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
|
return get_sha1_with_context_1(str, flags, NULL, sha1, oc);
|
||||||
}
|
}
|
||||||
|
96
t/t4063-diff-blobs.sh
Executable file
96
t/t4063-diff-blobs.sh
Executable file
@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='test direct comparison of blobs via git-diff'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
run_diff () {
|
||||||
|
# use full-index to make it easy to match the index line
|
||||||
|
git diff --full-index "$@" >diff
|
||||||
|
}
|
||||||
|
|
||||||
|
check_index () {
|
||||||
|
grep "^index $1\\.\\.$2" diff
|
||||||
|
}
|
||||||
|
|
||||||
|
check_mode () {
|
||||||
|
grep "^old mode $1" diff &&
|
||||||
|
grep "^new mode $2" diff
|
||||||
|
}
|
||||||
|
|
||||||
|
check_paths () {
|
||||||
|
grep "^diff --git a/$1 b/$2" diff
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'create some blobs' '
|
||||||
|
echo one >one &&
|
||||||
|
echo two >two &&
|
||||||
|
chmod +x two &&
|
||||||
|
git add . &&
|
||||||
|
|
||||||
|
# cover systems where modes are ignored
|
||||||
|
git update-index --chmod=+x two &&
|
||||||
|
|
||||||
|
git commit -m base &&
|
||||||
|
|
||||||
|
sha1_one=$(git rev-parse HEAD:one) &&
|
||||||
|
sha1_two=$(git rev-parse HEAD:two)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'diff by sha1' '
|
||||||
|
run_diff $sha1_one $sha1_two
|
||||||
|
'
|
||||||
|
test_expect_success 'index of sha1 diff' '
|
||||||
|
check_index $sha1_one $sha1_two
|
||||||
|
'
|
||||||
|
test_expect_success 'sha1 diff uses arguments as paths' '
|
||||||
|
check_paths $sha1_one $sha1_two
|
||||||
|
'
|
||||||
|
test_expect_success 'sha1 diff has no mode change' '
|
||||||
|
! grep mode diff
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'diff by tree:path (run)' '
|
||||||
|
run_diff HEAD:one HEAD:two
|
||||||
|
'
|
||||||
|
test_expect_success 'index of tree:path diff' '
|
||||||
|
check_index $sha1_one $sha1_two
|
||||||
|
'
|
||||||
|
test_expect_success 'tree:path diff uses filenames as paths' '
|
||||||
|
check_paths one two
|
||||||
|
'
|
||||||
|
test_expect_success 'tree:path diff shows mode change' '
|
||||||
|
check_mode 100644 100755
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'diff by ranged tree:path' '
|
||||||
|
run_diff HEAD:one..HEAD:two
|
||||||
|
'
|
||||||
|
test_expect_success 'index of ranged tree:path diff' '
|
||||||
|
check_index $sha1_one $sha1_two
|
||||||
|
'
|
||||||
|
test_expect_success 'ranged tree:path diff uses filenames as paths' '
|
||||||
|
check_paths one two
|
||||||
|
'
|
||||||
|
test_expect_success 'ranged tree:path diff shows mode change' '
|
||||||
|
check_mode 100644 100755
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'diff blob against file' '
|
||||||
|
run_diff HEAD:one two
|
||||||
|
'
|
||||||
|
test_expect_success 'index of blob-file diff' '
|
||||||
|
check_index $sha1_one $sha1_two
|
||||||
|
'
|
||||||
|
test_expect_success 'blob-file diff uses filename as paths' '
|
||||||
|
check_paths one two
|
||||||
|
'
|
||||||
|
test_expect_success FILEMODE 'blob-file diff shows mode change' '
|
||||||
|
check_mode 100644 100755
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'blob-file diff prefers filename to sha1' '
|
||||||
|
run_diff $sha1_one two &&
|
||||||
|
check_paths two two
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
@ -1392,4 +1392,13 @@ test_expect_success 'log --source paints tag names' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'log --source paints symmetric ranges' '
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
09e12a9 source-b three
|
||||||
|
8e393e1 source-a two
|
||||||
|
EOF
|
||||||
|
git log --oneline --source source-a...source-b >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
@ -589,7 +589,6 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
init_tree_desc(&t, NULL, 0UL);
|
init_tree_desc(&t, NULL, 0UL);
|
||||||
strbuf_init(result_path, 0);
|
|
||||||
strbuf_addstr(&namebuf, name);
|
strbuf_addstr(&namebuf, name);
|
||||||
hashcpy(current_tree_sha1, tree_sha1);
|
hashcpy(current_tree_sha1, tree_sha1);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user