Merge branch 'sd/branch-copy'
[gitweb.git] / config.c
index acebc85d81372f15d3d490d84dc751f59bde26c4..4831c1273542706437139ccc379afb38e7ca3d3b 100644 (file)
--- a/config.c
+++ b/config.c
@@ -929,7 +929,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)
        return ret;
 }
 
-int git_parse_maybe_bool(const char *value)
+static int git_parse_maybe_bool_text(const char *value)
 {
        if (!value)
                return 1;
@@ -946,9 +946,9 @@ int git_parse_maybe_bool(const char *value)
        return -1;
 }
 
-int git_config_maybe_bool(const char *name, const char *value)
+int git_parse_maybe_bool(const char *value)
 {
-       int v = git_parse_maybe_bool(value);
+       int v = git_parse_maybe_bool_text(value);
        if (0 <= v)
                return v;
        if (git_parse_int(value, &v))
@@ -958,7 +958,7 @@ int git_config_maybe_bool(const char *name, const char *value)
 
 int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
 {
-       int v = git_parse_maybe_bool(value);
+       int v = git_parse_maybe_bool_text(value);
        if (0 <= v) {
                *is_bool = 1;
                return v;
@@ -1464,9 +1464,9 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ
        return do_config_from(&top, fn, data);
 }
 
-int git_config_from_blob_sha1(config_fn_t fn,
+int git_config_from_blob_oid(config_fn_t fn,
                              const char *name,
-                             const unsigned char *sha1,
+                             const struct object_id *oid,
                              void *data)
 {
        enum object_type type;
@@ -1474,7 +1474,7 @@ int git_config_from_blob_sha1(config_fn_t fn,
        unsigned long size;
        int ret;
 
-       buf = read_sha1_file(sha1, &type, &size);
+       buf = read_sha1_file(oid->hash, &type, &size);
        if (!buf)
                return error("unable to load config blob object '%s'", name);
        if (type != OBJ_BLOB) {
@@ -1492,11 +1492,11 @@ static int git_config_from_blob_ref(config_fn_t fn,
                                    const char *name,
                                    void *data)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
 
-       if (get_sha1(name, sha1) < 0)
+       if (get_oid(name, &oid) < 0)
                return error("unable to resolve config blob '%s'", name);
-       return git_config_from_blob_sha1(fn, name, sha1, data);
+       return git_config_from_blob_oid(fn, name, &oid, data);
 }
 
 const char *git_etc_gitconfig(void)
@@ -1719,17 +1719,19 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
 }
 
 static int config_set_element_cmp(const void *unused_cmp_data,
-                                 const struct config_set_element *e1,
-                                 const struct config_set_element *e2,
+                                 const void *entry,
+                                 const void *entry_or_key,
                                  const void *unused_keydata)
 {
+       const struct config_set_element *e1 = entry;
+       const struct config_set_element *e2 = entry_or_key;
+
        return strcmp(e1->key, e2->key);
 }
 
 void git_configset_init(struct config_set *cs)
 {
-       hashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp,
-                    NULL, 0);
+       hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
        cs->hash_initialized = 1;
        cs->list.nr = 0;
        cs->list.alloc = 0;
@@ -1850,7 +1852,7 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de
 {
        const char *value;
        if (!git_configset_get_value(cs, key, &value)) {
-               *dest = git_config_maybe_bool(key, value);
+               *dest = git_parse_maybe_bool(value);
                if (*dest == -1)
                        return -1;
                return 0;
@@ -2057,6 +2059,23 @@ int git_config_get_pathname(const char *key, const char **dest)
        return repo_config_get_pathname(the_repository, key, dest);
 }
 
+/*
+ * Note: This function exists solely to maintain backward compatibility with
+ * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should
+ * NOT be used anywhere else.
+ *
+ * Runs the provided config function on the '.gitmodules' file found in the
+ * working directory.
+ */
+void config_from_gitmodules(config_fn_t fn, void *data)
+{
+       if (the_repository->worktree) {
+               char *file = repo_worktree_path(the_repository, GITMODULES_FILE);
+               git_config_from_file(fn, file, data);
+               free(file);
+       }
+}
+
 int git_config_get_expiry(const char *key, const char **output)
 {
        int ret = git_config_get_string_const(key, output);
@@ -2070,6 +2089,28 @@ int git_config_get_expiry(const char *key, const char **output)
        return ret;
 }
 
+int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now)
+{
+       char *expiry_string;
+       intmax_t days;
+       timestamp_t when;
+
+       if (git_config_get_string(key, &expiry_string))
+               return 1; /* no such thing */
+
+       if (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {
+               const int scale = 86400;
+               *expiry = now - days * scale;
+               return 0;
+       }
+
+       if (!parse_expiry_date(expiry_string, &when)) {
+               *expiry = when;
+               return 0;
+       }
+       return -1; /* thing exists but cannot be parsed */
+}
+
 int git_config_get_untracked_cache(void)
 {
        int val = -1;
@@ -2159,7 +2200,7 @@ static struct {
        size_t *offset;
        unsigned int offset_alloc;
        enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
-       int seen;
+       unsigned int seen;
 } store;
 
 static int matches(const char *key, const char *value)
@@ -2251,10 +2292,10 @@ static int write_error(const char *filename)
        return 4;
 }
 
-static int store_write_section(int fd, const char *key)
+static struct strbuf store_create_section(const char *key)
 {
        const char *dot;
-       int i, success;
+       int i;
        struct strbuf sb = STRBUF_INIT;
 
        dot = memchr(key, '.', store.baselen);
@@ -2270,15 +2311,24 @@ static int store_write_section(int fd, const char *key)
                strbuf_addf(&sb, "[%.*s]\n", store.baselen, key);
        }
 
-       success = write_in_full(fd, sb.buf, sb.len) == sb.len;
+       return sb;
+}
+
+static ssize_t write_section(int fd, const char *key)
+{
+       struct strbuf sb = store_create_section(key);
+       ssize_t ret;
+
+       ret = write_in_full(fd, sb.buf, sb.len) == sb.len;
        strbuf_release(&sb);
 
-       return success;
+       return ret;
 }
 
-static int store_write_pair(int fd, const char *key, const char *value)
+static ssize_t write_pair(int fd, const char *key, const char *value)
 {
-       int i, success;
+       int i;
+       ssize_t ret;
        int length = strlen(key + store.baselen + 1);
        const char *quote = "";
        struct strbuf sb = STRBUF_INIT;
@@ -2312,16 +2362,17 @@ static int store_write_pair(int fd, const char *key, const char *value)
                case '"':
                case '\\':
                        strbuf_addch(&sb, '\\');
+                       /* fallthrough */
                default:
                        strbuf_addch(&sb, value[i]);
                        break;
                }
        strbuf_addf(&sb, "%s\n", quote);
 
-       success = write_in_full(fd, sb.buf, sb.len) == sb.len;
+       ret = write_in_full(fd, sb.buf, sb.len);
        strbuf_release(&sb);
 
-       return success;
+       return ret;
 }
 
 static ssize_t find_beginning_of_line(const char *contents, size_t size,
@@ -2404,7 +2455,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
 {
        int fd = -1, in_fd = -1;
        int ret;
-       struct lock_file *lock = NULL;
+       struct lock_file lock = LOCK_INIT;
        char *filename_buf = NULL;
        char *contents = NULL;
        size_t contents_sz;
@@ -2423,8 +2474,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
         * The lock serves a purpose in addition to locking: the new
         * contents of .git/config will be written into it.
         */
-       lock = xcalloc(1, sizeof(struct lock_file));
-       fd = hold_lock_file_for_update(lock, config_filename, 0);
+       fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (fd < 0) {
                error_errno("could not lock config file %s", config_filename);
                free(store.key);
@@ -2451,8 +2501,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                }
 
                store.key = (char *)key;
-               if (!store_write_section(fd, key) ||
-                   !store_write_pair(fd, key, value))
+               if (write_section(fd, key) < 0 ||
+                   write_pair(fd, key, value) < 0)
                        goto write_err_out;
        } else {
                struct stat st;
@@ -2537,8 +2587,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                close(in_fd);
                in_fd = -1;
 
-               if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
-                       error_errno("chmod on %s failed", get_lock_file_path(lock));
+               if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) {
+                       error_errno("chmod on %s failed", get_lock_file_path(&lock));
                        ret = CONFIG_NO_WRITE;
                        goto out_free;
                }
@@ -2562,11 +2612,10 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                        /* write the first part of the config */
                        if (copy_end > copy_begin) {
                                if (write_in_full(fd, contents + copy_begin,
-                                                 copy_end - copy_begin) <
-                                   copy_end - copy_begin)
+                                                 copy_end - copy_begin) < 0)
                                        goto write_err_out;
                                if (new_line &&
-                                   write_str_in_full(fd, "\n") != 1)
+                                   write_str_in_full(fd, "\n") < 0)
                                        goto write_err_out;
                        }
                        copy_begin = store.offset[i];
@@ -2575,46 +2624,36 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                /* write the pair (value == NULL means unset) */
                if (value != NULL) {
                        if (store.state == START) {
-                               if (!store_write_section(fd, key))
+                               if (write_section(fd, key) < 0)
                                        goto write_err_out;
                        }
-                       if (!store_write_pair(fd, key, value))
+                       if (write_pair(fd, key, value) < 0)
                                goto write_err_out;
                }
 
                /* write the rest of the config */
                if (copy_begin < contents_sz)
                        if (write_in_full(fd, contents + copy_begin,
-                                         contents_sz - copy_begin) <
-                           contents_sz - copy_begin)
+                                         contents_sz - copy_begin) < 0)
                                goto write_err_out;
 
                munmap(contents, contents_sz);
                contents = NULL;
        }
 
-       if (commit_lock_file(lock) < 0) {
+       if (commit_lock_file(&lock) < 0) {
                error_errno("could not write config file %s", config_filename);
                ret = CONFIG_NO_WRITE;
-               lock = NULL;
                goto out_free;
        }
 
-       /*
-        * lock is committed, so don't try to roll it back below.
-        * NOTE: Since lockfile.c keeps a linked list of all created
-        * lock_file structures, it isn't safe to free(lock).  It's
-        * better to just leave it hanging around.
-        */
-       lock = NULL;
        ret = 0;
 
        /* Invalidate the config cache */
        git_config_clear();
 
 out_free:
-       if (lock)
-               rollback_lock_file(lock);
+       rollback_lock_file(&lock);
        free(filename_buf);
        if (contents)
                munmap(contents, contents_sz);
@@ -2623,7 +2662,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
        return ret;
 
 write_err_out:
-       ret = write_error(get_lock_file_path(lock));
+       ret = write_error(get_lock_file_path(&lock));
        goto out_free;
 
 }
@@ -2711,8 +2750,8 @@ static int section_name_is_ok(const char *name)
 }
 
 /* if new_name == NULL, the section is removed instead */
-int git_config_rename_section_in_file(const char *config_filename,
-                                     const char *old_name, const char *new_name)
+static int git_config_copy_or_rename_section_in_file(const char *config_filename,
+                                     const char *old_name, const char *new_name, int copy)
 {
        int ret = 0, remove = 0;
        char *filename_buf = NULL;
@@ -2721,6 +2760,7 @@ int git_config_rename_section_in_file(const char *config_filename,
        char buf[1024];
        FILE *config_file = NULL;
        struct stat st;
+       struct strbuf copystr = STRBUF_INIT;
 
        if (new_name && !section_name_is_ok(new_name)) {
                ret = error("invalid section name: %s", new_name);
@@ -2759,12 +2799,30 @@ int git_config_rename_section_in_file(const char *config_filename,
        while (fgets(buf, sizeof(buf), config_file)) {
                int i;
                int length;
+               int is_section = 0;
                char *output = buf;
                for (i = 0; buf[i] && isspace(buf[i]); i++)
                        ; /* do nothing */
                if (buf[i] == '[') {
                        /* it's a section */
-                       int offset = section_name_match(&buf[i], old_name);
+                       int offset;
+                       is_section = 1;
+
+                       /*
+                        * When encountering a new section under -c we
+                        * need to flush out any section we're already
+                        * coping and begin anew. There might be
+                        * multiple [branch "$name"] sections.
+                        */
+                       if (copystr.len > 0) {
+                               if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+                                       ret = write_error(get_lock_file_path(lock));
+                                       goto out;
+                               }
+                               strbuf_reset(&copystr);
+                       }
+
+                       offset = section_name_match(&buf[i], old_name);
                        if (offset > 0) {
                                ret++;
                                if (new_name == NULL) {
@@ -2772,25 +2830,29 @@ int git_config_rename_section_in_file(const char *config_filename,
                                        continue;
                                }
                                store.baselen = strlen(new_name);
-                               if (!store_write_section(out_fd, new_name)) {
-                                       ret = write_error(get_lock_file_path(lock));
-                                       goto out;
-                               }
-                               /*
-                                * We wrote out the new section, with
-                                * a newline, now skip the old
-                                * section's length
-                                */
-                               output += offset + i;
-                               if (strlen(output) > 0) {
+                               if (!copy) {
+                                       if (write_section(out_fd, new_name) < 0) {
+                                               ret = write_error(get_lock_file_path(lock));
+                                               goto out;
+                                       }
                                        /*
-                                        * More content means there's
-                                        * a declaration to put on the
-                                        * next line; indent with a
-                                        * tab
+                                        * We wrote out the new section, with
+                                        * a newline, now skip the old
+                                        * section's length
                                         */
-                                       output -= 1;
-                                       output[0] = '\t';
+                                       output += offset + i;
+                                       if (strlen(output) > 0) {
+                                               /*
+                                                * More content means there's
+                                                * a declaration to put on the
+                                                * next line; indent with a
+                                                * tab
+                                                */
+                                               output -= 1;
+                                               output[0] = '\t';
+                                       }
+                               } else {
+                                       copystr = store_create_section(new_name);
                                }
                        }
                        remove = 0;
@@ -2798,11 +2860,30 @@ int git_config_rename_section_in_file(const char *config_filename,
                if (remove)
                        continue;
                length = strlen(output);
-               if (write_in_full(out_fd, output, length) != length) {
+
+               if (!is_section && copystr.len > 0) {
+                       strbuf_add(&copystr, output, length);
+               }
+
+               if (write_in_full(out_fd, output, length) < 0) {
                        ret = write_error(get_lock_file_path(lock));
                        goto out;
                }
        }
+
+       /*
+        * Copy a trailing section at the end of the config, won't be
+        * flushed by the usual "flush because we have a new section
+        * logic in the loop above.
+        */
+       if (copystr.len > 0) {
+               if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+                       ret = write_error(get_lock_file_path(lock));
+                       goto out;
+               }
+               strbuf_reset(&copystr);
+       }
+
        fclose(config_file);
        config_file = NULL;
 commit_and_out:
@@ -2818,11 +2899,30 @@ int git_config_rename_section_in_file(const char *config_filename,
        return ret;
 }
 
+int git_config_rename_section_in_file(const char *config_filename,
+                                     const char *old_name, const char *new_name)
+{
+       return git_config_copy_or_rename_section_in_file(config_filename,
+                                        old_name, new_name, 0);
+}
+
 int git_config_rename_section(const char *old_name, const char *new_name)
 {
        return git_config_rename_section_in_file(NULL, old_name, new_name);
 }
 
+int git_config_copy_section_in_file(const char *config_filename,
+                                     const char *old_name, const char *new_name)
+{
+       return git_config_copy_or_rename_section_in_file(config_filename,
+                                        old_name, new_name, 1);
+}
+
+int git_config_copy_section(const char *old_name, const char *new_name)
+{
+       return git_config_copy_section_in_file(NULL, old_name, new_name);
+}
+
 /*
  * Call this to report error for your variable that should not
  * get a boolean value (i.e. "[my] var" means "true").