Show modified files in git-ls-files
Add -m/--modified to show files that have been modified wrt. the index. [jc: The original came from Brian Gerst on Sep 1st but it only checked if the paths were cache dirty without actually checking the files were modified. I also added the usage string and a new test.] Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
		@ -1,6 +1,5 @@
 | 
			
		||||
git-ls-files(1)
 | 
			
		||||
===============
 | 
			
		||||
v0.1, May 2005
 | 
			
		||||
 | 
			
		||||
NAME
 | 
			
		||||
----
 | 
			
		||||
@ -10,8 +9,8 @@ git-ls-files - Information about files in the cache/working directory
 | 
			
		||||
SYNOPSIS
 | 
			
		||||
--------
 | 
			
		||||
'git-ls-files' [-z] [-t]
 | 
			
		||||
		(--[cached|deleted|others|ignored|stage|unmerged|killed])\*
 | 
			
		||||
		(-[c|d|o|i|s|u|k])\*
 | 
			
		||||
		(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
 | 
			
		||||
		(-[c|d|o|i|s|u|k|m])\*
 | 
			
		||||
		[-x <pattern>|--exclude=<pattern>]
 | 
			
		||||
		[-X <file>|--exclude-from=<file>]
 | 
			
		||||
		[--exclude-per-directory=<file>]
 | 
			
		||||
@ -33,6 +32,9 @@ OPTIONS
 | 
			
		||||
-d|--deleted::
 | 
			
		||||
	Show deleted files in the output
 | 
			
		||||
 | 
			
		||||
-m|--modified::
 | 
			
		||||
	Show modified files in the output
 | 
			
		||||
 | 
			
		||||
-o|--others::
 | 
			
		||||
	Show other files in the output
 | 
			
		||||
 | 
			
		||||
@ -71,6 +73,7 @@ OPTIONS
 | 
			
		||||
	H	cached
 | 
			
		||||
	M	unmerged
 | 
			
		||||
	R	removed/deleted
 | 
			
		||||
	C	modifed/changed
 | 
			
		||||
	K	to be killed
 | 
			
		||||
	?	other
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								cache.h
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								cache.h
									
									
									
									
									
								
							@ -161,6 +161,7 @@ extern int remove_cache_entry_at(int pos);
 | 
			
		||||
extern int remove_file_from_cache(char *path);
 | 
			
		||||
extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 | 
			
		||||
extern int ce_match_stat(struct cache_entry *ce, struct stat *st);
 | 
			
		||||
extern int ce_modified(struct cache_entry *ce, struct stat *st);
 | 
			
		||||
extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 | 
			
		||||
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type);
 | 
			
		||||
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								ls-files.c
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								ls-files.c
									
									
									
									
									
								
							@ -16,6 +16,7 @@ static int show_others = 0;
 | 
			
		||||
static int show_ignored = 0;
 | 
			
		||||
static int show_stage = 0;
 | 
			
		||||
static int show_unmerged = 0;
 | 
			
		||||
static int show_modified = 0;
 | 
			
		||||
static int show_killed = 0;
 | 
			
		||||
static int line_terminator = '\n';
 | 
			
		||||
 | 
			
		||||
@ -28,6 +29,7 @@ static const char *tag_unmerged = "";
 | 
			
		||||
static const char *tag_removed = "";
 | 
			
		||||
static const char *tag_other = "";
 | 
			
		||||
static const char *tag_killed = "";
 | 
			
		||||
static const char *tag_modified = "";
 | 
			
		||||
 | 
			
		||||
static char *exclude_per_dir = NULL;
 | 
			
		||||
 | 
			
		||||
@ -443,15 +445,18 @@ static void show_files(void)
 | 
			
		||||
			show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (show_deleted) {
 | 
			
		||||
	if (show_deleted | show_modified) {
 | 
			
		||||
		for (i = 0; i < active_nr; i++) {
 | 
			
		||||
			struct cache_entry *ce = active_cache[i];
 | 
			
		||||
			struct stat st;
 | 
			
		||||
			int err;
 | 
			
		||||
			if (excluded(ce->name) != show_ignored)
 | 
			
		||||
				continue;
 | 
			
		||||
			if (!lstat(ce->name, &st))
 | 
			
		||||
				continue;
 | 
			
		||||
			show_ce_entry(tag_removed, ce);
 | 
			
		||||
			err = lstat(ce->name, &st);
 | 
			
		||||
			if (show_deleted && err)
 | 
			
		||||
				show_ce_entry(tag_removed, ce);
 | 
			
		||||
			if (show_modified && ce_modified(ce, &st))
 | 
			
		||||
				show_ce_entry(tag_modified, ce);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -523,7 +528,7 @@ static void verify_pathspec(void)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char ls_files_usage[] =
 | 
			
		||||
	"git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* "
 | 
			
		||||
	"git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
 | 
			
		||||
	"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
 | 
			
		||||
	"[ --exclude-per-directory=<filename> ]";
 | 
			
		||||
 | 
			
		||||
@ -547,6 +552,7 @@ int main(int argc, char **argv)
 | 
			
		||||
			tag_cached = "H ";
 | 
			
		||||
			tag_unmerged = "M ";
 | 
			
		||||
			tag_removed = "R ";
 | 
			
		||||
			tag_modified = "C ";
 | 
			
		||||
			tag_other = "? ";
 | 
			
		||||
			tag_killed = "K ";
 | 
			
		||||
			continue;
 | 
			
		||||
@ -559,6 +565,10 @@ int main(int argc, char **argv)
 | 
			
		||||
			show_deleted = 1;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
 | 
			
		||||
			show_modified = 1;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
 | 
			
		||||
			show_others = 1;
 | 
			
		||||
			continue;
 | 
			
		||||
@ -630,7 +640,8 @@ int main(int argc, char **argv)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* With no flags, we default to showing the cached files */
 | 
			
		||||
	if (!(show_stage | show_deleted | show_others | show_unmerged | show_killed))
 | 
			
		||||
	if (!(show_stage | show_deleted | show_others | show_unmerged |
 | 
			
		||||
	      show_killed | show_modified))
 | 
			
		||||
		show_cached = 1;
 | 
			
		||||
 | 
			
		||||
	read_cache();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										77
									
								
								read-cache.c
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								read-cache.c
									
									
									
									
									
								
							@ -83,6 +83,83 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st)
 | 
			
		||||
	return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int ce_compare_data(struct cache_entry *ce, struct stat *st)
 | 
			
		||||
{
 | 
			
		||||
	int match = -1;
 | 
			
		||||
	int fd = open(ce->name, O_RDONLY);
 | 
			
		||||
 | 
			
		||||
	if (fd >= 0) {
 | 
			
		||||
		unsigned char sha1[20];
 | 
			
		||||
		if (!index_fd(sha1, fd, st, 0, NULL))
 | 
			
		||||
			match = memcmp(sha1, ce->sha1, 20);
 | 
			
		||||
		close(fd);
 | 
			
		||||
	}
 | 
			
		||||
	return match;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int ce_compare_link(struct cache_entry *ce, unsigned long expected_size)
 | 
			
		||||
{
 | 
			
		||||
	int match = -1;
 | 
			
		||||
	char *target;
 | 
			
		||||
	void *buffer;
 | 
			
		||||
	unsigned long size;
 | 
			
		||||
	char type[10];
 | 
			
		||||
	int len;
 | 
			
		||||
 | 
			
		||||
	target = xmalloc(expected_size);
 | 
			
		||||
	len = readlink(ce->name, target, expected_size);
 | 
			
		||||
	if (len != expected_size) {
 | 
			
		||||
		free(target);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	buffer = read_sha1_file(ce->sha1, type, &size);
 | 
			
		||||
	if (!buffer) {
 | 
			
		||||
		free(target);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	if (size == expected_size)
 | 
			
		||||
		match = memcmp(buffer, target, size);
 | 
			
		||||
	free(buffer);
 | 
			
		||||
	free(target);
 | 
			
		||||
	return match;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ce_modified(struct cache_entry *ce, struct stat *st)
 | 
			
		||||
{
 | 
			
		||||
	int changed;
 | 
			
		||||
	changed = ce_match_stat(ce, st);
 | 
			
		||||
	if (!changed)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * If the mode or type has changed, there's no point in trying
 | 
			
		||||
	 * to refresh the entry - it's not going to match
 | 
			
		||||
	 */
 | 
			
		||||
	if (changed & (MODE_CHANGED | TYPE_CHANGED))
 | 
			
		||||
		return changed;
 | 
			
		||||
 | 
			
		||||
	/* Immediately after read-tree or update-index --cacheinfo,
 | 
			
		||||
	 * the length field is zero.  For other cases the ce_size
 | 
			
		||||
	 * should match the SHA1 recorded in the index entry.
 | 
			
		||||
	 */
 | 
			
		||||
	if ((changed & DATA_CHANGED) && ce->ce_size != htonl(0))
 | 
			
		||||
		return changed;
 | 
			
		||||
 | 
			
		||||
	switch (st->st_mode & S_IFMT) {
 | 
			
		||||
	case S_IFREG:
 | 
			
		||||
		if (ce_compare_data(ce, st))
 | 
			
		||||
			return changed | DATA_CHANGED;
 | 
			
		||||
		break;
 | 
			
		||||
	case S_IFLNK:
 | 
			
		||||
		if (ce_compare_link(ce, st->st_size))
 | 
			
		||||
			return changed | DATA_CHANGED;
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		return changed | TYPE_CHANGED;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int base_name_compare(const char *name1, int len1, int mode1,
 | 
			
		||||
		      const char *name2, int len2, int mode2)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
# Copyright (c) 2005 Junio C Hamano
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
test_description='git-ls-files -k flag test.
 | 
			
		||||
test_description='git-ls-files -k and -m flags test.
 | 
			
		||||
 | 
			
		||||
This test prepares the following in the cache:
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,16 @@ and the following on the filesystem:
 | 
			
		||||
 | 
			
		||||
git-ls-files -k should report that existing filesystem
 | 
			
		||||
objects except path4, path5 and path6/file6 to be killed.
 | 
			
		||||
 | 
			
		||||
Also for modification test, the cache and working tree have:
 | 
			
		||||
 | 
			
		||||
    path7       - an empty file, modified to a non-empty file.
 | 
			
		||||
    path8       - a non-empty file, modified to an empty file.
 | 
			
		||||
    path9	- an empty file, cache dirtied.
 | 
			
		||||
    path10	- a non-empty file, cache dirtied.
 | 
			
		||||
 | 
			
		||||
We should report path0, path1, path2/file2, path3/file3, path7 and path8
 | 
			
		||||
modified without reporting path9 and path10.
 | 
			
		||||
'
 | 
			
		||||
. ./test-lib.sh
 | 
			
		||||
 | 
			
		||||
@ -32,11 +42,15 @@ ln -s xyzzy path1
 | 
			
		||||
mkdir path2 path3
 | 
			
		||||
date >path2/file2
 | 
			
		||||
date >path3/file3
 | 
			
		||||
: >path7
 | 
			
		||||
date >path8
 | 
			
		||||
: >path9
 | 
			
		||||
date >path10
 | 
			
		||||
test_expect_success \
 | 
			
		||||
    'git-update-index --add to add various paths.' \
 | 
			
		||||
    "git-update-index --add -- path0 path1 path?/file?"
 | 
			
		||||
    "git-update-index --add -- path0 path1 path?/file? path7 path8 path9 path10"
 | 
			
		||||
 | 
			
		||||
rm -fr path?
 | 
			
		||||
rm -fr path? ;# leave path10 alone
 | 
			
		||||
date >path2
 | 
			
		||||
ln -s frotz path3
 | 
			
		||||
ln -s nitfol path5
 | 
			
		||||
@ -44,6 +58,10 @@ mkdir path0 path1 path6
 | 
			
		||||
date >path0/file0
 | 
			
		||||
date >path1/file1
 | 
			
		||||
date >path6/file6
 | 
			
		||||
date >path7
 | 
			
		||||
: >path8
 | 
			
		||||
: >path9
 | 
			
		||||
touch path10
 | 
			
		||||
 | 
			
		||||
test_expect_success \
 | 
			
		||||
    'git-ls-files -k to show killed files.' \
 | 
			
		||||
@ -58,4 +76,21 @@ EOF
 | 
			
		||||
test_expect_success \
 | 
			
		||||
    'validate git-ls-files -k output.' \
 | 
			
		||||
    'diff .output .expected'
 | 
			
		||||
 | 
			
		||||
test_expect_success \
 | 
			
		||||
    'git-ls-files -m to show modified files.' \
 | 
			
		||||
    'git-ls-files -m >.output'
 | 
			
		||||
cat >.expected <<EOF
 | 
			
		||||
path0
 | 
			
		||||
path1
 | 
			
		||||
path2/file2
 | 
			
		||||
path3/file3
 | 
			
		||||
path7
 | 
			
		||||
path8
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
test_expect_success \
 | 
			
		||||
    'validate git-ls-files -m output.' \
 | 
			
		||||
    'diff .output .expected'
 | 
			
		||||
 | 
			
		||||
test_done
 | 
			
		||||
@ -112,47 +112,6 @@ static int add_file_to_cache(char *path)
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int compare_data(struct cache_entry *ce, struct stat *st)
 | 
			
		||||
{
 | 
			
		||||
	int match = -1;
 | 
			
		||||
	int fd = open(ce->name, O_RDONLY);
 | 
			
		||||
 | 
			
		||||
	if (fd >= 0) {
 | 
			
		||||
		unsigned char sha1[20];
 | 
			
		||||
		if (!index_fd(sha1, fd, st, 0, NULL))
 | 
			
		||||
			match = memcmp(sha1, ce->sha1, 20);
 | 
			
		||||
		close(fd);
 | 
			
		||||
	}
 | 
			
		||||
	return match;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int compare_link(struct cache_entry *ce, unsigned long expected_size)
 | 
			
		||||
{
 | 
			
		||||
	int match = -1;
 | 
			
		||||
	char *target;
 | 
			
		||||
	void *buffer;
 | 
			
		||||
	unsigned long size;
 | 
			
		||||
	char type[10];
 | 
			
		||||
	int len;
 | 
			
		||||
 | 
			
		||||
	target = xmalloc(expected_size);
 | 
			
		||||
	len = readlink(ce->name, target, expected_size);
 | 
			
		||||
	if (len != expected_size) {
 | 
			
		||||
		free(target);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	buffer = read_sha1_file(ce->sha1, type, &size);
 | 
			
		||||
	if (!buffer) {
 | 
			
		||||
		free(target);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	if (size == expected_size)
 | 
			
		||||
		match = memcmp(buffer, target, size);
 | 
			
		||||
	free(buffer);
 | 
			
		||||
	free(target);
 | 
			
		||||
	return match;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * "refresh" does not calculate a new sha1 file or bring the
 | 
			
		||||
 * cache up-to-date for mode/content changes. But what it
 | 
			
		||||
@ -177,33 +136,9 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce)
 | 
			
		||||
	if (!changed)
 | 
			
		||||
		return ce;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * If the mode or type has changed, there's no point in trying
 | 
			
		||||
	 * to refresh the entry - it's not going to match
 | 
			
		||||
	 */
 | 
			
		||||
	if (changed & (MODE_CHANGED | TYPE_CHANGED))
 | 
			
		||||
	if (ce_modified(ce, &st))
 | 
			
		||||
		return ERR_PTR(-EINVAL);
 | 
			
		||||
 | 
			
		||||
	/* Immediately after read-tree or update-index --cacheinfo,
 | 
			
		||||
	 * the length field is zero.  For other cases the ce_size
 | 
			
		||||
	 * should match the SHA1 recorded in the index entry.
 | 
			
		||||
	 */
 | 
			
		||||
	if ((changed & DATA_CHANGED) && ce->ce_size != htonl(0))
 | 
			
		||||
		return ERR_PTR(-EINVAL);
 | 
			
		||||
 | 
			
		||||
	switch (st.st_mode & S_IFMT) {
 | 
			
		||||
	case S_IFREG:
 | 
			
		||||
		if (compare_data(ce, &st))
 | 
			
		||||
			return ERR_PTR(-EINVAL);
 | 
			
		||||
		break;
 | 
			
		||||
	case S_IFLNK:
 | 
			
		||||
		if (compare_link(ce, st.st_size))
 | 
			
		||||
			return ERR_PTR(-EINVAL);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		return ERR_PTR(-EINVAL);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size = ce_size(ce);
 | 
			
		||||
	updated = xmalloc(size);
 | 
			
		||||
	memcpy(updated, ce, size);
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user