Add git-update-cache --replace option.
authorJunio C Hamano <junkio@cox.net>
Sun, 8 May 2005 04:55:21 +0000 (21:55 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 8 May 2005 04:55:21 +0000 (21:55 -0700)
When "path" exists as a file or a symlink in the index, an
attempt to add "path/file" is refused because it results in file
vs directory conflict.  Similarly when "path/file1",
"path/file2", etc. exist, an attempt to add "path" as a file or
a symlink is refused.  With git-update-cache --replace, these
existing entries that conflict with the entry being added are
automatically removed from the cache, with warning messages.

Signed-off-by: Junio C Hamano <junkio@cox.net>
Documentation/core-git.txt
cache.h
read-cache.c
tree.c
update-cache.c

index 014b979..5e702fd 100644 (file)
@@ -1098,7 +1098,7 @@ returns the name of the temporary file in the following format:
 ################################################################
 git-update-cache
        git-update-cache
-            [--add] [--remove] [--refresh]
+            [--add] [--remove] [--refresh] [--replace]
             [--ignore-missing]
             [--force-remove <file>]
             [--cacheinfo <mode> <object> <file>]*
@@ -1135,6 +1135,14 @@ using the various options:
        Remove the file from the index even when the working directory
        still has such a file.
 
+--replace
+       By default, when a file "path" exists in the index,
+       git-update-cache refuses an attempt to add "path/file".
+       Similarly if a file "path/file" exists, a file "path"
+       cannot be added.  With --replace flag, existing entries
+       that conflicts with the entry being added are
+       automatically removed with warning messages.
+
 --
        Do not interpret any more arguments as options.
 
diff --git a/cache.h b/cache.h
index 314ee0d..7a656c7 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -116,7 +116,9 @@ unsigned int active_nr, active_alloc, active_cache_changed;
 extern int read_cache(void);
 extern int write_cache(int newfd, struct cache_entry **cache, int entries);
 extern int cache_name_pos(const char *name, int namelen);
-extern int add_cache_entry(struct cache_entry *ce, int ok_to_add);
+#define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
+#define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
+extern int add_cache_entry(struct cache_entry *ce, int option);
 extern int remove_entry_at(int pos);
 extern int remove_file_from_cache(char *path);
 extern int same_name(struct cache_entry *a, struct cache_entry *b);
index 327888b..47aa2d4 100644 (file)
@@ -127,10 +127,15 @@ int same_name(struct cache_entry *a, struct cache_entry *b)
  * is being added, or we already have path and path/file is being
  * added.  Either one would result in a nonsense tree that has path
  * twice when git-write-tree tries to write it out.  Prevent it.
+ * 
+ * If ok-to-replace is specified, we remove the conflicting entries
+ * from the cache so the caller should recompute the insert position.
+ * When this happens, we return non-zero.
  */
-static int check_file_directory_conflict(const struct cache_entry *ce)
+static int check_file_directory_conflict(const struct cache_entry *ce,
+                                        int ok_to_replace)
 {
-       int pos;
+       int pos, replaced = 0;
        const char *path = ce->name;
        int namelen = strlen(path);
        int stage = ce_stage(ce);
@@ -157,8 +162,13 @@ static int check_file_directory_conflict(const struct cache_entry *ce)
                         * and we are trying to make it a directory.  This is
                         * bad.
                         */
-                       free(pathbuf);
-                       return -1;
+                       if (!ok_to_replace) {
+                               free(pathbuf);
+                               return -1;
+                       }
+                       fprintf(stderr, "removing file '%s' to replace it with a directory to create '%s'.\n", pathbuf, path);
+                       remove_entry_at(pos);
+                       replaced = 1;
                }
                *ep = '/';  /* then restore it and go downwards */
                cp = ep + 1;
@@ -185,30 +195,40 @@ static int check_file_directory_conflict(const struct cache_entry *ce)
         *
         *        1 path
         * pos->  3 path
-        *        2 path/file
-        *        3 path/file
+        *        2 path/file1
+        *        3 path/file1
+        *        2 path/file2
+        *        2 patho
         *
         * We need to examine pos, ignore it because it is at different
         * stage, examine next to find the path/file at stage 2, and
-        * complain.
+        * complain.  We need to do this until we are not the leading
+        * path of an existing entry anymore.
         */
 
        while (pos < active_nr) {
                struct cache_entry *other = active_cache[pos];
                if (strncmp(other->name, path, namelen))
                        break; /* it is not our "subdirectory" anymore */
-               if ((ce_stage(other) == stage) && other->name[namelen] == '/')
-                       return -1;
+               if ((ce_stage(other) == stage) &&
+                   other->name[namelen] == '/') {
+                       if (!ok_to_replace)
+                               return -1;
+                       fprintf(stderr, "removing file '%s' under '%s' to be replaced with a file\n", other->name, path);
+                       remove_entry_at(pos);
+                       replaced = 1;
+                       continue; /* cycle without updating pos */
+               }
                pos++;
        }
-
-       return 0;
+       return replaced;
 }
 
-int add_cache_entry(struct cache_entry *ce, int ok_to_add)
+int add_cache_entry(struct cache_entry *ce, int option)
 {
        int pos;
-
+       int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
+       int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
        pos = cache_name_pos(ce->name, htons(ce->ce_flags));
 
        /* existing match? Just replace it */
@@ -234,8 +254,12 @@ int add_cache_entry(struct cache_entry *ce, int ok_to_add)
        if (!ok_to_add)
                return -1;
 
-       if (check_file_directory_conflict(ce))
-               return -1;
+       if (check_file_directory_conflict(ce, ok_to_replace)) {
+               if (!ok_to_replace)
+                       return -1;
+               pos = cache_name_pos(ce->name, htons(ce->ce_flags));
+               pos = -pos-1;
+       }
 
        /* Make sure the array is big enough .. */
        if (active_nr == active_alloc) {
diff --git a/tree.c b/tree.c
index d9777bf..a978c53 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -18,7 +18,7 @@ static int read_one_entry(unsigned char *sha1, const char *base, int baselen, co
        memcpy(ce->name, base, baselen);
        memcpy(ce->name + baselen, pathname, len+1);
        memcpy(ce->sha1, sha1, 20);
-       return add_cache_entry(ce, 1);
+       return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
 }
 
 static int read_tree_recursive(void *buffer, unsigned long size,
index 735d199..1e4e62c 100644 (file)
@@ -13,7 +13,7 @@
  * like "update-cache *" and suddenly having all the object
  * files be revision controlled.
  */
-static int allow_add = 0, allow_remove = 0, not_new = 0;
+static int allow_add = 0, allow_remove = 0, allow_replace = 0, not_new = 0;
 
 /* Three functions to allow overloaded pointer return; see linux/err.h */
 static inline void *ERR_PTR(long error)
@@ -53,7 +53,7 @@ static void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
 
 static int add_file_to_cache(char *path)
 {
-       int size, namelen;
+       int size, namelen, option;
        struct cache_entry *ce;
        struct stat st;
        int fd;
@@ -95,7 +95,9 @@ static int add_file_to_cache(char *path)
        default:
                return -1;
        }
-       return add_cache_entry(ce, allow_add);
+       option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+       option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+       return add_cache_entry(ce, option);
 }
 
 static int match_data(int fd, void *buffer, unsigned long size)
@@ -273,7 +275,7 @@ inside:
 
 static int add_cacheinfo(char *arg1, char *arg2, char *arg3)
 {
-       int size, len;
+       int size, len, option;
        unsigned int mode;
        unsigned char sha1[20];
        struct cache_entry *ce;
@@ -294,7 +296,9 @@ static int add_cacheinfo(char *arg1, char *arg2, char *arg3)
        memcpy(ce->name, arg3, len);
        ce->ce_flags = htons(len);
        ce->ce_mode = create_ce_mode(mode);
-       return add_cache_entry(ce, allow_add);
+       option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+       option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+       return add_cache_entry(ce, option);
 }
 
 static const char *lockfile_name = NULL;
@@ -343,6 +347,10 @@ int main(int argc, char **argv)
                                allow_add = 1;
                                continue;
                        }
+                       if (!strcmp(path, "--replace")) {
+                               allow_replace = 1;
+                               continue;
+                       }
                        if (!strcmp(path, "--remove")) {
                                allow_remove = 1;
                                continue;