Merge branch 'sb/merge-recursive-copy-paste-fix'
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 2ce5d690907d33bda557f66150ef1317f39b4f93..ffd45e92922ec52dccccfaecf1ceaf29f9231337 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -784,37 +784,32 @@ static void prime_ref_dir(struct ref_dir *dir)
                        prime_ref_dir(get_ref_dir(entry));
        }
 }
-/*
- * Return true iff refname1 and refname2 conflict with each other.
- * Two reference names conflict if one of them exactly matches the
- * leading components of the other; e.g., "foo/bar" conflicts with
- * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
- * "foo/barbados".
- */
-static int names_conflict(const char *refname1, const char *refname2)
+
+static int entry_matches(struct ref_entry *entry, const char *refname)
 {
-       for (; *refname1 && *refname1 == *refname2; refname1++, refname2++)
-               ;
-       return (*refname1 == '\0' && *refname2 == '/')
-               || (*refname1 == '/' && *refname2 == '\0');
+       return refname && !strcmp(entry->name, refname);
 }
 
-struct name_conflict_cb {
-       const char *refname;
-       const char *oldrefname;
-       const char *conflicting_refname;
+struct nonmatching_ref_data {
+       const char *skip;
+       struct ref_entry *found;
 };
 
-static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
+static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
 {
-       struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
-       if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
+       struct nonmatching_ref_data *data = vdata;
+
+       if (entry_matches(entry, data->skip))
                return 0;
-       if (names_conflict(data->refname, entry->name)) {
-               data->conflicting_refname = entry->name;
-               return 1;
-       }
-       return 0;
+
+       data->found = entry;
+       return 1;
+}
+
+static void report_refname_conflict(struct ref_entry *entry,
+                                   const char *refname)
+{
+       error("'%s' exists; cannot create '%s'", entry->name, refname);
 }
 
 /*
@@ -823,21 +818,84 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
  * oldrefname is non-NULL, ignore potential conflicts with oldrefname
  * (e.g., because oldrefname is scheduled for deletion in the same
  * operation).
+ *
+ * Two reference names conflict if one of them exactly matches the
+ * leading components of the other; e.g., "foo/bar" conflicts with
+ * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
+ * "foo/barbados".
  */
 static int is_refname_available(const char *refname, const char *oldrefname,
                                struct ref_dir *dir)
 {
-       struct name_conflict_cb data;
-       data.refname = refname;
-       data.oldrefname = oldrefname;
-       data.conflicting_refname = NULL;
+       const char *slash;
+       size_t len;
+       int pos;
+       char *dirname;
 
-       sort_ref_dir(dir);
-       if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
-               error("'%s' exists; cannot create '%s'",
-                     data.conflicting_refname, refname);
+       for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
+               /*
+                * We are still at a leading dir of the refname; we are
+                * looking for a conflict with a leaf entry.
+                *
+                * If we find one, we still must make sure it is
+                * not "oldrefname".
+                */
+               pos = search_ref_dir(dir, refname, slash - refname);
+               if (pos >= 0) {
+                       struct ref_entry *entry = dir->entries[pos];
+                       if (entry_matches(entry, oldrefname))
+                               return 1;
+                       report_refname_conflict(entry, refname);
+                       return 0;
+               }
+
+
+               /*
+                * Otherwise, we can try to continue our search with
+                * the next component; if we come up empty, we know
+                * there is nothing under this whole prefix.
+                */
+               pos = search_ref_dir(dir, refname, slash + 1 - refname);
+               if (pos < 0)
+                       return 1;
+
+               dir = get_ref_dir(dir->entries[pos]);
+       }
+
+       /*
+        * We are at the leaf of our refname; we want to
+        * make sure there are no directories which match it.
+        */
+       len = strlen(refname);
+       dirname = xmallocz(len + 1);
+       sprintf(dirname, "%s/", refname);
+       pos = search_ref_dir(dir, dirname, len + 1);
+       free(dirname);
+
+       if (pos >= 0) {
+               /*
+                * We found a directory named "refname". It is a
+                * problem iff it contains any ref that is not
+                * "oldrefname".
+                */
+               struct ref_entry *entry = dir->entries[pos];
+               struct ref_dir *dir = get_ref_dir(entry);
+               struct nonmatching_ref_data data;
+
+               data.skip = oldrefname;
+               sort_ref_dir(dir);
+               if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data))
+                       return 1;
+
+               report_refname_conflict(data.found, refname);
                return 0;
        }
+
+       /*
+        * There is no point in searching for another leaf
+        * node which matches it; such an entry would be the
+        * ref we are looking for, not a conflict.
+        */
        return 1;
 }
 
@@ -2190,25 +2248,12 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
  * Write an entry to the packed-refs file for the specified refname.
  * If peeled is non-NULL, write it as the entry's peeled value.
  */
-static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
+static void write_packed_entry(FILE *fh, char *refname, unsigned char *sha1,
                               unsigned char *peeled)
 {
-       char line[PATH_MAX + 100];
-       int len;
-
-       len = snprintf(line, sizeof(line), "%s %s\n",
-                      sha1_to_hex(sha1), refname);
-       /* this should not happen but just being defensive */
-       if (len > sizeof(line))
-               die("too long a refname '%s'", refname);
-       write_or_die(fd, line, len);
-
-       if (peeled) {
-               if (snprintf(line, sizeof(line), "^%s\n",
-                            sha1_to_hex(peeled)) != PEELED_LINE_LENGTH)
-                       die("internal error");
-               write_or_die(fd, line, PEELED_LINE_LENGTH);
-       }
+       fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname);
+       if (peeled)
+               fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled));
 }
 
 /*
@@ -2216,13 +2261,12 @@ static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
  */
 static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
 {
-       int *fd = cb_data;
        enum peel_status peel_status = peel_entry(entry, 0);
 
        if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
                error("internal error: %s is not a valid packed reference!",
                      entry->name);
-       write_packed_entry(*fd, entry->name, entry->u.value.sha1,
+       write_packed_entry(cb_data, entry->name, entry->u.value.sha1,
                           peel_status == PEEL_PEELED ?
                           entry->u.value.peeled : NULL);
        return 0;
@@ -2258,15 +2302,22 @@ int commit_packed_refs(void)
                get_packed_ref_cache(&ref_cache);
        int error = 0;
        int save_errno = 0;
+       FILE *out;
 
        if (!packed_ref_cache->lock)
                die("internal error: packed-refs not locked");
-       write_or_die(packed_ref_cache->lock->fd,
-                    PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
 
+       out = fdopen(packed_ref_cache->lock->fd, "w");
+       if (!out)
+               die_errno("unable to fdopen packed-refs descriptor");
+
+       fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
        do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
-                                0, write_packed_entry_fn,
-                                &packed_ref_cache->lock->fd);
+                                0, write_packed_entry_fn, out);
+       if (fclose(out))
+               die_errno("write error");
+       packed_ref_cache->lock->fd = -1;
+
        if (commit_lock_file(packed_ref_cache->lock)) {
                save_errno = errno;
                error = -1;
@@ -3108,7 +3159,7 @@ static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,
        return 1;
 }
 
-int read_ref_at(const char *refname, unsigned long at_time, int cnt,
+int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time, int cnt,
                unsigned char *sha1, char **msg,
                unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
@@ -3126,8 +3177,12 @@ int read_ref_at(const char *refname, unsigned long at_time, int cnt,
 
        for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
 
-       if (!cb.reccnt)
-               die("Log for %s is empty.", refname);
+       if (!cb.reccnt) {
+               if (flags & GET_SHA1_QUIETLY)
+                       exit(128);
+               else
+                       die("Log for %s is empty.", refname);
+       }
        if (cb.found_it)
                return 0;