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>
git-ls-files(1)
===============
-v0.1, May 2005
NAME
----
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>]
-d|--deleted::
Show deleted files in the output
+-m|--modified::
+ Show modified files in the output
+
-o|--others::
Show other files in the output
H cached
M unmerged
R removed/deleted
+ C modifed/changed
K to be killed
? other
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);
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';
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;
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);
}
}
}
}
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> ]";
tag_cached = "H ";
tag_unmerged = "M ";
tag_removed = "R ";
+ tag_modified = "C ";
tag_other = "? ";
tag_killed = "K ";
continue;
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;
}
/* 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();
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)
{
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files -k and -m flags test.
+
+This test prepares the following in the cache:
+
+ path0 - a file
+ path1 - a symlink
+ path2/file2 - a file in a directory
+ path3/file3 - a file in a directory
+
+and the following on the filesystem:
+
+ path0/file0 - a file in a directory
+ path1/file1 - a file in a directory
+ path2 - a file
+ path3 - a symlink
+ path4 - a file
+ path5 - a symlink
+ path6/file6 - a file in a directory
+
+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
+
+date >path0
+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? path7 path8 path9 path10"
+
+rm -fr path? ;# leave path10 alone
+date >path2
+ln -s frotz path3
+ln -s nitfol path5
+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.' \
+ 'git-ls-files -k >.output'
+cat >.expected <<EOF
+path0/file0
+path1/file1
+path2
+path3
+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
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-test_description='git-ls-files -k flag test.
-
-This test prepares the following in the cache:
-
- path0 - a file
- path1 - a symlink
- path2/file2 - a file in a directory
- path3/file3 - a file in a directory
-
-and the following on the filesystem:
-
- path0/file0 - a file in a directory
- path1/file1 - a file in a directory
- path2 - a file
- path3 - a symlink
- path4 - a file
- path5 - a symlink
- path6/file6 - a file in a directory
-
-git-ls-files -k should report that existing filesystem
-objects except path4, path5 and path6/file6 to be killed.
-'
-. ./test-lib.sh
-
-date >path0
-ln -s xyzzy path1
-mkdir path2 path3
-date >path2/file2
-date >path3/file3
-test_expect_success \
- 'git-update-index --add to add various paths.' \
- "git-update-index --add -- path0 path1 path?/file?"
-
-rm -fr path?
-date >path2
-ln -s frotz path3
-ln -s nitfol path5
-mkdir path0 path1 path6
-date >path0/file0
-date >path1/file1
-date >path6/file6
-
-test_expect_success \
- 'git-ls-files -k to show killed files.' \
- 'git-ls-files -k >.output'
-cat >.expected <<EOF
-path0/file0
-path1/file1
-path2
-path3
-EOF
-
-test_expect_success \
- 'validate git-ls-files -k output.' \
- 'diff .output .expected'
-test_done
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
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))
- 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))
+ if (ce_modified(ce, &st))
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);