From: Junio C Hamano Date: Tue, 25 Aug 2015 21:57:08 +0000 (-0700) Subject: Merge branch 'dt/refs-pseudo' X-Git-Tag: v2.6.0-rc0~48 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/080cc646637f20494138c62fe6b8b0fee8d521fa?ds=inline;hp=-c Merge branch 'dt/refs-pseudo' To prepare for allowing a different "ref" backend to be plugged in to the system, update_ref()/delete_ref() have been taught about ref-like things like MERGE_HEAD that are per-worktree (they will always be written to the filesystem inside $GIT_DIR). * dt/refs-pseudo: pseudoref: check return values from read_ref() sequencer: replace write_cherry_pick_head with update_ref bisect: use update_ref pseudorefs: create and use pseudoref update and delete functions refs: add ref_type function refs: introduce pseudoref and per-worktree ref concepts --- 080cc646637f20494138c62fe6b8b0fee8d521fa diff --combined bisect.c index f9da9d1346,7eaa37a929..041a13d093 --- a/bisect.c +++ b/bisect.c @@@ -19,11 -19,7 +19,10 @@@ static struct object_id *current_bad_oi static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL}; static const char *argv_show_branch[] = {"show-branch", NULL, NULL}; - static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL}; +static const char *term_bad; +static const char *term_good; + /* Remember to update object flag allocation in object.h */ #define COUNTED (1u<<16) @@@ -406,21 -402,15 +405,21 @@@ struct commit_list *find_bisection(stru static int register_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) { - if (!strcmp(refname, "bad")) { + struct strbuf good_prefix = STRBUF_INIT; + strbuf_addstr(&good_prefix, term_good); + strbuf_addstr(&good_prefix, "-"); + + if (!strcmp(refname, term_bad)) { current_bad_oid = xmalloc(sizeof(*current_bad_oid)); oidcpy(current_bad_oid, oid); - } else if (starts_with(refname, "good-")) { + } else if (starts_with(refname, good_prefix.buf)) { sha1_array_append(&good_revs, oid->hash); } else if (starts_with(refname, "skip-")) { sha1_array_append(&skipped_revs, oid->hash); } + strbuf_release(&good_prefix); + return 0; } @@@ -429,13 -419,10 +428,13 @@@ static int read_bisect_refs(void return for_each_ref_in("refs/bisect/", register_ref, NULL); } +static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") +static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") + static void read_bisect_paths(struct argv_array *array) { struct strbuf str = STRBUF_INIT; - const char *filename = git_path("BISECT_NAMES"); + const char *filename = git_path_bisect_names(); FILE *fp = fopen(filename, "r"); if (!fp) @@@ -646,7 -633,7 +645,7 @@@ static void exit_if_skipped_commits(str return; printf("There are only 'skip'ped commits left to test.\n" - "The first bad commit could be any of:\n"); + "The first %s commit could be any of:\n", term_bad); print_commit_list(tried, "%s\n", "%s\n"); if (bad) printf("%s\n", oid_to_hex(bad)); @@@ -656,7 -643,7 +655,7 @@@ static int is_expected_rev(const struct object_id *oid) { - const char *filename = git_path("BISECT_EXPECTED_REV"); + const char *filename = git_path_bisect_expected_rev(); struct stat st; struct strbuf str = STRBUF_INIT; FILE *fp; @@@ -678,34 -665,16 +677,16 @@@ return res; } - static void mark_expected_rev(char *bisect_rev_hex) - { - int len = strlen(bisect_rev_hex); - const char *filename = git_path("BISECT_EXPECTED_REV"); - int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); - - if (fd < 0) - die_errno("could not create file '%s'", filename); - - bisect_rev_hex[len] = '\n'; - write_or_die(fd, bisect_rev_hex, len + 1); - bisect_rev_hex[len] = '\0'; - - if (close(fd) < 0) - die("closing file %s: %s", filename, strerror(errno)); - } - - static int bisect_checkout(char *bisect_rev_hex, int no_checkout) + static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout) { + char bisect_rev_hex[GIT_SHA1_HEXSZ + 1]; - mark_expected_rev(bisect_rev_hex); + memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1); + update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR); argv_checkout[2] = bisect_rev_hex; if (no_checkout) { - argv_update_ref[3] = bisect_rev_hex; - if (run_command_v_opt(argv_update_ref, RUN_GIT_CMD)) - die("update-ref --no-deref HEAD failed on %s", - bisect_rev_hex); + update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR); } else { int res; res = run_command_v_opt(argv_checkout, RUN_GIT_CMD); @@@ -744,24 -713,18 +725,24 @@@ static void handle_bad_merge_base(void if (is_expected_rev(current_bad_oid)) { char *bad_hex = oid_to_hex(current_bad_oid); char *good_hex = join_sha1_array_hex(&good_revs, ' '); - - fprintf(stderr, "The merge base %s is bad.\n" - "This means the bug has been fixed " - "between %s and [%s].\n", - bad_hex, bad_hex, good_hex); - + if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) { + fprintf(stderr, "The merge base %s is bad.\n" + "This means the bug has been fixed " + "between %s and [%s].\n", + bad_hex, bad_hex, good_hex); + } else { + fprintf(stderr, "The merge base %s is %s.\n" + "This means the first '%s' commit is " + "between %s and [%s].\n", + bad_hex, term_bad, term_good, bad_hex, good_hex); + } exit(3); } - fprintf(stderr, "Some good revs are not ancestor of the bad rev.\n" + fprintf(stderr, "Some %s revs are not ancestor of the %s rev.\n" "git bisect cannot work properly in this case.\n" - "Maybe you mistake good and bad revs?\n"); + "Maybe you mistook %s and %s revs?\n", + term_good, term_bad, term_good, term_bad); exit(1); } @@@ -773,10 -736,10 +754,10 @@@ static void handle_skipped_merge_base(c warning("the merge base between %s and [%s] " "must be skipped.\n" - "So we cannot be sure the first bad commit is " + "So we cannot be sure the first %s commit is " "between %s and %s.\n" "We continue anyway.", - bad_hex, good_hex, mb_hex, bad_hex); + bad_hex, good_hex, term_bad, mb_hex, bad_hex); free(good_hex); } @@@ -807,7 -770,7 +788,7 @@@ static void check_merge_bases(int no_ch handle_skipped_merge_base(mb); } else { printf("Bisecting: a merge base must be tested\n"); - exit(bisect_checkout(sha1_to_hex(mb), no_checkout)); + exit(bisect_checkout(mb, no_checkout)); } } @@@ -857,7 -820,7 +838,7 @@@ static void check_good_are_ancestors_of int fd; if (!current_bad_oid) - die("a bad revision is needed"); + die("a %s revision is needed", term_bad); /* Check if file BISECT_ANCESTORS_OK exists. */ if (!stat(filename, &st) && S_ISREG(st.st_mode)) @@@ -907,36 -870,6 +888,36 @@@ static void show_diff_tree(const char * log_tree_commit(&opt, commit); } +/* + * The terms used for this bisect session are stored in BISECT_TERMS. + * We read them and store them to adapt the messages accordingly. + * Default is bad/good. + */ +void read_bisect_terms(const char **read_bad, const char **read_good) +{ + struct strbuf str = STRBUF_INIT; + const char *filename = git_path("BISECT_TERMS"); + FILE *fp = fopen(filename, "r"); + + if (!fp) { + if (errno == ENOENT) { + *read_bad = "bad"; + *read_good = "good"; + return; + } else { + die("could not read file '%s': %s", filename, + strerror(errno)); + } + } else { + strbuf_getline(&str, fp, '\n'); + *read_bad = strbuf_detach(&str, NULL); + strbuf_getline(&str, fp, '\n'); + *read_good = strbuf_detach(&str, NULL); + } + strbuf_release(&str); + fclose(fp); +} + /* * We use the convention that exiting with an exit code 10 means that * the bisection process finished successfully. @@@ -951,9 -884,7 +932,8 @@@ int bisect_next_all(const char *prefix struct commit_list *tried; int reaches = 0, all = 0, nr, steps; const unsigned char *bisect_rev; - char bisect_rev_hex[GIT_SHA1_HEXSZ + 1]; + read_bisect_terms(&term_bad, &term_good); if (read_bisect_refs()) die("reading bisect refs failed"); @@@ -975,10 -906,8 +955,10 @@@ */ exit_if_skipped_commits(tried, NULL); - printf("%s was both good and bad\n", - oid_to_hex(current_bad_oid)); + printf("%s was both %s and %s\n", + oid_to_hex(current_bad_oid), + term_good, + term_bad); exit(1); } @@@ -989,12 -918,10 +969,11 @@@ } bisect_rev = revs.commits->item->object.sha1; - memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1); if (!hashcmp(bisect_rev, current_bad_oid->hash)) { exit_if_skipped_commits(tried, current_bad_oid); - printf("%s is the first %s commit\n", bisect_rev_hex, - printf("%s is the first bad commit\n", sha1_to_hex(bisect_rev)); ++ printf("%s is the first %s commit\n", sha1_to_hex(bisect_rev), + term_bad); show_diff_tree(prefix, revs.commits->item); /* This means the bisection process succeeded. */ exit(10); @@@ -1006,7 -933,7 +985,7 @@@ "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"), steps, (steps == 1 ? "" : "s")); - return bisect_checkout(bisect_rev_hex, no_checkout); + return bisect_checkout(bisect_rev, no_checkout); } static inline int log2i(int n) diff --combined refs.c index 835801905b,1db3654b8e..b0e0fe560c --- a/refs.c +++ b/refs.c @@@ -19,14 -19,12 +19,14 @@@ struct ref_lock * 1: End-of-component * 2: ., look for a preceding . to reject .. in refs * 3: {, look for a preceding @ to reject @{ in refs - * 4: A bad character: ASCII control characters, "~", "^", ":" or SP + * 4: A bad character: ASCII control characters, and + * ":", "?", "[", "\", "^", "~", SP, or TAB + * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set */ static unsigned char refname_disposition[256] = { 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0, @@@ -77,14 -75,12 +77,14 @@@ * * - any path component of it begins with ".", or * - it has double dots "..", or - * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or - * - it ends with a "/". - * - it ends with ".lock" - * - it contains a "\" (backslash) + * - it has ASCII control characters, or + * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or + * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or + * - it ends with a "/", or + * - it ends with ".lock", or + * - it contains a "@{" portion */ -static int check_refname_component(const char *refname, int flags) +static int check_refname_component(const char *refname, int *flags) { const char *cp; char last = '\0'; @@@ -105,16 -101,6 +105,16 @@@ break; case 4: return -1; + case 5: + if (!(*flags & REFNAME_REFSPEC_PATTERN)) + return -1; /* refspec can't be a pattern */ + + /* + * Unset the pattern flag so that we only accept + * a single asterisk for one side of refspec. + */ + *flags &= ~ REFNAME_REFSPEC_PATTERN; + break; } last = ch; } @@@ -139,10 -125,18 +139,10 @@@ int check_refname_format(const char *re while (1) { /* We are at the start of a path component. */ - component_len = check_refname_component(refname, flags); - if (component_len <= 0) { - if ((flags & REFNAME_REFSPEC_PATTERN) && - refname[0] == '*' && - (refname[1] == '\0' || refname[1] == '/')) { - /* Accept one wildcard as a full refname component. */ - flags &= ~REFNAME_REFSPEC_PATTERN; - component_len = 1; - } else { - return -1; - } - } + component_len = check_refname_component(refname, &flags); + if (component_len <= 0) + return -1; + component_count++; if (refname[component_len] == '\0') break; @@@ -1288,12 -1282,12 +1288,12 @@@ static void read_packed_refs(FILE *f, s */ static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs) { - const char *packed_refs_file; + char *packed_refs_file; if (*refs->name) - packed_refs_file = git_path_submodule(refs->name, "packed-refs"); + packed_refs_file = git_pathdup_submodule(refs->name, "packed-refs"); else - packed_refs_file = git_path("packed-refs"); + packed_refs_file = git_pathdup("packed-refs"); if (refs->packed && !stat_validity_check(&refs->packed->validity, packed_refs_file)) @@@ -1312,7 -1306,6 +1312,7 @@@ fclose(f); } } + free(packed_refs_file); return refs->packed; } @@@ -1326,13 -1319,7 +1326,13 @@@ static struct ref_dir *get_packed_refs( return get_packed_ref_dir(get_packed_ref_cache(refs)); } -void add_packed_ref(const char *refname, const unsigned char *sha1) +/* + * Add a reference to the in-memory packed reference cache. This may + * only be called while the packed-refs file is locked (see + * lock_packed_refs()). To actually write the packed-refs file, call + * commit_packed_refs(). + */ +static void add_packed_ref(const char *refname, const unsigned char *sha1) { struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(&ref_cache); @@@ -1352,23 -1339,19 +1352,23 @@@ static void read_loose_refs(const char { struct ref_cache *refs = dir->ref_cache; DIR *d; - const char *path; struct dirent *de; int dirnamelen = strlen(dirname); struct strbuf refname; + struct strbuf path = STRBUF_INIT; + size_t path_baselen; if (*refs->name) - path = git_path_submodule(refs->name, "%s", dirname); + strbuf_git_path_submodule(&path, refs->name, "%s", dirname); else - path = git_path("%s", dirname); + strbuf_git_path(&path, "%s", dirname); + path_baselen = path.len; - d = opendir(path); - if (!d) + d = opendir(path.buf); + if (!d) { + strbuf_release(&path); return; + } strbuf_init(&refname, dirnamelen + 257); strbuf_add(&refname, dirname, dirnamelen); @@@ -1377,14 -1360,17 +1377,14 @@@ unsigned char sha1[20]; struct stat st; int flag; - const char *refdir; if (de->d_name[0] == '.') continue; if (ends_with(de->d_name, ".lock")) continue; strbuf_addstr(&refname, de->d_name); - refdir = *refs->name - ? git_path_submodule(refs->name, "%s", refname.buf) - : git_path("%s", refname.buf); - if (stat(refdir, &st) < 0) { + strbuf_addstr(&path, de->d_name); + if (stat(path.buf, &st) < 0) { ; /* silently ignore */ } else if (S_ISDIR(st.st_mode)) { strbuf_addch(&refname, '/'); @@@ -1431,10 -1417,8 +1431,10 @@@ create_ref_entry(refname.buf, sha1, flag, 0)); } strbuf_setlen(&refname, dirnamelen); + strbuf_setlen(&path, path_baselen); } strbuf_release(&refname); + strbuf_release(&path); closedir(d); } @@@ -1485,15 -1469,14 +1485,15 @@@ static int resolve_gitlink_ref_recursiv { int fd, len; char buffer[128], *p; - const char *path; + char *path; if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN) return -1; path = *refs->name - ? git_path_submodule(refs->name, "%s", refname) - : git_path("%s", refname); + ? git_pathdup_submodule(refs->name, "%s", refname) + : git_pathdup("%s", refname); fd = open(path, O_RDONLY); + free(path); if (fd < 0) return resolve_gitlink_packed_ref(refs, refname, sha1); @@@ -1763,11 -1746,9 +1763,11 @@@ const char *resolve_ref_unsafe(const ch return ret; } -char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags) +char *resolve_refdup(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags) { - return xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags)); + return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags, + sha1, flags)); } /* The argument to filter_refs */ @@@ -2146,8 -2127,7 +2146,8 @@@ int for_each_remote_ref_submodule(cons int for_each_replace_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data); + return do_for_each_ref(&ref_cache, git_replace_ref_base, fn, + strlen(git_replace_ref_base), 0, cb_data); } int head_ref_namespaced(each_ref_fn fn, void *cb_data) @@@ -2290,14 -2270,25 +2290,14 @@@ static int verify_lock(struct ref_lock return 0; } -static int remove_empty_directories(const char *file) +static int remove_empty_directories(struct strbuf *path) { - /* we want to create a file but there is a directory there; + /* + * we want to create a file but there is a directory there; * if that is an empty directory (or a directory that contains * only empty directories), remove them. */ - struct strbuf path; - int result, save_errno; - - strbuf_init(&path, 20); - strbuf_addstr(&path, file); - - result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY); - save_errno = errno; - - strbuf_release(&path); - errno = save_errno; - - return result; + return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY); } /* @@@ -2397,8 -2388,7 +2397,8 @@@ static struct ref_lock *lock_ref_sha1_b unsigned int flags, int *type_p, struct strbuf *err) { - const char *ref_file; + struct strbuf ref_file = STRBUF_INIT; + struct strbuf orig_ref_file = STRBUF_INIT; const char *orig_refname = refname; struct ref_lock *lock; int last_errno = 0; @@@ -2422,19 -2412,20 +2422,19 @@@ refname = resolve_ref_unsafe(refname, resolve_flags, lock->old_oid.hash, &type); if (!refname && errno == EISDIR) { - /* we are trying to lock foo but we used to + /* + * we are trying to lock foo but we used to * have foo/bar which now does not exist; * it is normal for the empty directory 'foo' * to remain. */ - ref_file = git_path("%s", orig_refname); - if (remove_empty_directories(ref_file)) { + strbuf_git_path(&orig_ref_file, "%s", orig_refname); + if (remove_empty_directories(&orig_ref_file)) { last_errno = errno; - if (!verify_refname_available(orig_refname, extras, skip, get_loose_refs(&ref_cache), err)) strbuf_addf(err, "there are still refs under '%s'", orig_refname); - goto error_return; } refname = resolve_ref_unsafe(orig_refname, resolve_flags, @@@ -2474,10 -2465,10 +2474,10 @@@ } lock->ref_name = xstrdup(refname); lock->orig_ref_name = xstrdup(orig_refname); - ref_file = git_path("%s", refname); + strbuf_git_path(&ref_file, "%s", refname); retry: - switch (safe_create_leading_directories_const(ref_file)) { + switch (safe_create_leading_directories_const(ref_file.buf)) { case SCLD_OK: break; /* success */ case SCLD_VANISHED: @@@ -2486,12 -2477,11 +2486,12 @@@ /* fall through */ default: last_errno = errno; - strbuf_addf(err, "unable to create directory for %s", ref_file); + strbuf_addf(err, "unable to create directory for %s", + ref_file.buf); goto error_return; } - if (hold_lock_file_for_update(lock->lk, ref_file, lflags) < 0) { + if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) { last_errno = errno; if (errno == ENOENT && --attempts_remaining > 0) /* @@@ -2501,7 -2491,7 +2501,7 @@@ */ goto retry; else { - unable_to_lock_message(ref_file, errno, err); + unable_to_lock_message(ref_file.buf, errno, err); goto error_return; } } @@@ -2509,17 -2499,12 +2509,17 @@@ last_errno = errno; goto error_return; } - return lock; + goto out; error_return: unlock_ref(lock); + lock = NULL; + + out: + strbuf_release(&ref_file); + strbuf_release(&orig_ref_file); errno = last_errno; - return NULL; + return lock; } /* @@@ -2550,12 -2535,8 +2550,12 @@@ static int write_packed_entry_fn(struc return 0; } -/* This should return a meaningful errno on failure */ -int lock_packed_refs(int flags) +/* + * Lock the packed-refs file for writing. Flags is passed to + * hold_lock_file_for_update(). Return 0 on success. On errors, set + * errno appropriately and return a nonzero value. + */ +static int lock_packed_refs(int flags) { static int timeout_configured = 0; static int timeout_value = 1000; @@@ -2585,12 -2566,10 +2585,12 @@@ } /* - * Commit the packed refs changes. - * On error we must make sure that errno contains a meaningful value. + * Write the current version of the packed refs cache from memory to + * disk. The packed-refs file must already be locked for writing (see + * lock_packed_refs()). Return zero on success. On errors, set errno + * and return a nonzero value */ -int commit_packed_refs(void) +static int commit_packed_refs(void) { struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(&ref_cache); @@@ -2619,12 -2598,7 +2619,12 @@@ return error; } -void rollback_packed_refs(void) +/* + * Rollback the lockfile for the packed-refs file, and discard the + * in-memory packed reference cache. (The packed-refs file will be + * read anew if it is needed again after this function is called.) + */ +static void rollback_packed_refs(void) { struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(&ref_cache); @@@ -2782,14 -2756,7 +2782,14 @@@ int pack_refs(unsigned int flags return 0; } -int repack_without_refs(struct string_list *refnames, struct strbuf *err) +/* + * Rewrite the packed-refs file, omitting any refs listed in + * 'refnames'. On error, leave packed-refs unchanged, write an error + * message to 'err', and return a nonzero value. + * + * The refs in 'refnames' needn't be sorted. `err` must not be NULL. + */ +static int repack_without_refs(struct string_list *refnames, struct strbuf *err) { struct ref_dir *packed; struct string_list_item *refname; @@@ -2854,15 -2821,120 +2854,120 @@@ static int delete_ref_loose(struct ref_ return 0; } + static int is_per_worktree_ref(const char *refname) + { + return !strcmp(refname, "HEAD"); + } + + static int is_pseudoref_syntax(const char *refname) + { + const char *c; + + for (c = refname; *c; c++) { + if (!isupper(*c) && *c != '-' && *c != '_') + return 0; + } + + return 1; + } + + enum ref_type ref_type(const char *refname) + { + if (is_per_worktree_ref(refname)) + return REF_TYPE_PER_WORKTREE; + if (is_pseudoref_syntax(refname)) + return REF_TYPE_PSEUDOREF; + return REF_TYPE_NORMAL; + } + + static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, + const unsigned char *old_sha1, struct strbuf *err) + { + const char *filename; + int fd; + static struct lock_file lock; + struct strbuf buf = STRBUF_INIT; + int ret = -1; + + strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); + + filename = git_path("%s", pseudoref); + fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); + if (fd < 0) { + strbuf_addf(err, "Could not open '%s' for writing: %s", + filename, strerror(errno)); + return -1; + } + + if (old_sha1) { + unsigned char actual_old_sha1[20]; + + if (read_ref(pseudoref, actual_old_sha1)) + die("could not read ref '%s'", pseudoref); + if (hashcmp(actual_old_sha1, old_sha1)) { + strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref); + rollback_lock_file(&lock); + goto done; + } + } + + if (write_in_full(fd, buf.buf, buf.len) != buf.len) { + strbuf_addf(err, "Could not write to '%s'", filename); + rollback_lock_file(&lock); + goto done; + } + + commit_lock_file(&lock); + ret = 0; + done: + strbuf_release(&buf); + return ret; + } + + static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1) + { + static struct lock_file lock; + const char *filename; + + filename = git_path("%s", pseudoref); + + if (old_sha1 && !is_null_sha1(old_sha1)) { + int fd; + unsigned char actual_old_sha1[20]; + + fd = hold_lock_file_for_update(&lock, filename, + LOCK_DIE_ON_ERROR); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (read_ref(pseudoref, actual_old_sha1)) + die("could not read ref '%s'", pseudoref); + if (hashcmp(actual_old_sha1, old_sha1)) { + warning("Unexpected sha1 when deleting %s", pseudoref); + rollback_lock_file(&lock); + return -1; + } + + unlink(filename); + rollback_lock_file(&lock); + } else { + unlink(filename); + } + + return 0; + } + -int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags) +int delete_ref(const char *refname, const unsigned char *old_sha1, + unsigned int flags) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; + if (ref_type(refname) == REF_TYPE_PSEUDOREF) - return delete_pseudoref(refname, sha1); ++ return delete_pseudoref(refname, old_sha1); + transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_delete(transaction, refname, - (sha1 && !is_null_sha1(sha1)) ? sha1 : NULL, + ref_transaction_delete(transaction, refname, old_sha1, flags, NULL, &err) || ref_transaction_commit(transaction, &err)) { error("%s", err.buf); @@@ -2875,44 -2947,6 +2980,44 @@@ return 0; } +int delete_refs(struct string_list *refnames) +{ + struct strbuf err = STRBUF_INIT; + int i, result = 0; + + if (!refnames->nr) + return 0; + + result = repack_without_refs(refnames, &err); + if (result) { + /* + * If we failed to rewrite the packed-refs file, then + * it is unsafe to try to remove loose refs, because + * doing so might expose an obsolete packed value for + * a reference that might even point at an object that + * has been garbage collected. + */ + if (refnames->nr == 1) + error(_("could not delete reference %s: %s"), + refnames->items[0].string, err.buf); + else + error(_("could not delete references: %s"), err.buf); + + goto out; + } + + for (i = 0; i < refnames->nr; i++) { + const char *refname = refnames->items[i].string; + + if (delete_ref(refname, NULL, 0)) + result |= error(_("could not remove reference %s"), refname); + } + +out: + strbuf_release(&err); + return result; +} + /* * People using contrib's git-new-workdir have .git/logs/refs -> * /some/other/path/.git/logs/refs, and that may live on another device. @@@ -2925,13 -2959,9 +3030,13 @@@ static int rename_tmp_log(const char *newrefname) { int attempts_remaining = 4; + struct strbuf path = STRBUF_INIT; + int ret = -1; retry: - switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) { + strbuf_reset(&path); + strbuf_git_path(&path, "logs/%s", newrefname); + switch (safe_create_leading_directories_const(path.buf)) { case SCLD_OK: break; /* success */ case SCLD_VANISHED: @@@ -2940,19 -2970,19 +3045,19 @@@ /* fall through */ default: error("unable to create directory for %s", newrefname); - return -1; + goto out; } - if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) { + if (rename(git_path(TMP_RENAMED_LOG), path.buf)) { if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) { /* * rename(a, b) when b is an existing * directory ought to result in ISDIR, but * Solaris 5.8 gives ENOTDIR. Sheesh. */ - if (remove_empty_directories(git_path("logs/%s", newrefname))) { + if (remove_empty_directories(&path)) { error("Directory not empty: logs/%s", newrefname); - return -1; + goto out; } goto retry; } else if (errno == ENOENT && --attempts_remaining > 0) { @@@ -2965,13 -2995,10 +3070,13 @@@ } else { error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", newrefname, strerror(errno)); - return -1; + goto out; } } - return 0; + ret = 0; +out: + strbuf_release(&path); + return ret; } static int rename_ref_available(const char *oldname, const char *newname) @@@ -3035,14 -3062,7 +3140,14 @@@ int rename_ref(const char *oldrefname, if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) && delete_ref(newrefname, sha1, REF_NODEREF)) { if (errno==EISDIR) { - if (remove_empty_directories(git_path("%s", newrefname))) { + struct strbuf path = STRBUF_INIT; + int result; + + strbuf_git_path(&path, "%s", newrefname); + result = remove_empty_directories(&path); + strbuf_release(&path); + + if (result) { error("Directory not empty: %s", newrefname); goto rollback; } @@@ -3159,21 -3179,25 +3264,21 @@@ static int should_autocreate_reflog(con * should_autocreate_reflog returns non-zero. Otherwise, create it * regardless of the ref name. Fill in *err and return -1 on failure. */ -static int log_ref_setup(const char *refname, struct strbuf *sb_logfile, struct strbuf *err, int force_create) +static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create) { int logfd, oflags = O_APPEND | O_WRONLY; - char *logfile; - strbuf_git_path(sb_logfile, "logs/%s", refname); - logfile = sb_logfile->buf; - /* make sure the rest of the function can't change "logfile" */ - sb_logfile = NULL; + strbuf_git_path(logfile, "logs/%s", refname); if (force_create || should_autocreate_reflog(refname)) { - if (safe_create_leading_directories(logfile) < 0) { + if (safe_create_leading_directories(logfile->buf) < 0) { strbuf_addf(err, "unable to create directory for %s: " - "%s", logfile, strerror(errno)); + "%s", logfile->buf, strerror(errno)); return -1; } oflags |= O_CREAT; } - logfd = open(logfile, oflags, 0666); + logfd = open(logfile->buf, oflags, 0666); if (logfd < 0) { if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR)) return 0; @@@ -3181,20 -3205,20 +3286,20 @@@ if (errno == EISDIR) { if (remove_empty_directories(logfile)) { strbuf_addf(err, "There are still logs under " - "'%s'", logfile); + "'%s'", logfile->buf); return -1; } - logfd = open(logfile, oflags, 0666); + logfd = open(logfile->buf, oflags, 0666); } if (logfd < 0) { strbuf_addf(err, "unable to append to %s: %s", - logfile, strerror(errno)); + logfile->buf, strerror(errno)); return -1; } } - adjust_shared_perm(logfile); + adjust_shared_perm(logfile->buf); close(logfd); return 0; } @@@ -3238,32 -3262,36 +3343,32 @@@ static int log_ref_write_fd(int fd, con static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *msg, - struct strbuf *sb_log_file, int flags, + struct strbuf *logfile, int flags, struct strbuf *err) { int logfd, result, oflags = O_APPEND | O_WRONLY; - char *log_file; if (log_all_ref_updates < 0) log_all_ref_updates = !is_bare_repository(); - result = log_ref_setup(refname, sb_log_file, err, flags & REF_FORCE_CREATE_REFLOG); + result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG); if (result) return result; - log_file = sb_log_file->buf; - /* make sure the rest of the function can't change "log_file" */ - sb_log_file = NULL; - logfd = open(log_file, oflags); + logfd = open(logfile->buf, oflags); if (logfd < 0) return 0; result = log_ref_write_fd(logfd, old_sha1, new_sha1, git_committer_info(0), msg); if (result) { - strbuf_addf(err, "unable to append to %s: %s", log_file, + strbuf_addf(err, "unable to append to %s: %s", logfile->buf, strerror(errno)); close(logfd); return -1; } if (close(logfd)) { - strbuf_addf(err, "unable to append to %s: %s", log_file, + strbuf_addf(err, "unable to append to %s: %s", logfile->buf, strerror(errno)); return -1; } @@@ -3384,7 -3412,7 +3489,7 @@@ static int commit_ref_update(struct ref int create_symref(const char *ref_target, const char *refs_heads_master, const char *logmsg) { - const char *lockpath; + char *lockpath = NULL; char ref[1000]; int fd, len, written; char *git_HEAD = git_pathdup("%s", ref_target); @@@ -3411,7 -3439,7 +3516,7 @@@ error("refname too long: %s", refs_heads_master); goto error_free_return; } - lockpath = mkpath("%s.lock", git_HEAD); + lockpath = mkpathdup("%s.lock", git_HEAD); fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd < 0) { error("Unable to open %s for writing", lockpath); @@@ -3431,11 -3459,9 +3536,11 @@@ error_unlink_return: unlink_or_warn(lockpath); error_free_return: + free(lockpath); free(git_HEAD); return -1; } + free(lockpath); #ifndef NO_SYMLINK_HEAD done: @@@ -3495,14 -3521,14 +3600,14 @@@ static int read_ref_at_ent(unsigned cha hashcpy(cb->sha1, nsha1); if (hashcmp(cb->osha1, nsha1)) warning("Log for ref %s has gap after %s.", - cb->refname, show_date(cb->date, cb->tz, DATE_RFC2822)); + cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); } else if (cb->date == cb->at_time) hashcpy(cb->sha1, nsha1); else if (hashcmp(nsha1, cb->sha1)) warning("Log for ref %s unexpectedly ended on %s.", cb->refname, show_date(cb->date, cb->tz, - DATE_RFC2822)); + DATE_MODE(RFC2822))); hashcpy(cb->osha1, osha1); hashcpy(cb->nsha1, nsha1); cb->found_it = 1; @@@ -3961,17 -3987,25 +4066,25 @@@ int update_ref(const char *msg, const c const unsigned char *new_sha1, const unsigned char *old_sha1, unsigned int flags, enum action_on_err onerr) { - struct ref_transaction *t; + struct ref_transaction *t = NULL; struct strbuf err = STRBUF_INIT; + int ret = 0; - t = ref_transaction_begin(&err); - if (!t || - ref_transaction_update(t, refname, new_sha1, old_sha1, - flags, msg, &err) || - ref_transaction_commit(t, &err)) { + if (ref_type(refname) == REF_TYPE_PSEUDOREF) { + ret = write_pseudoref(refname, new_sha1, old_sha1, &err); + } else { + t = ref_transaction_begin(&err); + if (!t || + ref_transaction_update(t, refname, new_sha1, old_sha1, + flags, msg, &err) || + ref_transaction_commit(t, &err)) { + ret = 1; + ref_transaction_free(t); + } + } + if (ret) { const char *str = "update_ref failed for ref '%s': %s"; - ref_transaction_free(t); switch (onerr) { case UPDATE_REFS_MSG_ON_ERR: error(str, refname, err.buf); @@@ -3986,7 -4020,8 +4099,8 @@@ return 1; } strbuf_release(&err); - ref_transaction_free(t); + if (t) + ref_transaction_free(t); return 0; } @@@ -4166,98 -4201,6 +4280,98 @@@ cleanup return ret; } +static int ref_present(const char *refname, + const struct object_id *oid, int flags, void *cb_data) +{ + struct string_list *affected_refnames = cb_data; + + return string_list_has_string(affected_refnames, refname); +} + +int initial_ref_transaction_commit(struct ref_transaction *transaction, + struct strbuf *err) +{ + struct ref_dir *loose_refs = get_loose_refs(&ref_cache); + struct ref_dir *packed_refs = get_packed_refs(&ref_cache); + int ret = 0, i; + int n = transaction->nr; + struct ref_update **updates = transaction->updates; + struct string_list affected_refnames = STRING_LIST_INIT_NODUP; + + assert(err); + + if (transaction->state != REF_TRANSACTION_OPEN) + die("BUG: commit called for transaction that is not open"); + + /* Fail if a refname appears more than once in the transaction: */ + for (i = 0; i < n; i++) + string_list_append(&affected_refnames, updates[i]->refname); + string_list_sort(&affected_refnames); + if (ref_update_reject_duplicates(&affected_refnames, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + + /* + * It's really undefined to call this function in an active + * repository or when there are existing references: we are + * only locking and changing packed-refs, so (1) any + * simultaneous processes might try to change a reference at + * the same time we do, and (2) any existing loose versions of + * the references that we are setting would have precedence + * over our values. But some remote helpers create the remote + * "HEAD" and "master" branches before calling this function, + * so here we really only check that none of the references + * that we are creating already exists. + */ + if (for_each_rawref(ref_present, &affected_refnames)) + die("BUG: initial ref transaction called with existing refs"); + + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if ((update->flags & REF_HAVE_OLD) && + !is_null_sha1(update->old_sha1)) + die("BUG: initial ref transaction with old_sha1 set"); + if (verify_refname_available(update->refname, + &affected_refnames, NULL, + loose_refs, err) || + verify_refname_available(update->refname, + &affected_refnames, NULL, + packed_refs, err)) { + ret = TRANSACTION_NAME_CONFLICT; + goto cleanup; + } + } + + if (lock_packed_refs(0)) { + strbuf_addf(err, "unable to lock packed-refs file: %s", + strerror(errno)); + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if ((update->flags & REF_HAVE_NEW) && + !is_null_sha1(update->new_sha1)) + add_packed_ref(update->refname, update->new_sha1); + } + + if (commit_packed_refs()) { + strbuf_addf(err, "unable to commit packed-refs file: %s", + strerror(errno)); + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + +cleanup: + transaction->state = REF_TRANSACTION_CLOSED; + string_list_clear(&affected_refnames, 0); + return ret; +} + char *shorten_unambiguous_ref(const char *refname, int strict) { int i; @@@ -4379,25 -4322,17 +4493,25 @@@ int parse_hide_refs_config(const char * int ref_is_hidden(const char *refname) { - struct string_list_item *item; + int i; if (!hide_refs) return 0; - for_each_string_list_item(item, hide_refs) { + for (i = hide_refs->nr - 1; i >= 0; i--) { + const char *match = hide_refs->items[i].string; + int neg = 0; int len; - if (!starts_with(refname, item->string)) + + if (*match == '!') { + neg = 1; + match++; + } + + if (!starts_with(refname, match)) continue; - len = strlen(item->string); + len = strlen(match); if (!refname[len] || refname[len] == '/') - return 1; + return !neg; } return 0; } diff --combined refs.h index 6a3fa6d41d,1927bda993..e9a5f3230a --- a/refs.h +++ b/refs.h @@@ -1,98 -1,6 +1,98 @@@ #ifndef REFS_H #define REFS_H +/* + * Resolve a reference, recursively following symbolic refererences. + * + * Store the referred-to object's name in sha1 and return the name of + * the non-symbolic reference that ultimately pointed at it. The + * return value, if not NULL, is a pointer into either a static buffer + * or the input ref. + * + * If the reference cannot be resolved to an object, the behavior + * depends on the RESOLVE_REF_READING flag: + * + * - If RESOLVE_REF_READING is set, return NULL. + * + * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of + * the last reference name in the chain, which will either be a non-symbolic + * reference or an undefined reference. If this is a prelude to + * "writing" to the ref, the return value is the name of the ref + * that will actually be created or changed. + * + * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one + * level of symbolic reference. The value stored in sha1 for a symbolic + * reference will always be null_sha1 in this case, and the return + * value is the reference that the symref refers to directly. + * + * If flags is non-NULL, set the value that it points to the + * combination of REF_ISPACKED (if the reference was found among the + * packed references), REF_ISSYMREF (if the initial reference was a + * symbolic reference), REF_BAD_NAME (if the reference name is ill + * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN + * (if the ref is malformed or has a bad name). See refs.h for more detail + * on each flag. + * + * If ref is not a properly-formatted, normalized reference, return + * NULL. If more than MAXDEPTH recursive symbolic lookups are needed, + * give up and return NULL. + * + * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their + * name is invalid according to git-check-ref-format(1). If the name + * is bad then the value stored in sha1 will be null_sha1 and the two + * flags REF_ISBROKEN and REF_BAD_NAME will be set. + * + * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/ + * directory and do not consist of all caps and underscores cannot be + * resolved. The function returns NULL for such ref names. + * Caps and underscores refers to the special refs, such as HEAD, + * FETCH_HEAD and friends, that all live outside of the refs/ directory. + */ +#define RESOLVE_REF_READING 0x01 +#define RESOLVE_REF_NO_RECURSE 0x02 +#define RESOLVE_REF_ALLOW_BAD_NAME 0x04 + +extern const char *resolve_ref_unsafe(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); + +extern char *resolve_refdup(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); + +extern int read_ref_full(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); +extern int read_ref(const char *refname, unsigned char *sha1); + +extern int ref_exists(const char *refname); + +extern int is_branch(const char *refname); + +/* + * If refname is a non-symbolic reference that refers to a tag object, + * and the tag can be (recursively) dereferenced to a non-tag object, + * store the SHA1 of the referred-to object to sha1 and return 0. If + * any of these conditions are not met, return a non-zero value. + * Symbolic references are considered unpeelable, even if they + * ultimately resolve to a peelable tag. + */ +extern int peel_ref(const char *refname, unsigned char *sha1); + +/** + * Resolve refname in the nested "gitlink" repository that is located + * at path. If the resolution is successful, return 0 and set sha1 to + * the name of the object; otherwise, return a non-zero value. + */ +extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1); + +/* + * Return true iff abbrev_name is a possible abbreviation for + * full_name according to the rules defined by ref_rev_parse_rules in + * refs.c. + */ +extern int refname_match(const char *abbrev_name, const char *full_name); + +extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); +extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); + /* * A ref_transaction represents a collection of ref updates * that should succeed or fail together. @@@ -170,15 -78,15 +170,15 @@@ typedef int each_ref_fn(const char *ref * modifies the reference also returns a nonzero value to immediately * stop the iteration. */ -extern int head_ref(each_ref_fn, void *); -extern int for_each_ref(each_ref_fn, void *); -extern int for_each_ref_in(const char *, each_ref_fn, void *); -extern int for_each_tag_ref(each_ref_fn, void *); -extern int for_each_branch_ref(each_ref_fn, void *); -extern int for_each_remote_ref(each_ref_fn, void *); -extern int for_each_replace_ref(each_ref_fn, void *); -extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *); -extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *); +extern int head_ref(each_ref_fn fn, void *cb_data); +extern int for_each_ref(each_ref_fn fn, void *cb_data); +extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data); +extern int for_each_tag_ref(each_ref_fn fn, void *cb_data); +extern int for_each_branch_ref(each_ref_fn fn, void *cb_data); +extern int for_each_remote_ref(each_ref_fn fn, void *cb_data); +extern int for_each_replace_ref(each_ref_fn fn, void *cb_data); +extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data); +extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data); extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); @@@ -191,17 -99,47 +191,17 @@@ extern int for_each_remote_ref_submodul extern int head_ref_namespaced(each_ref_fn fn, void *cb_data); extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data); +/* can be used to learn about broken ref and symref */ +extern int for_each_rawref(each_ref_fn fn, void *cb_data); + static inline const char *has_glob_specials(const char *pattern) { return strpbrk(pattern, "?*["); } -/* can be used to learn about broken ref and symref */ -extern int for_each_rawref(each_ref_fn, void *); - extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname); extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames); -/* - * Lock the packed-refs file for writing. Flags is passed to - * hold_lock_file_for_update(). Return 0 on success. - * Errno is set to something meaningful on error. - */ -extern int lock_packed_refs(int flags); - -/* - * Add a reference to the in-memory packed reference cache. This may - * only be called while the packed-refs file is locked (see - * lock_packed_refs()). To actually write the packed-refs file, call - * commit_packed_refs(). - */ -extern void add_packed_ref(const char *refname, const unsigned char *sha1); - -/* - * Write the current version of the packed refs cache from memory to - * disk. The packed-refs file must already be locked for writing (see - * lock_packed_refs()). Return zero on success. - * Sets errno to something meaningful on error. - */ -extern int commit_packed_refs(void); - -/* - * Rollback the lockfile for the packed-refs file, and discard the - * in-memory packed reference cache. (The packed-refs file will be - * read anew if it is needed again after this function is called.) - */ -extern void rollback_packed_refs(void); - /* * Flags for controlling behaviour of pack_refs() * PACK_REFS_PRUNE: Prune loose refs after packing @@@ -216,6 -154,31 +216,6 @@@ */ int pack_refs(unsigned int flags); -/* - * Rewrite the packed-refs file, omitting any refs listed in - * 'refnames'. On error, packed-refs will be unchanged, the return - * value is nonzero, and a message about the error is written to the - * 'err' strbuf. - * - * The refs in 'refnames' needn't be sorted. `err` must not be NULL. - */ -extern int repack_without_refs(struct string_list *refnames, - struct strbuf *err); - -extern int ref_exists(const char *); - -extern int is_branch(const char *refname); - -/* - * If refname is a non-symbolic reference that refers to a tag object, - * and the tag can be (recursively) dereferenced to a non-tag object, - * store the SHA1 of the referred-to object to sha1 and return 0. If - * any of these conditions are not met, return a non-zero value. - * Symbolic references are considered unpeelable, even if they - * ultimately resolve to a peelable tag. - */ -extern int peel_ref(const char *refname, unsigned char *sha1); - /* * Flags controlling ref_transaction_update(), ref_transaction_create(), etc. * REF_NODEREF: act on the ref directly, instead of dereferencing @@@ -240,23 -203,6 +240,23 @@@ extern int read_ref_at(const char *refn /** Check if a particular reflog exists */ extern int reflog_exists(const char *refname); +/* + * Delete the specified reference. If old_sha1 is non-NULL, then + * verify that the current value of the reference is old_sha1 before + * deleting it. If old_sha1 is NULL, delete the reference if it + * exists, regardless of its old value. It is an error for old_sha1 to + * be NULL_SHA1. flags is passed through to ref_transaction_delete(). + */ +extern int delete_ref(const char *refname, const unsigned char *old_sha1, + unsigned int flags); + +/* + * Delete the specified references. If there are any problems, emit + * errors but attempt to keep going (i.e., the deletes are not done in + * an all-or-nothing transaction). + */ +extern int delete_refs(struct string_list *refnames); + /** Delete a reflog */ extern int delete_reflog(const char *refname); @@@ -279,19 -225,23 +279,19 @@@ extern int for_each_reflog(each_ref_fn * to the rules described in Documentation/git-check-ref-format.txt. * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level * reference names. If REFNAME_REFSPEC_PATTERN is set in flags, then - * allow a "*" wildcard character in place of one of the name - * components. No leading or repeated slashes are accepted. + * allow a single "*" wildcard character in the refspec. No leading or + * repeated slashes are accepted. */ extern int check_refname_format(const char *refname, int flags); extern const char *prettify_refname(const char *refname); + extern char *shorten_unambiguous_ref(const char *refname, int strict); /** rename ref, return 0 on success **/ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); -/** - * Resolve refname in the nested "gitlink" repository that is located - * at path. If the resolution is successful, return 0 and set sha1 to - * the name of the object; otherwise, return a non-zero value. - */ -extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1); +extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg); enum action_on_err { UPDATE_REFS_MSG_ON_ERR, @@@ -410,20 -360,6 +410,20 @@@ int ref_transaction_verify(struct ref_t int ref_transaction_commit(struct ref_transaction *transaction, struct strbuf *err); +/* + * Like ref_transaction_commit(), but optimized for creating + * references when originally initializing a repository (e.g., by "git + * clone"). It writes the new references directly to packed-refs + * without locking the individual references. + * + * It is a bug to call this function when there might be other + * processes accessing the repository or if there are existing + * references that might conflict with the ones being created. All + * old_sha1 values must either be absent or NULL_SHA1. + */ +int initial_ref_transaction_commit(struct ref_transaction *transaction, + struct strbuf *err); + /* * Free an existing transaction and all associated data. */ @@@ -442,9 -378,16 +442,17 @@@ int update_ref(const char *msg, const c unsigned int flags, enum action_on_err onerr); extern int parse_hide_refs_config(const char *var, const char *value, const char *); + extern int ref_is_hidden(const char *); + enum ref_type { + REF_TYPE_PER_WORKTREE, + REF_TYPE_PSEUDOREF, + REF_TYPE_NORMAL, + }; + + enum ref_type ref_type(const char *refname); + enum expire_reflog_flags { EXPIRE_REFLOGS_DRY_RUN = 1 << 0, EXPIRE_REFLOGS_UPDATE_REF = 1 << 1, diff --combined sequencer.c index 147adfac81,554a704e23..a0600aebca --- a/sequencer.c +++ b/sequencer.c @@@ -21,11 -21,6 +21,11 @@@ const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; +static GIT_PATH_FUNC(git_path_todo_file, SEQ_TODO_FILE) +static GIT_PATH_FUNC(git_path_opts_file, SEQ_OPTS_FILE) +static GIT_PATH_FUNC(git_path_seq_dir, SEQ_DIR) +static GIT_PATH_FUNC(git_path_head_file, SEQ_HEAD_FILE) + static int is_rfc2822_line(const char *buf, int len) { int i; @@@ -163,23 -158,6 +163,6 @@@ static void free_message(struct commit unuse_commit_buffer(commit, msg->message); } - static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) - { - const char *filename; - int fd; - struct strbuf buf = STRBUF_INIT; - - strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); - - filename = git_path("%s", pseudoref); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); - if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) - die_errno(_("Could not write to '%s'"), filename); - strbuf_release(&buf); - } - static void print_advice(int show_hint, struct replay_opts *opts) { char *msg = getenv("GIT_CHERRY_PICK_HELP"); @@@ -191,7 -169,7 +174,7 @@@ * (typically rebase --interactive) wants to take care * of the commit itself so remove CHERRY_PICK_HEAD */ - unlink(git_path("CHERRY_PICK_HEAD")); + unlink(git_path_cherry_pick_head()); return; } @@@ -472,6 -450,7 +455,6 @@@ static int do_pick_commit(struct commi struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; - char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; int res, unborn = 0, allow; @@@ -541,6 -520,8 +524,6 @@@ * reverse of it if we are revert. */ - defmsg = git_pathdup("MERGE_MSG"); - if (opts->action == REPLAY_REVERT) { base = commit; base_label = msg.label; @@@ -587,12 -568,12 +570,12 @@@ if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); - write_message(&msgbuf, defmsg); + write_message(&msgbuf, git_path_merge_msg()); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; - write_message(&msgbuf, defmsg); + write_message(&msgbuf, git_path_merge_msg()); commit_list_insert(base, &common); commit_list_insert(next, &remotes); @@@ -609,9 -590,11 +592,11 @@@ * write it at all. */ if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) - write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); + update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.sha1, NULL, + REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) - write_cherry_pick_head(commit, "REVERT_HEAD"); + update_ref(NULL, "REVERT_HEAD", commit->object.sha1, NULL, + REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); if (res) { error(opts->action == REPLAY_REVERT @@@ -630,10 -613,11 +615,10 @@@ goto leave; } if (!opts->no_commit) - res = run_git_commit(defmsg, opts, allow); + res = run_git_commit(git_path_merge_msg(), opts, allow); leave: free_message(commit, &msg); - free(defmsg); return res; } @@@ -757,23 -741,24 +742,23 @@@ static int parse_insn_buffer(char *buf static void read_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { - const char *todo_file = git_path(SEQ_TODO_FILE); struct strbuf buf = STRBUF_INIT; int fd, res; - fd = open(todo_file, O_RDONLY); + fd = open(git_path_todo_file(), O_RDONLY); if (fd < 0) - die_errno(_("Could not open %s"), todo_file); + die_errno(_("Could not open %s"), git_path_todo_file()); if (strbuf_read(&buf, fd, 0) < 0) { close(fd); strbuf_release(&buf); - die(_("Could not read %s."), todo_file); + die(_("Could not read %s."), git_path_todo_file()); } close(fd); res = parse_insn_buffer(buf.buf, todo_list, opts); strbuf_release(&buf); if (res) - die(_("Unusable instruction sheet: %s"), todo_file); + die(_("Unusable instruction sheet: %s"), git_path_todo_file()); } static int populate_opts_cb(const char *key, const char *value, void *data) @@@ -813,10 -798,12 +798,10 @@@ static void read_populate_opts(struct replay_opts **opts_ptr) { - const char *opts_file = git_path(SEQ_OPTS_FILE); - - if (!file_exists(opts_file)) + if (!file_exists(git_path_opts_file())) return; - if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0) - die(_("Malformed options sheet: %s"), opts_file); + if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts_ptr) < 0) + die(_("Malformed options sheet: %s"), git_path_opts_file()); } static void walk_revs_populate_todo(struct commit_list **todo_list, @@@ -834,29 -821,31 +819,29 @@@ static int create_seq_dir(void) { - const char *seq_dir = git_path(SEQ_DIR); - - if (file_exists(seq_dir)) { + if (file_exists(git_path_seq_dir())) { error(_("a cherry-pick or revert is already in progress")); advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); return -1; } - else if (mkdir(seq_dir, 0777) < 0) - die_errno(_("Could not create sequencer directory %s"), seq_dir); + else if (mkdir(git_path_seq_dir(), 0777) < 0) + die_errno(_("Could not create sequencer directory %s"), + git_path_seq_dir()); return 0; } static void save_head(const char *head) { - const char *head_file = git_path(SEQ_HEAD_FILE); static struct lock_file head_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), LOCK_DIE_ON_ERROR); strbuf_addf(&buf, "%s\n", head); if (write_in_full(fd, buf.buf, buf.len) < 0) - die_errno(_("Could not write to %s"), head_file); + die_errno(_("Could not write to %s"), git_path_head_file()); if (commit_lock_file(&head_lock) < 0) - die(_("Error wrapping up %s."), head_file); + die(_("Error wrapping up %s."), git_path_head_file()); } static int reset_for_rollback(const unsigned char *sha1) @@@ -873,8 -862,8 +858,8 @@@ static int rollback_single_pick(void { unsigned char head_sha1[20]; - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) + if (!file_exists(git_path_cherry_pick_head()) && + !file_exists(git_path_revert_head())) return error(_("no cherry-pick or revert in progress")); if (read_ref_full("HEAD", 0, head_sha1, NULL)) return error(_("cannot resolve HEAD")); @@@ -885,11 -874,13 +870,11 @@@ static int sequencer_rollback(struct replay_opts *opts) { - const char *filename; FILE *f; unsigned char sha1[20]; struct strbuf buf = STRBUF_INIT; - filename = git_path(SEQ_HEAD_FILE); - f = fopen(filename, "r"); + f = fopen(git_path_head_file(), "r"); if (!f && errno == ENOENT) { /* * There is no multiple-cherry-pick in progress. @@@ -899,18 -890,18 +884,18 @@@ return rollback_single_pick(); } if (!f) - return error(_("cannot open %s: %s"), filename, + return error(_("cannot open %s: %s"), git_path_head_file(), strerror(errno)); if (strbuf_getline(&buf, f, '\n')) { - error(_("cannot read %s: %s"), filename, ferror(f) ? - strerror(errno) : _("unexpected end of file")); + error(_("cannot read %s: %s"), git_path_head_file(), + ferror(f) ? strerror(errno) : _("unexpected end of file")); fclose(f); goto fail; } fclose(f); if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), - filename); + git_path_head_file()); goto fail; } if (reset_for_rollback(sha1)) @@@ -925,27 -916,28 +910,27 @@@ fail static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) { - const char *todo_file = git_path(SEQ_TODO_FILE); static struct lock_file todo_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), LOCK_DIE_ON_ERROR); if (format_todo(&buf, todo_list, opts) < 0) - die(_("Could not format %s."), todo_file); + die(_("Could not format %s."), git_path_todo_file()); if (write_in_full(fd, buf.buf, buf.len) < 0) { strbuf_release(&buf); - die_errno(_("Could not write to %s"), todo_file); + die_errno(_("Could not write to %s"), git_path_todo_file()); } if (commit_lock_file(&todo_lock) < 0) { strbuf_release(&buf); - die(_("Error wrapping up %s."), todo_file); + die(_("Error wrapping up %s."), git_path_todo_file()); } strbuf_release(&buf); } static void save_opts(struct replay_opts *opts) { - const char *opts_file = git_path(SEQ_OPTS_FILE); + const char *opts_file = git_path_opts_file(); if (opts->no_commit) git_config_set_in_file(opts_file, "options.no-commit", "true"); @@@ -1006,8 -998,8 +991,8 @@@ static int continue_single_pick(void { const char *argv[] = { "commit", NULL }; - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) + if (!file_exists(git_path_cherry_pick_head()) && + !file_exists(git_path_revert_head())) return error(_("no cherry-pick or revert in progress")); return run_command_v_opt(argv, RUN_GIT_CMD); } @@@ -1016,14 -1008,14 +1001,14 @@@ static int sequencer_continue(struct re { struct commit_list *todo_list = NULL; - if (!file_exists(git_path(SEQ_TODO_FILE))) + if (!file_exists(git_path_todo_file())) return continue_single_pick(); read_populate_opts(&opts); read_populate_todo(&todo_list, opts); /* Verify that the conflict has been resolved */ - if (file_exists(git_path("CHERRY_PICK_HEAD")) || - file_exists(git_path("REVERT_HEAD"))) { + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { int ret = continue_single_pick(); if (ret) return ret;