summaryrefslogtreecommitdiff
path: root/builtin-update-index.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin-update-index.c')
-rw-r--r--builtin-update-index.c204
1 files changed, 140 insertions, 64 deletions
diff --git a/builtin-update-index.c b/builtin-update-index.c
index 8659800eec..8f9899178b 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -9,6 +9,7 @@
#include "cache-tree.h"
#include "tree-walk.h"
#include "builtin.h"
+#include "refs.h"
/*
* Default to not allowing changes to the list of files. The
@@ -60,78 +61,153 @@ static int mark_valid(const char *path)
return -1;
}
-static int add_file_to_cache(const char *path)
+static int remove_one_path(const char *path)
{
- int size, namelen, option, status;
- struct cache_entry *ce;
- struct stat st;
-
- status = lstat(path, &st);
-
- /* We probably want to do this in remove_file_from_cache() and
- * add_cache_entry() instead...
- */
- cache_tree_invalidate_path(active_cache_tree, path);
+ if (!allow_remove)
+ return error("%s: does not exist and --remove not passed", path);
+ if (remove_file_from_cache(path))
+ return error("%s: cannot remove from the index", path);
+ return 0;
+}
- if (status < 0 || S_ISDIR(st.st_mode)) {
- /* When we used to have "path" and now we want to add
- * "path/file", we need a way to remove "path" before
- * being able to add "path/file". However,
- * "git-update-index --remove path" would not work.
- * --force-remove can be used but this is more user
- * friendly, especially since we can do the opposite
- * case just fine without --force-remove.
- */
- if (status == 0 || (errno == ENOENT || errno == ENOTDIR)) {
- if (allow_remove) {
- if (remove_file_from_cache(path))
- return error("%s: cannot remove from the index",
- path);
- else
- return 0;
- } else if (status < 0) {
- return error("%s: does not exist and --remove not passed",
- path);
- }
- }
- if (0 == status)
- return error("%s: is a directory - add files inside instead",
- path);
- else
- return error("lstat(\"%s\"): %s", path,
- strerror(errno));
- }
+/*
+ * Handle a path that couldn't be lstat'ed. It's either:
+ * - missing file (ENOENT or ENOTDIR). That's ok if we're
+ * supposed to be removing it and the removal actually
+ * succeeds.
+ * - permission error. That's never ok.
+ */
+static int process_lstat_error(const char *path, int err)
+{
+ if (err == ENOENT || err == ENOTDIR)
+ return remove_one_path(path);
+ return error("lstat(\"%s\"): %s", path, strerror(errno));
+}
- 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);
-
- if (trust_executable_bit && has_symlinks)
- ce->ce_mode = create_ce_mode(st.st_mode);
- else {
- /* If there is an existing entry, pick the mode bits and type
- * from it, otherwise assume unexecutable regular file.
- */
- struct cache_entry *ent;
- int pos = cache_name_pos(path, namelen);
+static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
+{
+ int option, size = cache_entry_size(len);
+ struct cache_entry *ce = xcalloc(1, size);
- ent = (0 <= pos) ? active_cache[pos] : NULL;
- ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
- }
+ memcpy(ce->name, path, len);
+ ce->ce_flags = htons(len);
+ fill_stat_cache_info(ce, st);
+ ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
- if (index_path(ce->sha1, path, &st, !info_only))
+ if (index_path(ce->sha1, path, st, !info_only))
return -1;
option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
if (add_cache_entry(ce, option))
- return error("%s: cannot add to the index - missing --add option?",
- path);
+ return error("%s: cannot add to the index - missing --add option?", path);
return 0;
}
+/*
+ * Handle a path that was a directory. Four cases:
+ *
+ * - it's already a gitlink in the index, and we keep it that
+ * way, and update it if we can (if we cannot find the HEAD,
+ * we're going to keep it unchanged in the index!)
+ *
+ * - it's a *file* in the index, in which case it should be
+ * removed as a file if removal is allowed, since it doesn't
+ * exist as such any more. If removal isn't allowed, it's
+ * an error.
+ *
+ * (NOTE! This is old and arguably fairly strange behaviour.
+ * We might want to make this an error unconditionally, and
+ * use "--force-remove" if you actually want to force removal).
+ *
+ * - it used to exist as a subdirectory (ie multiple files with
+ * this particular prefix) in the index, in which case it's wrong
+ * to try to update it as a directory.
+ *
+ * - it doesn't exist at all in the index, but it is a valid
+ * git directory, and it should be *added* as a gitlink.
+ */
+static int process_directory(const char *path, int len, struct stat *st)
+{
+ unsigned char sha1[20];
+ int pos = cache_name_pos(path, len);
+
+ /* Exact match: file or existing gitlink */
+ if (pos >= 0) {
+ struct cache_entry *ce = active_cache[pos];
+ if (S_ISDIRLNK(ntohl(ce->ce_mode))) {
+
+ /* Do nothing to the index if there is no HEAD! */
+ if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
+ return 0;
+
+ return add_one_path(ce, path, len, st);
+ }
+ /* Should this be an unconditional error? */
+ return remove_one_path(path);
+ }
+
+ /* Inexact match: is there perhaps a subdirectory match? */
+ pos = -pos-1;
+ while (pos < active_nr) {
+ struct cache_entry *ce = active_cache[pos++];
+
+ if (strncmp(ce->name, path, len))
+ break;
+ if (ce->name[len] > '/')
+ break;
+ if (ce->name[len] < '/')
+ continue;
+
+ /* Subdirectory match - error out */
+ return error("%s: is a directory - add individual files instead", path);
+ }
+
+ /* No match - should we add it as a gitlink? */
+ if (!resolve_gitlink_ref(path, "HEAD", sha1))
+ return add_one_path(NULL, path, len, st);
+
+ /* Error out. */
+ return error("%s: is a directory - add files inside instead", path);
+}
+
+/*
+ * Process a regular file
+ */
+static int process_file(const char *path, int len, struct stat *st)
+{
+ int pos = cache_name_pos(path, len);
+ struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
+
+ if (ce && S_ISDIRLNK(ntohl(ce->ce_mode)))
+ return error("%s is already a gitlink, not replacing", path);
+
+ return add_one_path(ce, path, len, st);
+}
+
+static int process_path(const char *path)
+{
+ int len;
+ struct stat st;
+
+ /* We probably want to do this in remove_file_from_cache() and
+ * add_cache_entry() instead...
+ */
+ cache_tree_invalidate_path(active_cache_tree, path);
+
+ /*
+ * First things first: get the stat information, to decide
+ * what to do about the pathname!
+ */
+ if (lstat(path, &st) < 0)
+ return process_lstat_error(path, errno);
+
+ len = strlen(path);
+ if (S_ISDIR(st.st_mode))
+ return process_directory(path, len, &st);
+
+ return process_file(path, len, &st);
+}
+
static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
const char *path, int stage)
{
@@ -210,8 +286,8 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
report("remove '%s'", path);
goto free_return;
}
- if (add_file_to_cache(p))
- die("Unable to process file %s", path);
+ if (process_path(p))
+ die("Unable to process path %s", path);
report("add '%s'", path);
free_return:
if (p < path || p > path + strlen(path))
@@ -499,7 +575,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
/* We can't free this memory, it becomes part of a linked list parsed atexit() */
lock_file = xcalloc(1, sizeof(struct lock_file));
- newfd = hold_lock_file_for_update(lock_file, get_index_file(), 0);
+ newfd = hold_locked_index(lock_file, 0);
if (newfd < 0)
lock_error = errno;
@@ -665,7 +741,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
get_index_file(), strerror(lock_error));
}
if (write_cache(newfd, active_cache, active_nr) ||
- close(newfd) || commit_lock_file(lock_file))
+ close(newfd) || commit_locked_index(lock_file))
die("Unable to write new index file");
}