mirror of
https://github.com/git/git.git
synced 2026-08-09 09:34:14 +00:00
The provider chain so far holds the diff-hunks store in front of the terminal builtin computation. Open it to external processes: a pair on a path whose driver configures diff.<driver>.process is answered by a long-running process speaking a pkt-line protocol (following the filter process protocol), registered at the head of the chain and consulted before the store and before any blob is loaded. The protocol starts with the smallest request that can carry an answer: object names alone. A request is the pathname and the pair's old-oid/new-oid, with no content. The process answers with hunk lines, with a zero-hunk success that asserts the blobs equivalent (trailing newlines included), or with status=need-content, on which the pair falls through to the builtin answer. This serves the two shapes that need no content pushed to them: a cache keyed on the blob pair, and a process that fetches the blobs itself (for example over "git cat-file --batch"). A pair whose side is not a stored blob carries a NULL id; the provider sends no request and passes it. Because Git holds no content for the exchange, the answer is used as sent: hunks are validated for order, overlap, lockstep alignment, and magnitude, then replayed without the normalization xdiff applies to diffs it computes itself. The magnitude bound is the blobs' sizes, read from the object database without loading content: a blob of N bytes holds at most N lines. Because the process's answer is authoritative, it outranks the store, and its head-of-chain position says so. A pair the process answers never reaches the store and is never recorded, so nothing it produces enters the store, which holds the builtin answer only. A request it does not answer, whether need-content, a missing capability, or a missing id, passes down the chain to the builtin answer, which is what the store serves, so the store may serve such a pair and a warming run may record it. Entries recorded before a process was configured are not purged; a pair the process answers ignores them, and "git diff-hunks clear" discards them. The provider gates itself per request. The driver is looked up by the old-side path, so a renamed file resolves to the same driver, and by the repository-relative path, so a diff.relative run from a subdirectory names the pair the same way. Options the process is never told about select no process: the whitespace-ignoring options, -I, --anchored, and an algorithm forced by option or configuration (blame routes its algorithm through xdl_opts, so --histogram is covered). The request gains its last field, the path; the consumers change only by filling it, and neither names the process. The provider's state is its repository's pool of running processes, keyed by the configured command, so drivers sharing a command share a process, a submodule speaks to its own, and releasing the provider (from repo_clear()) stops them. The pool owns a copy of each command string, so an entry outlives a config re-read. A command that fails stays as an entry that is not retried: its request and every later one pass, so the store may serve the path for the rest of the command. A protocol error in a response never kills the command. The response is read through a packet reader gentle about framing, so an error takes one path: a single warning, the process stopped and marked failed, and the builtin diff for the rest of the command. That covers garbage bytes, a truncated response, an empty packet, a bare status, and an unrecognized status. Semantically invalid coordinates cost only their pair: the response is drained, the pair is computed, and the process stays alive. A path the protocol cannot carry (an embedded newline, or one too long for a packet) falls back per path rather than costing the command its process. The handshake keeps one fatal check: a process that announces a capability Git did not request aborts the command, as the long-running filter protocol does. Consulting is allowed per command, following the allow_textconv precedent. "git diff", "git log" and "git show", and "git blame" set allow_diff_process; the plumbing diff commands and the interactive-patch machinery never set it, so scripted and staging output stays builtin. The options adjust the flag: - --no-ext-diff clears it and --ext-diff sets it; - --diff-process and --no-diff-process set and clear it alone, leaving external diff drivers as they were; - format-patch clears it unconditionally, so a generated patch applies for recipients without the process; - range-diff passes --no-ext-diff to the "git log" it compares. git blame and the summary formats consult the process. For blame, a pair reported equivalent emits no hunks, so the whole commit passes to its parent. In the stat formats such a pair sums to a zero-count entry, which the "nothing changed" rule omits, as under -w. The subprocess is long-running: one startup cost across a traversal, one round-trip per consulted pair. Answers travel in struct xdl_hunk, new in xdiff-interface.h, holding xdiff's 1-based coordinates; nothing feeds them back to xdiff, since only coordinate consumers consult. A content-carrying request is the natural extension: it would serve sides that are not stored blobs and processes that want content pushed to them, and bring patch output and log -L's range tracking to the same answer. As it stands, a process's answers show in blame and the summary formats while patch output stays builtin. t4080 exercises the protocol, the per-command gate, and the error paths: - each adversarial response shape warns and falls back to builtin, the request log proving which failures disable the process and which keep it alive (a malformed hunk line, coordinates past the blob size, a count overflowing strtol(), overlapping or misaligned hunks, an unrecognized status, a bare status, an empty packet, a mid-response crash, and raw garbage); - a capability-less process and status=abort degrade without noise, and a failed start warns once and returns the path to the store; - a trailing token on a hunk line is ignored, pinning field appendability; - positive consults for git diff, git show, and diff-tree under --ext-diff and --diff-process; textconv output and gitlink sides are never identified; a diff.relative run consults by the repo-relative path; - the equivalence answer is pinned from both consumers, and a warming run past a deferring process records the pair for a later read. Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Michael Montalbo <mmontalbo@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
667 lines
18 KiB
C
667 lines
18 KiB
C
/*
|
|
* Builtin "git diff"
|
|
*
|
|
* Copyright (c) 2006 Junio C Hamano
|
|
*/
|
|
|
|
#define USE_THE_REPOSITORY_VARIABLE
|
|
#define DISABLE_SIGN_COMPARE_WARNINGS
|
|
|
|
#include "builtin.h"
|
|
#include "config.h"
|
|
#include "ewah/ewok.h"
|
|
#include "lockfile.h"
|
|
#include "color.h"
|
|
#include "commit.h"
|
|
#include "gettext.h"
|
|
#include "tag.h"
|
|
#include "diff.h"
|
|
#include "diff-merges.h"
|
|
#include "diffcore.h"
|
|
#include "preload-index.h"
|
|
#include "read-cache-ll.h"
|
|
#include "revision.h"
|
|
#include "log-tree.h"
|
|
#include "setup.h"
|
|
#include "oid-array.h"
|
|
#include "tree.h"
|
|
|
|
#define DIFF_NO_INDEX_EXPLICIT 1
|
|
#define DIFF_NO_INDEX_IMPLICIT 2
|
|
|
|
static const char builtin_diff_usage[] =
|
|
"git diff [<options>] [<commit>] [--] [<path>...]\n"
|
|
" or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n"
|
|
" or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n"
|
|
" or: git diff [<options>] <commit>...<commit> [--] [<path>...]\n"
|
|
" or: git diff [<options>] <blob> <blob>\n"
|
|
" or: git diff [<options>] --no-index [--] <path> <path> [<pathspec>...]"
|
|
"\n"
|
|
COMMON_DIFF_OPTIONS_HELP;
|
|
|
|
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,
|
|
unsigned old_mode, unsigned new_mode,
|
|
const struct object_id *old_oid,
|
|
const struct object_id *new_oid,
|
|
int old_oid_valid,
|
|
int new_oid_valid,
|
|
const char *old_path,
|
|
const char *new_path)
|
|
{
|
|
struct diff_filespec *one, *two;
|
|
|
|
if (!is_null_oid(old_oid) && !is_null_oid(new_oid) &&
|
|
oideq(old_oid, new_oid) && (old_mode == new_mode))
|
|
return;
|
|
|
|
if (opt->flags.reverse_diff) {
|
|
SWAP(old_mode, new_mode);
|
|
SWAP(old_oid, new_oid);
|
|
SWAP(old_path, new_path);
|
|
}
|
|
|
|
if (opt->prefix &&
|
|
(strncmp(old_path, opt->prefix, opt->prefix_length) ||
|
|
strncmp(new_path, opt->prefix, opt->prefix_length)))
|
|
return;
|
|
|
|
one = alloc_filespec(old_path);
|
|
two = alloc_filespec(new_path);
|
|
fill_filespec(one, old_oid, old_oid_valid, old_mode);
|
|
fill_filespec(two, new_oid, new_oid_valid, new_mode);
|
|
|
|
diff_queue(&diff_queued_diff, one, two);
|
|
}
|
|
|
|
static void builtin_diff_b_f(struct rev_info *revs,
|
|
int argc, const char **argv UNUSED,
|
|
struct object_array_entry **blob)
|
|
{
|
|
/* Blob vs file in the working tree*/
|
|
struct stat st;
|
|
const char *path;
|
|
|
|
if (argc > 1)
|
|
usage(builtin_diff_usage);
|
|
|
|
GUARD_PATHSPEC(&revs->prune_data, PATHSPEC_FROMTOP | PATHSPEC_LITERAL);
|
|
path = revs->prune_data.items[0].match;
|
|
|
|
if (lstat(path, &st))
|
|
die_errno(_("failed to stat '%s'"), path);
|
|
if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
|
|
die(_("'%s': not a regular file or symlink"), path);
|
|
|
|
diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
|
|
|
|
if (blob[0]->mode == S_IFINVALID)
|
|
blob[0]->mode = canon_mode(st.st_mode);
|
|
|
|
stuff_change(&revs->diffopt,
|
|
blob[0]->mode, canon_mode(st.st_mode),
|
|
&blob[0]->item->oid, null_oid(the_hash_algo),
|
|
1, 0,
|
|
blob[0]->path ? blob[0]->path : path,
|
|
path);
|
|
diffcore_std(&revs->diffopt);
|
|
diff_flush(&revs->diffopt);
|
|
}
|
|
|
|
static void builtin_diff_blobs(struct rev_info *revs,
|
|
int argc, const char **argv UNUSED,
|
|
struct object_array_entry **blob)
|
|
{
|
|
const unsigned mode = canon_mode(S_IFREG | 0644);
|
|
|
|
if (argc > 1)
|
|
usage(builtin_diff_usage);
|
|
|
|
if (blob[0]->mode == S_IFINVALID)
|
|
blob[0]->mode = mode;
|
|
|
|
if (blob[1]->mode == S_IFINVALID)
|
|
blob[1]->mode = mode;
|
|
|
|
stuff_change(&revs->diffopt,
|
|
blob[0]->mode, blob[1]->mode,
|
|
&blob[0]->item->oid, &blob[1]->item->oid,
|
|
1, 1,
|
|
blob_path(blob[0]), blob_path(blob[1]));
|
|
diffcore_std(&revs->diffopt);
|
|
diff_flush(&revs->diffopt);
|
|
}
|
|
|
|
static void builtin_diff_index(struct rev_info *revs,
|
|
int argc, const char **argv)
|
|
{
|
|
unsigned int option = 0;
|
|
while (1 < argc) {
|
|
const char *arg = argv[1];
|
|
if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
|
|
option |= DIFF_INDEX_CACHED;
|
|
else if (!strcmp(arg, "--merge-base"))
|
|
option |= DIFF_INDEX_MERGE_BASE;
|
|
else
|
|
usage(builtin_diff_usage);
|
|
argv++; argc--;
|
|
}
|
|
/*
|
|
* Make sure there is one revision (i.e. pending object),
|
|
* and there is no revision filtering parameters.
|
|
*/
|
|
if (revs->pending.nr != 1 ||
|
|
revs->max_count != -1 || revs->min_age != -1 ||
|
|
revs->max_age != -1)
|
|
usage(builtin_diff_usage);
|
|
if (!(option & DIFF_INDEX_CACHED)) {
|
|
setup_work_tree(the_repository);
|
|
if (repo_read_index_preload(the_repository,
|
|
&revs->diffopt.pathspec, 0) < 0) {
|
|
die_errno("repo_read_index_preload");
|
|
}
|
|
} else if (repo_read_index(the_repository) < 0) {
|
|
die_errno("repo_read_cache");
|
|
}
|
|
run_diff_index(revs, option);
|
|
}
|
|
|
|
static void builtin_diff_tree(struct rev_info *revs,
|
|
int argc, const char **argv,
|
|
struct object_array_entry *ent0,
|
|
struct object_array_entry *ent1)
|
|
{
|
|
const struct object_id *(oid[2]);
|
|
struct object_id mb_oid;
|
|
int merge_base = 0;
|
|
|
|
while (1 < argc) {
|
|
const char *arg = argv[1];
|
|
if (!strcmp(arg, "--merge-base"))
|
|
merge_base = 1;
|
|
else
|
|
usage(builtin_diff_usage);
|
|
argv++; argc--;
|
|
}
|
|
|
|
if (merge_base) {
|
|
diff_get_merge_base(revs, &mb_oid);
|
|
oid[0] = &mb_oid;
|
|
oid[1] = &revs->pending.objects[1].item->oid;
|
|
} else {
|
|
int swap = 0;
|
|
|
|
/*
|
|
* We saw two trees, ent0 and ent1. If ent1 is uninteresting,
|
|
* swap them.
|
|
*/
|
|
if (ent1->item->flags & UNINTERESTING)
|
|
swap = 1;
|
|
oid[swap] = &ent0->item->oid;
|
|
oid[1 - swap] = &ent1->item->oid;
|
|
}
|
|
diff_tree_oid(oid[0], oid[1], "", &revs->diffopt);
|
|
log_tree_diff_flush(revs);
|
|
}
|
|
|
|
static void builtin_diff_combined(struct rev_info *revs,
|
|
int argc, const char **argv UNUSED,
|
|
struct object_array_entry *ent,
|
|
int ents, int first_non_parent)
|
|
{
|
|
struct oid_array parents = OID_ARRAY_INIT;
|
|
int i;
|
|
|
|
if (argc > 1)
|
|
usage(builtin_diff_usage);
|
|
|
|
if (first_non_parent < 0)
|
|
die(_("no merge given, only parents."));
|
|
if (first_non_parent >= ents)
|
|
BUG("first_non_parent out of range: %d", first_non_parent);
|
|
|
|
diff_merges_set_dense_combined_if_unset(revs);
|
|
|
|
for (i = 0; i < ents; i++) {
|
|
if (i != first_non_parent)
|
|
oid_array_append(&parents, &ent[i].item->oid);
|
|
}
|
|
diff_tree_combined(&ent[first_non_parent].item->oid, &parents, revs);
|
|
oid_array_clear(&parents);
|
|
}
|
|
|
|
static void refresh_index_quietly(void)
|
|
{
|
|
struct lock_file lock_file = LOCK_INIT;
|
|
int fd;
|
|
|
|
fd = repo_hold_locked_index(the_repository, &lock_file, 0);
|
|
if (fd < 0)
|
|
return;
|
|
discard_index(the_repository->index);
|
|
repo_read_index(the_repository);
|
|
refresh_index(the_repository->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL,
|
|
NULL);
|
|
repo_update_index_if_able(the_repository, &lock_file);
|
|
}
|
|
|
|
static void builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
|
|
{
|
|
unsigned int options = 0;
|
|
|
|
while (1 < argc && argv[1][0] == '-') {
|
|
if (!strcmp(argv[1], "--base"))
|
|
revs->max_count = 1;
|
|
else if (!strcmp(argv[1], "--ours"))
|
|
revs->max_count = 2;
|
|
else if (!strcmp(argv[1], "--theirs"))
|
|
revs->max_count = 3;
|
|
else if (!strcmp(argv[1], "-q"))
|
|
options |= DIFF_SILENT_ON_REMOVED;
|
|
else if (!strcmp(argv[1], "-h"))
|
|
usage(builtin_diff_usage);
|
|
else {
|
|
error(_("invalid option: %s"), argv[1]);
|
|
usage(builtin_diff_usage);
|
|
}
|
|
argv++; argc--;
|
|
}
|
|
|
|
/*
|
|
* "diff --base" should not combine merges because it was not
|
|
* asked to. "diff -c" should not densify (if the user wants
|
|
* dense one, --cc can be explicitly asked for, or just rely
|
|
* on the default).
|
|
*/
|
|
if (revs->max_count == -1 &&
|
|
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
|
|
diff_merges_set_dense_combined_if_unset(revs);
|
|
|
|
setup_work_tree(the_repository);
|
|
if (repo_read_index_preload(the_repository, &revs->diffopt.pathspec,
|
|
0) < 0) {
|
|
die_errno("repo_read_index_preload");
|
|
}
|
|
run_diff_files(revs, options);
|
|
}
|
|
|
|
struct symdiff {
|
|
struct bitmap *skip;
|
|
int warn;
|
|
const char *base, *left, *right;
|
|
};
|
|
|
|
/*
|
|
* Check for symmetric-difference arguments, and if present, arrange
|
|
* everything we need to know to handle them correctly. As a bonus,
|
|
* weed out all bogus range-based revision specifications, e.g.,
|
|
* "git diff A..B C..D" or "git diff A..B C" get rejected.
|
|
*
|
|
* For an actual symmetric diff, *symdiff is set this way:
|
|
*
|
|
* - its skip is non-NULL and marks *all* rev->pending.objects[i]
|
|
* indices that the caller should ignore (extra merge bases, of
|
|
* which there might be many, and A in A...B). Note that the
|
|
* chosen merge base and right side are NOT marked.
|
|
* - warn is set if there are multiple merge bases.
|
|
* - base, left, and right point to the names to use in a
|
|
* warning about multiple merge bases.
|
|
*
|
|
* If there is no symmetric diff argument, sym->skip is NULL and
|
|
* sym->warn is cleared. The remaining fields are not set.
|
|
*/
|
|
static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)
|
|
{
|
|
int i, is_symdiff = 0, basecount = 0, othercount = 0;
|
|
int lpos = -1, rpos = -1, basepos = -1;
|
|
struct bitmap *map = NULL;
|
|
|
|
/*
|
|
* Use the whence fields to find merge bases and left and
|
|
* right parts of symmetric difference, so that we do not
|
|
* depend on the order that revisions are parsed. If there
|
|
* are any revs that aren't from these sources, we have a
|
|
* "git diff C A...B" or "git diff A...B C" case. Or we
|
|
* could even get "git diff A...B C...E", for instance.
|
|
*
|
|
* If we don't have just one merge base, we pick one
|
|
* at random.
|
|
*
|
|
* NB: REV_CMD_LEFT, REV_CMD_RIGHT are also used for A..B,
|
|
* so we must check for SYMMETRIC_LEFT too. The two arrays
|
|
* rev->pending.objects and rev->cmdline.rev are parallel.
|
|
*/
|
|
for (i = 0; i < rev->cmdline.nr; i++) {
|
|
struct object *obj = rev->pending.objects[i].item;
|
|
switch (rev->cmdline.rev[i].whence) {
|
|
case REV_CMD_MERGE_BASE:
|
|
if (basepos < 0)
|
|
basepos = i;
|
|
basecount++;
|
|
break; /* do mark all bases */
|
|
case REV_CMD_LEFT:
|
|
if (lpos >= 0)
|
|
usage(builtin_diff_usage);
|
|
lpos = i;
|
|
if (obj->flags & SYMMETRIC_LEFT) {
|
|
is_symdiff = 1;
|
|
break; /* do mark A */
|
|
}
|
|
continue;
|
|
case REV_CMD_RIGHT:
|
|
if (rpos >= 0)
|
|
usage(builtin_diff_usage);
|
|
rpos = i;
|
|
continue; /* don't mark B */
|
|
case REV_CMD_PARENTS_ONLY:
|
|
case REV_CMD_REF:
|
|
case REV_CMD_REV:
|
|
othercount++;
|
|
continue;
|
|
}
|
|
if (!map)
|
|
map = bitmap_new();
|
|
bitmap_set(map, i);
|
|
}
|
|
|
|
/*
|
|
* Forbid any additional revs for both A...B and A..B.
|
|
*/
|
|
if (lpos >= 0 && othercount > 0)
|
|
usage(builtin_diff_usage);
|
|
|
|
if (!is_symdiff) {
|
|
bitmap_free(map);
|
|
sym->warn = 0;
|
|
sym->skip = NULL;
|
|
return;
|
|
}
|
|
|
|
sym->left = rev->pending.objects[lpos].name;
|
|
sym->right = rev->pending.objects[rpos].name;
|
|
if (basecount == 0)
|
|
die(_("%s...%s: no merge base"), sym->left, sym->right);
|
|
sym->base = rev->pending.objects[basepos].name;
|
|
bitmap_unset(map, basepos); /* unmark the base we want */
|
|
sym->warn = basecount > 1;
|
|
sym->skip = map;
|
|
}
|
|
|
|
static void symdiff_release(struct symdiff *sdiff)
|
|
{
|
|
bitmap_free(sdiff->skip);
|
|
}
|
|
|
|
int cmd_diff(int argc,
|
|
const char **argv,
|
|
const char *prefix,
|
|
struct repository *repo UNUSED)
|
|
{
|
|
int i;
|
|
struct rev_info rev;
|
|
struct object_array ent = OBJECT_ARRAY_INIT;
|
|
int first_non_parent = -1;
|
|
int blobs = 0, paths = 0;
|
|
struct object_array_entry *blob[2];
|
|
int nongit = 0, no_index = 0;
|
|
int result;
|
|
struct symdiff sdiff;
|
|
|
|
/*
|
|
* We could get N tree-ish in the rev.pending_objects list.
|
|
* Also there could be M blobs there, and P pathspecs. --cached may
|
|
* also be present.
|
|
*
|
|
* N=0, M=0:
|
|
* cache vs files (diff-files)
|
|
*
|
|
* N=0, M=0, --cached:
|
|
* HEAD vs cache (diff-index --cached)
|
|
*
|
|
* N=0, M=2:
|
|
* compare two random blobs. P must be zero.
|
|
*
|
|
* N=0, M=1, P=1:
|
|
* compare a blob with a working tree file.
|
|
*
|
|
* N=1, M=0:
|
|
* tree vs files (diff-index)
|
|
*
|
|
* N=1, M=0, --cached:
|
|
* tree vs cache (diff-index --cached)
|
|
*
|
|
* N=2, M=0:
|
|
* tree vs tree (diff-tree)
|
|
*
|
|
* N=0, M=0, P=2:
|
|
* compare two filesystem entities (aka --no-index).
|
|
*
|
|
* Other cases are errors.
|
|
*/
|
|
|
|
/* Were we asked to do --no-index explicitly? */
|
|
for (i = 1; i < argc; i++) {
|
|
if (!strcmp(argv[i], "--")) {
|
|
i++;
|
|
break;
|
|
}
|
|
if (!strcmp(argv[i], "--no-index"))
|
|
no_index = DIFF_NO_INDEX_EXPLICIT;
|
|
if (argv[i][0] != '-')
|
|
break;
|
|
}
|
|
|
|
prefix = setup_git_directory_gently(the_repository, &nongit);
|
|
|
|
if (!nongit) {
|
|
prepare_repo_settings(the_repository);
|
|
the_repository->settings.command_requires_full_index = 0;
|
|
}
|
|
|
|
if (!no_index) {
|
|
/*
|
|
* Treat git diff with at least one path outside of the
|
|
* repo the same as if the command would have been executed
|
|
* outside of a git repository. In this case it behaves
|
|
* the same way as "git diff --no-index <a> <b>", which acts
|
|
* as a colourful "diff" replacement.
|
|
*/
|
|
if (nongit || ((argc == i + 2) &&
|
|
(!path_inside_repo(the_repository, prefix, argv[i]) ||
|
|
!path_inside_repo(the_repository, prefix, argv[i + 1]))))
|
|
no_index = DIFF_NO_INDEX_IMPLICIT;
|
|
}
|
|
|
|
/*
|
|
* When operating outside of a Git repository we need to have a hash
|
|
* algorithm at hand so that we can generate the blob hashes. We
|
|
* default to SHA1 here, but may eventually want to change this to be
|
|
* configurable via a command line option.
|
|
*/
|
|
if (nongit)
|
|
repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT);
|
|
|
|
init_diff_ui_defaults();
|
|
repo_config(the_repository, git_diff_ui_config, NULL);
|
|
|
|
/*
|
|
* If we are ignoring the fact that our current directory may
|
|
* be part of a working tree controlled by a Git repository to
|
|
* pretend to be a "better GNU diff", we should undo the
|
|
* effect of the setup code that did a chdir() to the top of
|
|
* the working tree. Where we came from is recorded in the
|
|
* prefix.
|
|
*/
|
|
if (no_index && prefix) {
|
|
if (chdir(prefix))
|
|
die(_("cannot come back to cwd"));
|
|
prefix = NULL;
|
|
}
|
|
|
|
prefix = precompose_argv_prefix(argc, argv, prefix);
|
|
|
|
repo_init_revisions(the_repository, &rev, prefix);
|
|
|
|
/* Set up defaults that will apply to both no-index and regular diffs. */
|
|
init_diffstat_widths(&rev.diffopt);
|
|
rev.diffopt.flags.allow_external = 1;
|
|
rev.diffopt.flags.allow_textconv = 1;
|
|
rev.diffopt.flags.allow_diff_process = 1;
|
|
|
|
/* If this is a no-index diff, just run it and exit there. */
|
|
if (no_index)
|
|
exit(diff_no_index(&rev, the_repository->hash_algo,
|
|
no_index == DIFF_NO_INDEX_IMPLICIT,
|
|
argc, argv));
|
|
|
|
|
|
/*
|
|
* Otherwise, we are doing the usual "git" diff; set up any
|
|
* further defaults that apply to regular diffs.
|
|
*/
|
|
rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
|
|
|
|
/*
|
|
* Default to intent-to-add entries invisible in the
|
|
* index. This makes them show up as new files in diff-files
|
|
* and not at all in diff-cached.
|
|
*/
|
|
rev.diffopt.ita_invisible_in_index = 1;
|
|
|
|
if (nongit)
|
|
die(_("Not a git repository"));
|
|
argc = setup_revisions(argc, argv, &rev, NULL);
|
|
if (!rev.diffopt.output_format) {
|
|
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
|
|
diff_setup_done(&rev.diffopt);
|
|
}
|
|
|
|
rev.diffopt.flags.recursive = 1;
|
|
rev.diffopt.rotate_to_strict = 1;
|
|
|
|
setup_diff_pager(&rev.diffopt);
|
|
|
|
/*
|
|
* Do we have --cached and not have a pending object, then
|
|
* default to HEAD by hand. Eek.
|
|
*/
|
|
if (!rev.pending.nr) {
|
|
int i;
|
|
for (i = 1; i < argc; i++) {
|
|
const char *arg = argv[i];
|
|
if (!strcmp(arg, "--"))
|
|
break;
|
|
else if (!strcmp(arg, "--cached") ||
|
|
!strcmp(arg, "--staged")) {
|
|
add_head_to_pending(&rev);
|
|
if (!rev.pending.nr) {
|
|
struct tree *tree;
|
|
tree = lookup_tree(the_repository,
|
|
the_repository->hash_algo->empty_tree);
|
|
add_pending_object(&rev, &tree->object, "HEAD");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The hunk store is keyed by blob pair, so any diff whose
|
|
* file pairs carry known blob object IDs (tree-to-tree,
|
|
* index-to-tree) can consult the same entries that
|
|
* "git log --stat" and "git blame" use; pairs without known
|
|
* blobs bypass it at lookup time.
|
|
*/
|
|
diff_hunks_attach(&rev.diffopt);
|
|
|
|
symdiff_prepare(&rev, &sdiff);
|
|
for (i = 0; i < rev.pending.nr; i++) {
|
|
struct object_array_entry *entry = &rev.pending.objects[i];
|
|
struct object *obj = entry->item;
|
|
const char *name = entry->name;
|
|
int flags = (obj->flags & UNINTERESTING);
|
|
if (!obj->parsed)
|
|
obj = parse_object(the_repository, &obj->oid);
|
|
obj = deref_tag(the_repository, obj, NULL, 0);
|
|
if (!obj)
|
|
die(_("invalid object '%s' given."), name);
|
|
if (obj->type == OBJ_COMMIT) {
|
|
struct tree *tree = repo_get_commit_tree(
|
|
the_repository, (struct commit *)obj);
|
|
if (!tree)
|
|
die(_("unable to read tree object for commit '%s'"), name);
|
|
obj = &tree->object;
|
|
}
|
|
|
|
if (obj->type == OBJ_TREE) {
|
|
if (sdiff.skip && bitmap_get(sdiff.skip, i))
|
|
continue;
|
|
obj->flags |= flags;
|
|
add_object_array(obj, name, &ent);
|
|
if (first_non_parent < 0 &&
|
|
(i >= rev.cmdline.nr || /* HEAD by hand. */
|
|
rev.cmdline.rev[i].whence != REV_CMD_PARENTS_ONLY))
|
|
first_non_parent = ent.nr - 1;
|
|
} else if (obj->type == OBJ_BLOB) {
|
|
if (2 <= blobs)
|
|
die(_("more than two blobs given: '%s'"), name);
|
|
blob[blobs] = entry;
|
|
blobs++;
|
|
|
|
} else {
|
|
die(_("unhandled object '%s' given."), name);
|
|
}
|
|
}
|
|
if (rev.prune_data.nr)
|
|
paths += rev.prune_data.nr;
|
|
|
|
/*
|
|
* Now, do the arguments look reasonable?
|
|
*/
|
|
if (!ent.nr) {
|
|
switch (blobs) {
|
|
case 0:
|
|
builtin_diff_files(&rev, argc, argv);
|
|
break;
|
|
case 1:
|
|
if (paths != 1)
|
|
usage(builtin_diff_usage);
|
|
builtin_diff_b_f(&rev, argc, argv, blob);
|
|
break;
|
|
case 2:
|
|
if (paths)
|
|
usage(builtin_diff_usage);
|
|
builtin_diff_blobs(&rev, argc, argv, blob);
|
|
break;
|
|
default:
|
|
usage(builtin_diff_usage);
|
|
}
|
|
}
|
|
else if (blobs)
|
|
usage(builtin_diff_usage);
|
|
else if (ent.nr == 1)
|
|
builtin_diff_index(&rev, argc, argv);
|
|
else if (ent.nr == 2) {
|
|
if (sdiff.warn)
|
|
warning(_("%s...%s: multiple merge bases, using %s"),
|
|
sdiff.left, sdiff.right, sdiff.base);
|
|
builtin_diff_tree(&rev, argc, argv,
|
|
&ent.objects[0], &ent.objects[1]);
|
|
} else
|
|
builtin_diff_combined(&rev, argc, argv,
|
|
ent.objects, ent.nr,
|
|
first_non_parent);
|
|
result = diff_result_code(&rev);
|
|
if (1 < rev.diffopt.skip_stat_unmatch)
|
|
refresh_index_quietly();
|
|
diff_hunks_detach(&rev.diffopt);
|
|
release_revisions(&rev);
|
|
object_array_clear(&ent);
|
|
symdiff_release(&sdiff);
|
|
return result;
|
|
}
|