Merge branch 'jk/diff-blob'

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:
Junio C Hamano
2017-06-02 15:06:05 +09:00
10 changed files with 301 additions and 147 deletions

View File

@ -1429,134 +1429,168 @@ static void prepare_show_merge(struct rev_info *revs)
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);
b_obj = parse_object(&b_oid);
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);
b = lookup_commit_reference(&b_obj->oid);
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)
{
struct object_context oc;
char *dotdot;
char *mark;
struct object *object;
struct object_id oid;
int local_flags;
const char *arg = arg_;
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;
dotdot = strstr(arg, "..");
if (dotdot) {
struct object_id from_oid;
const char *next = dotdot + 2;
const char *this = arg;
int symmetric = *next == '.';
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_oid.hash) &&
!get_sha1_committish(next, oid.hash)) {
struct object *a_obj, *b_obj;
if (!cant_be_filename) {
*dotdot = '.';
verify_non_filename(revs->prefix, arg);
}
a_obj = parse_object(&from_oid);
b_obj = parse_object(&oid);
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));
b = (b_obj->type == OBJ_COMMIT
? (struct commit *)b_obj
: lookup_commit_reference(&b_obj->oid));
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 = '.';
if (!cant_be_filename && !strcmp(arg, "..")) {
/*
* Just ".."? That is not a range but the
* pathspec for the parent directory.
*/
return -1;
}
dotdot = strstr(arg, "^@");
if (dotdot && !dotdot[2]) {
*dotdot = 0;
if (!handle_dotdot(arg, revs, flags, revarg_opt))
return 0;
mark = strstr(arg, "^@");
if (mark && !mark[2]) {
*mark = 0;
if (add_parents_only(revs, arg, flags, 0))
return 0;
*dotdot = '^';
*mark = '^';
}
dotdot = strstr(arg, "^!");
if (dotdot && !dotdot[2]) {
*dotdot = 0;
mark = strstr(arg, "^!");
if (mark && !mark[2]) {
*mark = 0;
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0))
*dotdot = '^';
*mark = '^';
}
dotdot = strstr(arg, "^-");
if (dotdot) {
mark = strstr(arg, "^-");
if (mark) {
int exclude_parent = 1;
if (dotdot[2]) {
if (mark[2]) {
char *end;
exclude_parent = strtoul(dotdot + 2, &end, 10);
exclude_parent = strtoul(mark + 2, &end, 10);
if (*end != '\0' || !exclude_parent)
return -1;
}
*dotdot = 0;
*mark = 0;
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent))
*dotdot = '^';
*mark = '^';
}
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)
get_sha1_flags = GET_SHA1_COMMITTISH;
get_sha1_flags |= GET_SHA1_COMMITTISH;
if (get_sha1_with_context(arg, get_sha1_flags, oid.hash, &oc))
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);
object = get_reference(revs, arg, &oid, 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;
}