mirror of
https://github.com/git/git.git
synced 2024-11-24 10:26:17 +08:00
3e09cdfd11
With "[core] filemode = false", you can tell git to ignore differences in the working tree file only in executable bit. * "git-update-index --refresh" does not say "needs update" if index entry and working tree file differs only in executable bit. * "git-update-index" on an existing path takes executable bit from the existing index entry, if the path and index entry are both regular files. * "git-diff-files" and "git-diff-index" without --cached flag pretend the path on the filesystem has the same executable bit as the existing index entry, if the path and index entry are both regular files. If you are on a filesystem with unreliable mode bits, you may need to force the executable bit after registering the path in the index. * "git-update-index --chmod=+x foo" flips the executable bit of the index file entry for path "foo" on. Use "--chmod=-x" to flip it off. Note that --chmod only works in index file and does not look at nor update the working tree. So if you are on a filesystem and do not have working executable bit, you would do: 1. set the appropriate .git/config option; 2. "git-update-index --add new-file.c" 3. "git-ls-files --stage new-file.c" to see if it has the desired mode bits. If not, e.g. to drop executable bit picked up from the filesystem, say "git-update-index --chmod=-x new-file.c". Signed-off-by: Junio C Hamano <junkio@cox.net>
248 lines
5.5 KiB
C
248 lines
5.5 KiB
C
#include "cache.h"
|
|
#include "diff.h"
|
|
|
|
static int cached_only = 0;
|
|
static int match_nonexisting = 0;
|
|
static struct diff_options diff_options;
|
|
|
|
/* A file entry went away or appeared */
|
|
static void show_file(const char *prefix,
|
|
struct cache_entry *ce,
|
|
unsigned char *sha1, unsigned int mode)
|
|
{
|
|
diff_addremove(&diff_options, prefix[0], ntohl(mode),
|
|
sha1, ce->name, NULL);
|
|
}
|
|
|
|
static int get_stat_data(struct cache_entry *ce,
|
|
unsigned char ** sha1p, unsigned int *modep)
|
|
{
|
|
unsigned char *sha1 = ce->sha1;
|
|
unsigned int mode = ce->ce_mode;
|
|
|
|
if (!cached_only) {
|
|
static unsigned char no_sha1[20];
|
|
int changed;
|
|
struct stat st;
|
|
if (lstat(ce->name, &st) < 0) {
|
|
if (errno == ENOENT && match_nonexisting) {
|
|
*sha1p = sha1;
|
|
*modep = mode;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
changed = ce_match_stat(ce, &st);
|
|
if (changed) {
|
|
mode = create_ce_mode(st.st_mode);
|
|
if (!trust_executable_bit &&
|
|
S_ISREG(mode) && S_ISREG(ce->ce_mode) &&
|
|
((mode ^ ce->ce_mode) == 0111))
|
|
mode = ce->ce_mode;
|
|
sha1 = no_sha1;
|
|
}
|
|
}
|
|
|
|
*sha1p = sha1;
|
|
*modep = mode;
|
|
return 0;
|
|
}
|
|
|
|
static void show_new_file(struct cache_entry *new)
|
|
{
|
|
unsigned char *sha1;
|
|
unsigned int mode;
|
|
|
|
/* New file in the index: it might actually be different in
|
|
* the working copy.
|
|
*/
|
|
if (get_stat_data(new, &sha1, &mode) < 0)
|
|
return;
|
|
|
|
show_file("+", new, sha1, mode);
|
|
}
|
|
|
|
static int show_modified(struct cache_entry *old,
|
|
struct cache_entry *new,
|
|
int report_missing)
|
|
{
|
|
unsigned int mode, oldmode;
|
|
unsigned char *sha1;
|
|
|
|
if (get_stat_data(new, &sha1, &mode) < 0) {
|
|
if (report_missing)
|
|
show_file("-", old, old->sha1, old->ce_mode);
|
|
return -1;
|
|
}
|
|
|
|
oldmode = old->ce_mode;
|
|
if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
|
|
!diff_options.find_copies_harder)
|
|
return 0;
|
|
|
|
mode = ntohl(mode);
|
|
oldmode = ntohl(oldmode);
|
|
|
|
diff_change(&diff_options, oldmode, mode,
|
|
old->sha1, sha1, old->name, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec)
|
|
{
|
|
while (entries) {
|
|
struct cache_entry *ce = *ac;
|
|
int same = (entries > 1) && ce_same_name(ce, ac[1]);
|
|
|
|
if (!ce_path_match(ce, pathspec))
|
|
goto skip_entry;
|
|
|
|
switch (ce_stage(ce)) {
|
|
case 0:
|
|
/* No stage 1 entry? That means it's a new file */
|
|
if (!same) {
|
|
show_new_file(ce);
|
|
break;
|
|
}
|
|
/* Show difference between old and new */
|
|
show_modified(ac[1], ce, 1);
|
|
break;
|
|
case 1:
|
|
/* No stage 3 (merge) entry? That means it's been deleted */
|
|
if (!same) {
|
|
show_file("-", ce, ce->sha1, ce->ce_mode);
|
|
break;
|
|
}
|
|
/* We come here with ce pointing at stage 1
|
|
* (original tree) and ac[1] pointing at stage
|
|
* 3 (unmerged). show-modified with
|
|
* report-mising set to false does not say the
|
|
* file is deleted but reports true if work
|
|
* tree does not have it, in which case we
|
|
* fall through to report the unmerged state.
|
|
* Otherwise, we show the differences between
|
|
* the original tree and the work tree.
|
|
*/
|
|
if (!cached_only && !show_modified(ce, ac[1], 0))
|
|
break;
|
|
/* fallthru */
|
|
case 3:
|
|
diff_unmerge(&diff_options, ce->name);
|
|
break;
|
|
|
|
default:
|
|
die("impossible cache entry stage");
|
|
}
|
|
|
|
skip_entry:
|
|
/*
|
|
* Ignore all the different stages for this file,
|
|
* we've handled the relevant cases now.
|
|
*/
|
|
do {
|
|
ac++;
|
|
entries--;
|
|
} while (entries && ce_same_name(ce, ac[0]));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This turns all merge entries into "stage 3". That guarantees that
|
|
* when we read in the new tree (into "stage 1"), we won't lose sight
|
|
* of the fact that we had unmerged entries.
|
|
*/
|
|
static void mark_merge_entries(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < active_nr; i++) {
|
|
struct cache_entry *ce = active_cache[i];
|
|
if (!ce_stage(ce))
|
|
continue;
|
|
ce->ce_flags |= htons(CE_STAGEMASK);
|
|
}
|
|
}
|
|
|
|
static const char diff_cache_usage[] =
|
|
"git-diff-index [-m] [--cached] "
|
|
"[<common diff options>] <tree-ish> [<path>...]"
|
|
COMMON_DIFF_OPTIONS_HELP;
|
|
|
|
int main(int argc, const char **argv)
|
|
{
|
|
const char *tree_name = NULL;
|
|
unsigned char sha1[20];
|
|
const char *prefix = setup_git_directory();
|
|
const char **pathspec = NULL;
|
|
void *tree;
|
|
unsigned long size;
|
|
int ret;
|
|
int allow_options = 1;
|
|
int i;
|
|
|
|
git_config(git_default_config);
|
|
diff_setup(&diff_options);
|
|
for (i = 1; i < argc; i++) {
|
|
const char *arg = argv[i];
|
|
int diff_opt_cnt;
|
|
|
|
if (!allow_options || *arg != '-') {
|
|
if (tree_name)
|
|
break;
|
|
tree_name = arg;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(arg, "--")) {
|
|
allow_options = 0;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "-r")) {
|
|
/* We accept the -r flag just to look like git-diff-tree */
|
|
continue;
|
|
}
|
|
diff_opt_cnt = diff_opt_parse(&diff_options, argv + i,
|
|
argc - i);
|
|
if (diff_opt_cnt < 0)
|
|
usage(diff_cache_usage);
|
|
else if (diff_opt_cnt) {
|
|
i += diff_opt_cnt - 1;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(arg, "-m")) {
|
|
match_nonexisting = 1;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--cached")) {
|
|
cached_only = 1;
|
|
continue;
|
|
}
|
|
usage(diff_cache_usage);
|
|
}
|
|
|
|
pathspec = get_pathspec(prefix, argv + i);
|
|
|
|
if (diff_setup_done(&diff_options) < 0)
|
|
usage(diff_cache_usage);
|
|
|
|
if (!tree_name || get_sha1(tree_name, sha1))
|
|
usage(diff_cache_usage);
|
|
|
|
read_cache();
|
|
|
|
mark_merge_entries();
|
|
|
|
tree = read_object_with_reference(sha1, "tree", &size, NULL);
|
|
if (!tree)
|
|
die("bad tree object %s", tree_name);
|
|
if (read_tree(tree, size, 1, pathspec))
|
|
die("unable to read tree object %s", tree_name);
|
|
|
|
ret = diff_cache(active_cache, active_nr, pathspec);
|
|
|
|
diffcore_std(&diff_options);
|
|
diff_flush(&diff_options);
|
|
return ret;
|
|
}
|