summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builtin-apply.c2
-rw-r--r--cache.h2
-rw-r--r--diff-lib.c10
-rw-r--r--symlinks.c82
-rw-r--r--unpack-trees.c12
5 files changed, 61 insertions, 47 deletions
diff --git a/builtin-apply.c b/builtin-apply.c
index caa3f2aa0c..1103625a4a 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -2247,7 +2247,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists)
* In such a case, path "new_name" does not exist as
* far as git is concerned.
*/
- if (has_symlink_leading_path(new_name, NULL))
+ if (has_symlink_leading_path(strlen(new_name), new_name))
return 0;
return error("%s: already exists in working directory", new_name);
diff --git a/cache.h b/cache.h
index 4803b032ec..0d9d9e2577 100644
--- a/cache.h
+++ b/cache.h
@@ -598,7 +598,7 @@ struct checkout {
};
extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
-extern int has_symlink_leading_path(const char *name, char *last_symlink);
+extern int has_symlink_leading_path(int len, const char *name);
extern struct alternate_object_database {
struct alternate_object_database *next;
diff --git a/diff-lib.c b/diff-lib.c
index c5894c7c5a..fe2ccec7e6 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -347,14 +347,14 @@ int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
* exists for ce that is a submodule -- it is a submodule that is not
* checked out). Return negative for an error.
*/
-static int check_removed(const struct cache_entry *ce, struct stat *st, char *symcache)
+static int check_removed(const struct cache_entry *ce, struct stat *st)
{
if (lstat(ce->name, st) < 0) {
if (errno != ENOENT && errno != ENOTDIR)
return -1;
return 1;
}
- if (has_symlink_leading_path(ce->name, symcache))
+ if (has_symlink_leading_path(ce_namelen(ce), ce->name))
return 1;
if (S_ISDIR(st->st_mode)) {
unsigned char sub[20];
@@ -421,7 +421,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
memset(&(dpath->parent[0]), 0,
sizeof(struct combine_diff_parent)*5);
- changed = check_removed(ce, &st, symcache);
+ changed = check_removed(ce, &st);
if (!changed)
dpath->mode = ce_mode_from_stat(ce, st.st_mode);
else {
@@ -485,7 +485,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
if (ce_uptodate(ce))
continue;
- changed = check_removed(ce, &st, symcache);
+ changed = check_removed(ce, &st);
if (changed) {
if (changed < 0) {
perror(ce->name);
@@ -546,7 +546,7 @@ static int get_stat_data(struct cache_entry *ce,
if (!cached) {
int changed;
struct stat st;
- changed = check_removed(ce, &st, cbdata->symcache);
+ changed = check_removed(ce, &st);
if (changed < 0)
return -1;
else if (changed) {
diff --git a/symlinks.c b/symlinks.c
index be9ace6c04..5a5e781a15 100644
--- a/symlinks.c
+++ b/symlinks.c
@@ -1,48 +1,64 @@
#include "cache.h"
-int has_symlink_leading_path(const char *name, char *last_symlink)
-{
+struct pathname {
+ int len;
char path[PATH_MAX];
- const char *sp, *ep;
- char *dp;
-
- sp = name;
- dp = path;
-
- if (last_symlink && *last_symlink) {
- size_t last_len = strlen(last_symlink);
- size_t len = strlen(name);
- if (last_len < len &&
- !strncmp(name, last_symlink, last_len) &&
- name[last_len] == '/')
- return 1;
- *last_symlink = '\0';
+};
+
+/* Return matching pathname prefix length, or zero if not matching */
+static inline int match_pathname(int len, const char *name, struct pathname *match)
+{
+ int match_len = match->len;
+ return (len > match_len &&
+ name[match_len] == '/' &&
+ !memcmp(name, match->path, match_len)) ? match_len : 0;
+}
+
+static inline void set_pathname(int len, const char *name, struct pathname *match)
+{
+ if (len < PATH_MAX) {
+ match->len = len;
+ memcpy(match->path, name, len);
+ match->path[len] = 0;
}
+}
+
+int has_symlink_leading_path(int len, const char *name)
+{
+ static struct pathname link, nonlink;
+ char path[PATH_MAX];
+ struct stat st;
+ char *sp;
+ int known_dir;
- while (1) {
- size_t len;
- struct stat st;
+ /*
+ * See if the last known symlink cache matches.
+ */
+ if (match_pathname(len, name, &link))
+ return 1;
- ep = strchr(sp, '/');
- if (!ep)
- break;
- len = ep - sp;
- if (PATH_MAX <= dp + len - path + 2)
- return 0; /* new name is longer than that??? */
- memcpy(dp, sp, len);
- dp[len] = 0;
+ /*
+ * Get rid of the last known directory part
+ */
+ known_dir = match_pathname(len, name, &nonlink);
+
+ while ((sp = strchr(name + known_dir + 1, '/')) != NULL) {
+ int thislen = sp - name ;
+ memcpy(path, name, thislen);
+ path[thislen] = 0;
if (lstat(path, &st))
return 0;
+ if (S_ISDIR(st.st_mode)) {
+ set_pathname(thislen, path, &nonlink);
+ known_dir = thislen;
+ continue;
+ }
if (S_ISLNK(st.st_mode)) {
- if (last_symlink)
- strcpy(last_symlink, path);
+ set_pathname(thislen, path, &link);
return 1;
}
-
- dp[len++] = '/';
- dp = dp + len;
- sp = ep + 1;
+ break;
}
return 0;
}
diff --git a/unpack-trees.c b/unpack-trees.c
index feae846226..1ab28fda45 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -26,11 +26,12 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
* directories, in case this unlink is the removal of the
* last entry in the directory -- empty directories are removed.
*/
-static void unlink_entry(char *name, char *last_symlink)
+static void unlink_entry(struct cache_entry *ce)
{
char *cp, *prev;
+ char *name = ce->name;
- if (has_symlink_leading_path(name, last_symlink))
+ if (has_symlink_leading_path(ce_namelen(ce), ce->name))
return;
if (unlink(name))
return;
@@ -58,7 +59,6 @@ static int check_updates(struct unpack_trees_options *o)
{
unsigned cnt = 0, total = 0;
struct progress *progress = NULL;
- char last_symlink[PATH_MAX];
struct index_state *index = &o->result;
int i;
int errs = 0;
@@ -75,14 +75,13 @@ static int check_updates(struct unpack_trees_options *o)
cnt = 0;
}
- *last_symlink = '\0';
for (i = 0; i < index->cache_nr; i++) {
struct cache_entry *ce = index->cache[i];
if (ce->ce_flags & CE_REMOVE) {
display_progress(progress, ++cnt);
if (o->update)
- unlink_entry(ce->name, last_symlink);
+ unlink_entry(ce);
remove_index_entry_at(&o->result, i);
i--;
continue;
@@ -97,7 +96,6 @@ static int check_updates(struct unpack_trees_options *o)
ce->ce_flags &= ~CE_UPDATE;
if (o->update) {
errs |= checkout_entry(ce, &state, NULL);
- *last_symlink = '\0';
}
}
}
@@ -553,7 +551,7 @@ static int verify_absent(struct cache_entry *ce, const char *action,
if (o->index_only || o->reset || !o->update)
return 0;
- if (has_symlink_leading_path(ce->name, NULL))
+ if (has_symlink_leading_path(ce_namelen(ce), ce->name))
return 0;
if (!lstat(ce->name, &st)) {