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)
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;
}
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)
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));
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;
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);
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);
}
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);
}
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));
}
}
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))
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.
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");
*/
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);
}
}
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);
"(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)
* 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,
*
* - 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';
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;
}
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;
*/
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))
fclose(f);
}
}
+ free(packed_refs_file);
return refs->packed;
}
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);
{
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);
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, '/');
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);
}
{
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);
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 */
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)
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);
}
/*
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;
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,
}
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:
/* 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)
/*
*/
goto retry;
else {
- unable_to_lock_message(ref_file, errno, err);
+ unable_to_lock_message(ref_file.buf, errno, err);
goto error_return;
}
}
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;
}
/*
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;
}
/*
- * 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);
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);
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;
return 0;
}
-int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags)
+ 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 *old_sha1,
+ unsigned int flags)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- return delete_pseudoref(refname, sha1);
+ if (ref_type(refname) == REF_TYPE_PSEUDOREF)
++ 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);
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.
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:
/* 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) {
} 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)
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;
}
* 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;
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;
}
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;
}
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);
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);
error_unlink_return:
unlink_or_warn(lockpath);
error_free_return:
+ free(lockpath);
free(git_HEAD);
return -1;
}
+ free(lockpath);
#ifndef NO_SYMLINK_HEAD
done:
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;
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);
return 1;
}
strbuf_release(&err);
- ref_transaction_free(t);
+ if (t)
+ ref_transaction_free(t);
return 0;
}
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;
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;
}
#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.
* 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);
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
*/
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
/** 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);
* 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,
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.
*/
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,
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;
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");
* (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;
}
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;
* reverse of it if we are revert.
*/
- defmsg = git_pathdup("MERGE_MSG");
-
if (opts->action == REPLAY_REVERT) {
base = commit;
base_label = msg.label;
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);
* 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
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;
}
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)
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,
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)
{
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"));
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.
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))
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");
{
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);
}
{
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;