summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Junio C Hamano <junkio@cox.net>2006-05-17 15:32:45 -0700
committerLibravatar Junio C Hamano <junkio@cox.net>2006-05-17 15:39:38 -0700
commitae12e59a8c895e48d4ef52ac6936674ac7be6370 (patch)
tree727b6fbbffb6e96d582606511d665b6eb0cb7ec1
parentMerge branch 'jc/gitlink' into next (diff)
parentbuiltin-add: warn on unmatched pathspecs (diff)
downloadtgif-ae12e59a8c895e48d4ef52ac6936674ac7be6370.tar.xz
Merge branch 'lt/dirwalk' into next
* lt/dirwalk: builtin-add: warn on unmatched pathspecs Do "git add" as a builtin Clean up git-ls-file directory walking library interface libify git-ls-files directory traversal Not a conflict, but builtin-add needed to be adjusted to properly invalidate the cache_tree entry.
-rw-r--r--Makefile6
-rw-r--r--builtin-add.c269
-rw-r--r--builtin.h1
-rw-r--r--dir.c321
-rw-r--r--dir.h48
-rw-r--r--git.c1
-rw-r--r--ls-files.c381
7 files changed, 671 insertions, 356 deletions
diff --git a/Makefile b/Makefile
index 0aeb94c55d..2c87886c44 100644
--- a/Makefile
+++ b/Makefile
@@ -201,7 +201,7 @@ LIB_H = \
blob.h cache.h commit.h csum-file.h delta.h \
diff.h object.h pack.h pkt-line.h quote.h refs.h \
run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
- tree-walk.h log-tree.h
+ tree-walk.h log-tree.h dir.h
DIFF_OBJS = \
diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -212,7 +212,7 @@ LIB_OBJS = \
blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
object.o pack-check.o patch-delta.o path.o pkt-line.o \
- quote.o read-cache.o refs.o run-command.o \
+ quote.o read-cache.o refs.o run-command.o dir.o \
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
@@ -220,7 +220,7 @@ LIB_OBJS = \
BUILTIN_OBJS = \
builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
- builtin-grep.o
+ builtin-grep.o builtin-add.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
LIBS = $(GITLIBS) -lz
diff --git a/builtin-add.c b/builtin-add.c
new file mode 100644
index 0000000000..7083820f7b
--- /dev/null
+++ b/builtin-add.c
@@ -0,0 +1,269 @@
+/*
+ * "git add" builtin command
+ *
+ * Copyright (C) 2006 Linus Torvalds
+ */
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+
+static const char builtin_add_usage[] =
+"git-add [-n] [-v] <filepattern>...";
+
+static int common_prefix(const char **pathspec)
+{
+ const char *path, *slash, *next;
+ int prefix;
+
+ if (!pathspec)
+ return 0;
+
+ path = *pathspec;
+ slash = strrchr(path, '/');
+ if (!slash)
+ return 0;
+
+ prefix = slash - path + 1;
+ while ((next = *++pathspec) != NULL) {
+ int len = strlen(next);
+ if (len >= prefix && !memcmp(path, next, len))
+ continue;
+ for (;;) {
+ if (!len)
+ return 0;
+ if (next[--len] != '/')
+ continue;
+ if (memcmp(path, next, len+1))
+ continue;
+ prefix = len + 1;
+ break;
+ }
+ }
+ return prefix;
+}
+
+static int match_one(const char *match, const char *name, int namelen)
+{
+ int matchlen;
+
+ /* If the match was just the prefix, we matched */
+ matchlen = strlen(match);
+ if (!matchlen)
+ return 1;
+
+ /*
+ * If we don't match the matchstring exactly,
+ * we need to match by fnmatch
+ */
+ if (strncmp(match, name, matchlen))
+ return !fnmatch(match, name, 0);
+
+ /*
+ * If we did match the string exactly, we still
+ * need to make sure that it happened on a path
+ * component boundary (ie either the last character
+ * of the match was '/', or the next character of
+ * the name was '/' or the terminating NUL.
+ */
+ return match[matchlen-1] == '/' ||
+ name[matchlen] == '/' ||
+ !name[matchlen];
+}
+
+static int match(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
+{
+ int retval;
+ const char *match;
+
+ name += prefix;
+ namelen -= prefix;
+
+ for (retval = 0; (match = *pathspec++) != NULL; seen++) {
+ if (retval & *seen)
+ continue;
+ match += prefix;
+ if (match_one(match, name, namelen)) {
+ retval = 1;
+ *seen = 1;
+ }
+ }
+ return retval;
+}
+
+static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
+{
+ char *seen;
+ int i, specs;
+ struct dir_entry **src, **dst;
+
+ for (specs = 0; pathspec[specs]; specs++)
+ /* nothing */;
+ seen = xmalloc(specs);
+ memset(seen, 0, specs);
+
+ src = dst = dir->entries;
+ i = dir->nr;
+ while (--i >= 0) {
+ struct dir_entry *entry = *src++;
+ if (!match(pathspec, entry->name, entry->len, prefix, seen)) {
+ free(entry);
+ continue;
+ }
+ *dst++ = entry;
+ }
+ dir->nr = dst - dir->entries;
+
+ for (i = 0; i < specs; i++) {
+ struct stat st;
+ const char *match;
+ if (seen[i])
+ continue;
+
+ /* Existing file? We must have ignored it */
+ match = pathspec[i];
+ if (!lstat(match, &st))
+ continue;
+ die("pathspec '%s' did not match any files", match);
+ }
+}
+
+static void fill_directory(struct dir_struct *dir, const char **pathspec)
+{
+ const char *path, *base;
+ int baselen;
+
+ /* Set up the default git porcelain excludes */
+ memset(dir, 0, sizeof(*dir));
+ dir->exclude_per_dir = ".gitignore";
+ path = git_path("info/exclude");
+ if (!access(path, R_OK))
+ add_excludes_from_file(dir, path);
+
+ /*
+ * Calculate common prefix for the pathspec, and
+ * use that to optimize the directory walk
+ */
+ baselen = common_prefix(pathspec);
+ path = ".";
+ base = "";
+ if (baselen) {
+ char *common = xmalloc(baselen + 1);
+ common = xmalloc(baselen + 1);
+ memcpy(common, *pathspec, baselen);
+ common[baselen] = 0;
+ path = base = common;
+ }
+
+ /* Read the directory and prune it */
+ read_directory(dir, path, base, baselen);
+ if (pathspec)
+ prune_directory(dir, pathspec, baselen);
+}
+
+static int add_file_to_index(const char *path, int verbose)
+{
+ int size, namelen;
+ struct stat st;
+ struct cache_entry *ce;
+
+ if (lstat(path, &st))
+ die("%s: unable to stat (%s)", path, strerror(errno));
+
+ if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
+ die("%s: can only add regular files or symbolic links", path);
+
+ namelen = strlen(path);
+ size = cache_entry_size(namelen);
+ ce = xcalloc(1, size);
+ memcpy(ce->name, path, namelen);
+ ce->ce_flags = htons(namelen);
+ fill_stat_cache_info(ce, &st);
+
+ ce->ce_mode = create_ce_mode(st.st_mode);
+ if (!trust_executable_bit) {
+ /* If there is an existing entry, pick the mode bits
+ * from it.
+ */
+ int pos = cache_name_pos(path, namelen);
+ if (pos >= 0)
+ ce->ce_mode = active_cache[pos]->ce_mode;
+ }
+
+ if (index_path(ce->sha1, path, &st, 1))
+ die("unable to index file %s", path);
+ if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
+ die("unable to add %s to index",path);
+ if (verbose)
+ printf("add '%s'\n", path);
+ cache_tree_invalidate_path(active_cache_tree, path);
+ return 0;
+}
+
+static struct cache_file cache_file;
+
+int cmd_add(int argc, const char **argv, char **envp)
+{
+ int i, newfd;
+ int verbose = 0, show_only = 0;
+ const char *prefix = setup_git_directory();
+ const char **pathspec;
+ struct dir_struct dir;
+
+ git_config(git_default_config);
+
+ newfd = hold_index_file_for_update(&cache_file, get_index_file());
+ if (newfd < 0)
+ die("unable to create new cachefile");
+
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (arg[0] != '-')
+ break;
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(arg, "-n")) {
+ show_only = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-v")) {
+ verbose = 1;
+ continue;
+ }
+ die(builtin_add_usage);
+ }
+ git_config(git_default_config);
+ pathspec = get_pathspec(prefix, argv + i);
+
+ fill_directory(&dir, pathspec);
+
+ if (show_only) {
+ const char *sep = "", *eof = "";
+ for (i = 0; i < dir.nr; i++) {
+ printf("%s%s", sep, dir.entries[i]->name);
+ sep = " ";
+ eof = "\n";
+ }
+ fputs(eof, stdout);
+ return 0;
+ }
+
+ for (i = 0; i < dir.nr; i++)
+ add_file_to_index(dir.entries[i]->name, verbose);
+
+ if (active_cache_changed) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_index_file(&cache_file))
+ die("Unable to write new index file");
+ }
+
+ return 0;
+}
diff --git a/builtin.h b/builtin.h
index 97e2464468..ccd0e31b2c 100644
--- a/builtin.h
+++ b/builtin.h
@@ -25,5 +25,6 @@ extern int cmd_count_objects(int argc, const char **argv, char **envp);
extern int cmd_push(int argc, const char **argv, char **envp);
extern int cmd_grep(int argc, const char **argv, char **envp);
+extern int cmd_add(int argc, const char **argv, char **envp);
#endif
diff --git a/dir.c b/dir.c
new file mode 100644
index 0000000000..d40b62e1c1
--- /dev/null
+++ b/dir.c
@@ -0,0 +1,321 @@
+/*
+ * This handles recursive filename detection with exclude
+ * files, index knowledge etc..
+ *
+ * Copyright (C) Linus Torvalds, 2005-2006
+ * Junio Hamano, 2005-2006
+ */
+#include <dirent.h>
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "dir.h"
+
+void add_exclude(const char *string, const char *base,
+ int baselen, struct exclude_list *which)
+{
+ struct exclude *x = xmalloc(sizeof (*x));
+
+ x->pattern = string;
+ x->base = base;
+ x->baselen = baselen;
+ if (which->nr == which->alloc) {
+ which->alloc = alloc_nr(which->alloc);
+ which->excludes = realloc(which->excludes,
+ which->alloc * sizeof(x));
+ }
+ which->excludes[which->nr++] = x;
+}
+
+static int add_excludes_from_file_1(const char *fname,
+ const char *base,
+ int baselen,
+ struct exclude_list *which)
+{
+ int fd, i;
+ long size;
+ char *buf, *entry;
+
+ fd = open(fname, O_RDONLY);
+ if (fd < 0)
+ goto err;
+ size = lseek(fd, 0, SEEK_END);
+ if (size < 0)
+ goto err;
+ lseek(fd, 0, SEEK_SET);
+ if (size == 0) {
+ close(fd);
+ return 0;
+ }
+ buf = xmalloc(size+1);
+ if (read(fd, buf, size) != size)
+ goto err;
+ close(fd);
+
+ buf[size++] = '\n';
+ entry = buf;
+ for (i = 0; i < size; i++) {
+ if (buf[i] == '\n') {
+ if (entry != buf + i && entry[0] != '#') {
+ buf[i - (i && buf[i-1] == '\r')] = 0;
+ add_exclude(entry, base, baselen, which);
+ }
+ entry = buf + i + 1;
+ }
+ }
+ return 0;
+
+ err:
+ if (0 <= fd)
+ close(fd);
+ return -1;
+}
+
+void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+{
+ if (add_excludes_from_file_1(fname, "", 0,
+ &dir->exclude_list[EXC_FILE]) < 0)
+ die("cannot use %s as an exclude file", fname);
+}
+
+static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+{
+ char exclude_file[PATH_MAX];
+ struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+ int current_nr = el->nr;
+
+ if (dir->exclude_per_dir) {
+ memcpy(exclude_file, base, baselen);
+ strcpy(exclude_file + baselen, dir->exclude_per_dir);
+ add_excludes_from_file_1(exclude_file, base, baselen, el);
+ }
+ return current_nr;
+}
+
+static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
+{
+ struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+
+ while (stk < el->nr)
+ free(el->excludes[--el->nr]);
+}
+
+/* Scan the list and let the last match determines the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+static int excluded_1(const char *pathname,
+ int pathlen,
+ struct exclude_list *el)
+{
+ int i;
+
+ if (el->nr) {
+ for (i = el->nr - 1; 0 <= i; i--) {
+ struct exclude *x = el->excludes[i];
+ const char *exclude = x->pattern;
+ int to_exclude = 1;
+
+ if (*exclude == '!') {
+ to_exclude = 0;
+ exclude++;
+ }
+
+ if (!strchr(exclude, '/')) {
+ /* match basename */
+ const char *basename = strrchr(pathname, '/');
+ basename = (basename) ? basename+1 : pathname;
+ if (fnmatch(exclude, basename, 0) == 0)
+ return to_exclude;
+ }
+ else {
+ /* match with FNM_PATHNAME:
+ * exclude has base (baselen long) implicitly
+ * in front of it.
+ */
+ int baselen = x->baselen;
+ if (*exclude == '/')
+ exclude++;
+
+ if (pathlen < baselen ||
+ (baselen && pathname[baselen-1] != '/') ||
+ strncmp(pathname, x->base, baselen))
+ continue;
+
+ if (fnmatch(exclude, pathname+baselen,
+ FNM_PATHNAME) == 0)
+ return to_exclude;
+ }
+ }
+ }
+ return -1; /* undecided */
+}
+
+int excluded(struct dir_struct *dir, const char *pathname)
+{
+ int pathlen = strlen(pathname);
+ int st;
+
+ for (st = EXC_CMDL; st <= EXC_FILE; st++) {
+ switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void add_name(struct dir_struct *dir, const char *pathname, int len)
+{
+ struct dir_entry *ent;
+
+ if (cache_name_pos(pathname, len) >= 0)
+ return;
+
+ if (dir->nr == dir->alloc) {
+ int alloc = alloc_nr(dir->alloc);
+ dir->alloc = alloc;
+ dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
+ }
+ ent = xmalloc(sizeof(*ent) + len + 1);
+ ent->len = len;
+ memcpy(ent->name, pathname, len);
+ ent->name[len] = 0;
+ dir->entries[dir->nr++] = ent;
+}
+
+static int dir_exists(const char *dirname, int len)
+{
+ int pos = cache_name_pos(dirname, len);
+ if (pos >= 0)
+ return 1;
+ pos = -pos-1;
+ if (pos >= active_nr) /* can't */
+ return 0;
+ return !strncmp(active_cache[pos]->name, dirname, len);
+}
+
+/*
+ * Read a directory tree. We currently ignore anything but
+ * directories, regular files and symlinks. That's because git
+ * doesn't handle them at all yet. Maybe that will change some
+ * day.
+ *
+ * Also, we ignore the name ".git" (even if it is not a directory).
+ * That likely will not change.
+ */
+static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
+{
+ DIR *fdir = opendir(path);
+ int contents = 0;
+
+ if (fdir) {
+ int exclude_stk;
+ struct dirent *de;
+ char fullname[MAXPATHLEN + 1];
+ memcpy(fullname, base, baselen);
+
+ exclude_stk = push_exclude_per_directory(dir, base, baselen);
+
+ while ((de = readdir(fdir)) != NULL) {
+ int len;
+
+ if ((de->d_name[0] == '.') &&
+ (de->d_name[1] == 0 ||
+ !strcmp(de->d_name + 1, ".") ||
+ !strcmp(de->d_name + 1, "git")))
+ continue;
+ len = strlen(de->d_name);
+ memcpy(fullname + baselen, de->d_name, len+1);
+ if (excluded(dir, fullname) != dir->show_ignored) {
+ if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
+ continue;
+ }
+ }
+
+ switch (DTYPE(de)) {
+ struct stat st;
+ int subdir, rewind_base;
+ default:
+ continue;
+ case DT_UNKNOWN:
+ if (lstat(fullname, &st))
+ continue;
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ break;
+ if (!S_ISDIR(st.st_mode))
+ continue;
+ /* fallthrough */
+ case DT_DIR:
+ memcpy(fullname + baselen + len, "/", 2);
+ len++;
+ rewind_base = dir->nr;
+ subdir = read_directory_recursive(dir, fullname, fullname,
+ baselen + len);
+ if (dir->show_other_directories &&
+ (subdir || !dir->hide_empty_directories) &&
+ !dir_exists(fullname, baselen + len)) {
+ // Rewind the read subdirectory
+ while (dir->nr > rewind_base)
+ free(dir->entries[--dir->nr]);
+ break;
+ }
+ contents += subdir;
+ continue;
+ case DT_REG:
+ case DT_LNK:
+ break;
+ }
+ add_name(dir, fullname, baselen + len);
+ contents++;
+ }
+ closedir(fdir);
+
+ pop_exclude_per_directory(dir, exclude_stk);
+ }
+
+ return contents;
+}
+
+static int cmp_name(const void *p1, const void *p2)
+{
+ const struct dir_entry *e1 = *(const struct dir_entry **)p1;
+ const struct dir_entry *e2 = *(const struct dir_entry **)p2;
+
+ return cache_name_compare(e1->name, e1->len,
+ e2->name, e2->len);
+}
+
+int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
+{
+ /*
+ * Make sure to do the per-directory exclude for all the
+ * directories leading up to our base.
+ */
+ if (baselen) {
+ if (dir->exclude_per_dir) {
+ char *p, *pp = xmalloc(baselen+1);
+ memcpy(pp, base, baselen+1);
+ p = pp;
+ while (1) {
+ char save = *p;
+ *p = 0;
+ push_exclude_per_directory(dir, pp, p-pp);
+ *p++ = save;
+ if (!save)
+ break;
+ p = strchr(p, '/');
+ if (p)
+ p++;
+ else
+ p = pp + baselen;
+ }
+ free(pp);
+ }
+ }
+
+ read_directory_recursive(dir, path, base, baselen);
+ qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
+ return dir->nr;
+}
diff --git a/dir.h b/dir.h
new file mode 100644
index 0000000000..4f65f57085
--- /dev/null
+++ b/dir.h
@@ -0,0 +1,48 @@
+#ifndef DIR_H
+#define DIR_H
+
+/*
+ * We maintain three exclude pattern lists:
+ * EXC_CMDL lists patterns explicitly given on the command line.
+ * EXC_DIRS lists patterns obtained from per-directory ignore files.
+ * EXC_FILE lists patterns from fallback ignore files.
+ */
+#define EXC_CMDL 0
+#define EXC_DIRS 1
+#define EXC_FILE 2
+
+
+struct dir_entry {
+ int len;
+ char name[FLEX_ARRAY]; /* more */
+};
+
+struct exclude_list {
+ int nr;
+ int alloc;
+ struct exclude {
+ const char *pattern;
+ const char *base;
+ int baselen;
+ } **excludes;
+};
+
+struct dir_struct {
+ int nr, alloc;
+ unsigned int show_ignored:1,
+ show_other_directories:1,
+ hide_empty_directories:1;
+ struct dir_entry **entries;
+
+ /* Exclude info */
+ const char *exclude_per_dir;
+ struct exclude_list exclude_list[3];
+};
+
+extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen);
+extern int excluded(struct dir_struct *, const char *);
+extern void add_excludes_from_file(struct dir_struct *, const char *fname);
+extern void add_exclude(const char *string, const char *base,
+ int baselen, struct exclude_list *which);
+
+#endif
diff --git a/git.c b/git.c
index 84803a62e6..6a470cf140 100644
--- a/git.c
+++ b/git.c
@@ -51,6 +51,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "count-objects", cmd_count_objects },
{ "diff", cmd_diff },
{ "grep", cmd_grep },
+ { "add", cmd_add },
};
int i;
diff --git a/ls-files.c b/ls-files.c
index 4a4af1ca3b..dfe1481a35 100644
--- a/ls-files.c
+++ b/ls-files.c
@@ -5,23 +5,20 @@
*
* Copyright (C) Linus Torvalds, 2005
*/
-#include <dirent.h>
#include <fnmatch.h>
#include "cache.h"
#include "quote.h"
+#include "dir.h"
static int abbrev = 0;
static int show_deleted = 0;
static int show_cached = 0;
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 show_other_directories = 0;
-static int hide_empty_directories = 0;
static int show_valid_bit = 0;
static int line_terminator = '\n';
@@ -38,309 +35,6 @@ static const char *tag_other = "";
static const char *tag_killed = "";
static const char *tag_modified = "";
-static const char *exclude_per_dir = NULL;
-
-/* We maintain three exclude pattern lists:
- * EXC_CMDL lists patterns explicitly given on the command line.
- * EXC_DIRS lists patterns obtained from per-directory ignore files.
- * EXC_FILE lists patterns from fallback ignore files.
- */
-#define EXC_CMDL 0
-#define EXC_DIRS 1
-#define EXC_FILE 2
-static struct exclude_list {
- int nr;
- int alloc;
- struct exclude {
- const char *pattern;
- const char *base;
- int baselen;
- } **excludes;
-} exclude_list[3];
-
-static void add_exclude(const char *string, const char *base,
- int baselen, struct exclude_list *which)
-{
- struct exclude *x = xmalloc(sizeof (*x));
-
- x->pattern = string;
- x->base = base;
- x->baselen = baselen;
- if (which->nr == which->alloc) {
- which->alloc = alloc_nr(which->alloc);
- which->excludes = realloc(which->excludes,
- which->alloc * sizeof(x));
- }
- which->excludes[which->nr++] = x;
-}
-
-static int add_excludes_from_file_1(const char *fname,
- const char *base,
- int baselen,
- struct exclude_list *which)
-{
- int fd, i;
- long size;
- char *buf, *entry;
-
- fd = open(fname, O_RDONLY);
- if (fd < 0)
- goto err;
- size = lseek(fd, 0, SEEK_END);
- if (size < 0)
- goto err;
- lseek(fd, 0, SEEK_SET);
- if (size == 0) {
- close(fd);
- return 0;
- }
- buf = xmalloc(size+1);
- if (read(fd, buf, size) != size)
- goto err;
- close(fd);
-
- buf[size++] = '\n';
- entry = buf;
- for (i = 0; i < size; i++) {
- if (buf[i] == '\n') {
- if (entry != buf + i && entry[0] != '#') {
- buf[i - (i && buf[i-1] == '\r')] = 0;
- add_exclude(entry, base, baselen, which);
- }
- entry = buf + i + 1;
- }
- }
- return 0;
-
- err:
- if (0 <= fd)
- close(fd);
- return -1;
-}
-
-static void add_excludes_from_file(const char *fname)
-{
- if (add_excludes_from_file_1(fname, "", 0,
- &exclude_list[EXC_FILE]) < 0)
- die("cannot use %s as an exclude file", fname);
-}
-
-static int push_exclude_per_directory(const char *base, int baselen)
-{
- char exclude_file[PATH_MAX];
- struct exclude_list *el = &exclude_list[EXC_DIRS];
- int current_nr = el->nr;
-
- if (exclude_per_dir) {
- memcpy(exclude_file, base, baselen);
- strcpy(exclude_file + baselen, exclude_per_dir);
- add_excludes_from_file_1(exclude_file, base, baselen, el);
- }
- return current_nr;
-}
-
-static void pop_exclude_per_directory(int stk)
-{
- struct exclude_list *el = &exclude_list[EXC_DIRS];
-
- while (stk < el->nr)
- free(el->excludes[--el->nr]);
-}
-
-/* Scan the list and let the last match determines the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
- */
-static int excluded_1(const char *pathname,
- int pathlen,
- struct exclude_list *el)
-{
- int i;
-
- if (el->nr) {
- for (i = el->nr - 1; 0 <= i; i--) {
- struct exclude *x = el->excludes[i];
- const char *exclude = x->pattern;
- int to_exclude = 1;
-
- if (*exclude == '!') {
- to_exclude = 0;
- exclude++;
- }
-
- if (!strchr(exclude, '/')) {
- /* match basename */
- const char *basename = strrchr(pathname, '/');
- basename = (basename) ? basename+1 : pathname;
- if (fnmatch(exclude, basename, 0) == 0)
- return to_exclude;
- }
- else {
- /* match with FNM_PATHNAME:
- * exclude has base (baselen long) implicitly
- * in front of it.
- */
- int baselen = x->baselen;
- if (*exclude == '/')
- exclude++;
-
- if (pathlen < baselen ||
- (baselen && pathname[baselen-1] != '/') ||
- strncmp(pathname, x->base, baselen))
- continue;
-
- if (fnmatch(exclude, pathname+baselen,
- FNM_PATHNAME) == 0)
- return to_exclude;
- }
- }
- }
- return -1; /* undecided */
-}
-
-static int excluded(const char *pathname)
-{
- int pathlen = strlen(pathname);
- int st;
-
- for (st = EXC_CMDL; st <= EXC_FILE; st++) {
- switch (excluded_1(pathname, pathlen, &exclude_list[st])) {
- case 0:
- return 0;
- case 1:
- return 1;
- }
- }
- return 0;
-}
-
-struct nond_on_fs {
- int len;
- char name[FLEX_ARRAY]; /* more */
-};
-
-static struct nond_on_fs **dir;
-static int nr_dir;
-static int dir_alloc;
-
-static void add_name(const char *pathname, int len)
-{
- struct nond_on_fs *ent;
-
- if (cache_name_pos(pathname, len) >= 0)
- return;
-
- if (nr_dir == dir_alloc) {
- dir_alloc = alloc_nr(dir_alloc);
- dir = xrealloc(dir, dir_alloc*sizeof(ent));
- }
- ent = xmalloc(sizeof(*ent) + len + 1);
- ent->len = len;
- memcpy(ent->name, pathname, len);
- ent->name[len] = 0;
- dir[nr_dir++] = ent;
-}
-
-static int dir_exists(const char *dirname, int len)
-{
- int pos = cache_name_pos(dirname, len);
- if (pos >= 0)
- return 1;
- pos = -pos-1;
- if (pos >= active_nr) /* can't */
- return 0;
- return !strncmp(active_cache[pos]->name, dirname, len);
-}
-
-/*
- * Read a directory tree. We currently ignore anything but
- * directories, regular files and symlinks. That's because git
- * doesn't handle them at all yet. Maybe that will change some
- * day.
- *
- * Also, we ignore the name ".git" (even if it is not a directory).
- * That likely will not change.
- */
-static int read_directory(const char *path, const char *base, int baselen)
-{
- DIR *fdir = opendir(path);
- int contents = 0;
-
- if (fdir) {
- int exclude_stk;
- struct dirent *de;
- char fullname[MAXPATHLEN + 1];
- memcpy(fullname, base, baselen);
-
- exclude_stk = push_exclude_per_directory(base, baselen);
-
- while ((de = readdir(fdir)) != NULL) {
- int len;
-
- if ((de->d_name[0] == '.') &&
- (de->d_name[1] == 0 ||
- !strcmp(de->d_name + 1, ".") ||
- !strcmp(de->d_name + 1, "git")))
- continue;
- len = strlen(de->d_name);
- memcpy(fullname + baselen, de->d_name, len+1);
- if (excluded(fullname) != show_ignored) {
- if (!show_ignored || DTYPE(de) != DT_DIR) {
- continue;
- }
- }
-
- switch (DTYPE(de)) {
- struct stat st;
- int subdir, rewind_base;
- default:
- continue;
- case DT_UNKNOWN:
- if (lstat(fullname, &st))
- continue;
- if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
- break;
- if (!S_ISDIR(st.st_mode))
- continue;
- /* fallthrough */
- case DT_DIR:
- memcpy(fullname + baselen + len, "/", 2);
- len++;
- rewind_base = nr_dir;
- subdir = read_directory(fullname, fullname,
- baselen + len);
- if (show_other_directories &&
- (subdir || !hide_empty_directories) &&
- !dir_exists(fullname, baselen + len)) {
- // Rewind the read subdirectory
- while (nr_dir > rewind_base)
- free(dir[--nr_dir]);
- break;
- }
- contents += subdir;
- continue;
- case DT_REG:
- case DT_LNK:
- break;
- }
- add_name(fullname, baselen + len);
- contents++;
- }
- closedir(fdir);
-
- pop_exclude_per_directory(exclude_stk);
- }
-
- return contents;
-}
-
-static int cmp_name(const void *p1, const void *p2)
-{
- const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1;
- const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2;
-
- return cache_name_compare(e1->name, e1->len,
- e2->name, e2->len);
-}
/*
* Match a pathspec against a filename. The first "len" characters
@@ -377,7 +71,7 @@ static int match(const char **spec, char *ps_matched,
return 0;
}
-static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
+static void show_dir_entry(const char *tag, struct dir_entry *ent)
{
int len = prefix_len;
int offset = prefix_offset;
@@ -393,14 +87,14 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
putchar(line_terminator);
}
-static void show_other_files(void)
+static void show_other_files(struct dir_struct *dir)
{
int i;
- for (i = 0; i < nr_dir; i++) {
+ for (i = 0; i < dir->nr; i++) {
/* We should not have a matching entry, but we
* may have an unmerged entry for this path.
*/
- struct nond_on_fs *ent = dir[i];
+ struct dir_entry *ent = dir->entries[i];
int pos = cache_name_pos(ent->name, ent->len);
struct cache_entry *ce;
if (0 <= pos)
@@ -416,11 +110,11 @@ static void show_other_files(void)
}
}
-static void show_killed_files(void)
+static void show_killed_files(struct dir_struct *dir)
{
int i;
- for (i = 0; i < nr_dir; i++) {
- struct nond_on_fs *ent = dir[i];
+ for (i = 0; i < dir->nr; i++) {
+ struct dir_entry *ent = dir->entries[i];
char *cp, *sp;
int pos, len, killed = 0;
@@ -461,7 +155,7 @@ static void show_killed_files(void)
}
}
if (killed)
- show_dir_entry(tag_killed, dir[i]);
+ show_dir_entry(tag_killed, dir->entries[i]);
}
}
@@ -512,7 +206,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
}
}
-static void show_files(void)
+static void show_files(struct dir_struct *dir)
{
int i;
@@ -521,39 +215,18 @@ static void show_files(void)
const char *path = ".", *base = "";
int baselen = prefix_len;
- if (baselen) {
+ if (baselen)
path = base = prefix;
- if (exclude_per_dir) {
- char *p, *pp = xmalloc(baselen+1);
- memcpy(pp, prefix, baselen+1);
- p = pp;
- while (1) {
- char save = *p;
- *p = 0;
- push_exclude_per_directory(pp, p-pp);
- *p++ = save;
- if (!save)
- break;
- p = strchr(p, '/');
- if (p)
- p++;
- else
- p = pp + baselen;
- }
- free(pp);
- }
- }
- read_directory(path, base, baselen);
- qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
+ read_directory(dir, path, base, baselen);
if (show_others)
- show_other_files();
+ show_other_files(dir);
if (show_killed)
- show_killed_files();
+ show_killed_files(dir);
}
if (show_cached | show_stage) {
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
- if (excluded(ce->name) != show_ignored)
+ if (excluded(dir, ce->name) != dir->show_ignored)
continue;
if (show_unmerged && !ce_stage(ce))
continue;
@@ -565,7 +238,7 @@ static void show_files(void)
struct cache_entry *ce = active_cache[i];
struct stat st;
int err;
- if (excluded(ce->name) != show_ignored)
+ if (excluded(dir, ce->name) != dir->show_ignored)
continue;
err = lstat(ce->name, &st);
if (show_deleted && err)
@@ -652,7 +325,9 @@ int main(int argc, const char **argv)
{
int i;
int exc_given = 0;
+ struct dir_struct dir;
+ memset(&dir, 0, sizeof(dir));
prefix = setup_git_directory();
if (prefix)
prefix_offset = strlen(prefix);
@@ -697,7 +372,7 @@ int main(int argc, const char **argv)
continue;
}
if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
- show_ignored = 1;
+ dir.show_ignored = 1;
continue;
}
if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
@@ -709,11 +384,11 @@ int main(int argc, const char **argv)
continue;
}
if (!strcmp(arg, "--directory")) {
- show_other_directories = 1;
+ dir.show_other_directories = 1;
continue;
}
if (!strcmp(arg, "--no-empty-directory")) {
- hide_empty_directories = 1;
+ dir.hide_empty_directories = 1;
continue;
}
if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
@@ -726,27 +401,27 @@ int main(int argc, const char **argv)
}
if (!strcmp(arg, "-x") && i+1 < argc) {
exc_given = 1;
- add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
+ add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
continue;
}
if (!strncmp(arg, "--exclude=", 10)) {
exc_given = 1;
- add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]);
+ add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
continue;
}
if (!strcmp(arg, "-X") && i+1 < argc) {
exc_given = 1;
- add_excludes_from_file(argv[++i]);
+ add_excludes_from_file(&dir, argv[++i]);
continue;
}
if (!strncmp(arg, "--exclude-from=", 15)) {
exc_given = 1;
- add_excludes_from_file(arg+15);
+ add_excludes_from_file(&dir, arg+15);
continue;
}
if (!strncmp(arg, "--exclude-per-directory=", 24)) {
exc_given = 1;
- exclude_per_dir = arg + 24;
+ dir.exclude_per_dir = arg + 24;
continue;
}
if (!strcmp(arg, "--full-name")) {
@@ -788,7 +463,7 @@ int main(int argc, const char **argv)
ps_matched = xcalloc(1, num);
}
- if (show_ignored && !exc_given) {
+ if (dir.show_ignored && !exc_given) {
fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
argv[0]);
exit(1);
@@ -802,7 +477,7 @@ int main(int argc, const char **argv)
read_cache();
if (prefix)
prune_cache();
- show_files();
+ show_files(&dir);
if (ps_matched) {
/* We need to make sure all pathspec matched otherwise