diff options
author | Junio C Hamano <junkio@cox.net> | 2006-11-01 08:48:50 -0800 |
---|---|---|
committer | Junio C Hamano <junkio@cox.net> | 2006-11-01 08:48:50 -0800 |
commit | 58a1e0e83bcdbf063fb708ee9a8a563c0aa14f87 (patch) | |
tree | e3534fae05b865abb5d686c191cdbde9b9cbf3d0 | |
parent | ec1e46897329317d7a9996264d7e999f9a68e307 (diff) | |
parent | 2e6d8f181dd77d40a1148549d4f33cdf2877fb19 (diff) | |
download | git-58a1e0e83bcdbf063fb708ee9a8a563c0aa14f87.tar.gz git-58a1e0e83bcdbf063fb708ee9a8a563c0aa14f87.tar.xz |
Merge branch 'lj/refs'
* lj/refs: (63 commits)
Fix show-ref usagestring
t3200: git-branch testsuite update
sha1_name.c: avoid compilation warnings.
Make git-branch a builtin
ref-log: fix D/F conflict coming from deleted refs.
git-revert with conflicts to behave as git-merge with conflicts
core.logallrefupdates thinko-fix
git-pack-refs --all
core.logallrefupdates create new log file only for branch heads.
Remove bashism from t3210-pack-refs.sh
ref-log: allow ref@{count} syntax.
pack-refs: call fflush before fsync.
pack-refs: use lockfile as everybody else does.
git-fetch: do not look into $GIT_DIR/refs to see if a tag exists.
lock_ref_sha1_basic does not remove empty directories on BSD
Do not create tag leading directories since git update-ref does it.
Check that a tag exists using show-ref instead of looking for the ref file.
Use git-update-ref to delete a tag instead of rm()ing the ref file.
Fix refs.c;:repack_without_ref() clean-up path
Clean up "git-branch.sh" and add remove recursive dir test cases.
...
45 files changed, 1475 insertions, 536 deletions
diff --git a/.gitignore b/.gitignore index b67087751..4c8c8e411 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ git-name-rev git-mv git-pack-redundant git-pack-objects +git-pack-refs git-parse-remote git-patch-id git-peek-remote @@ -106,6 +107,7 @@ git-shortlog git-show git-show-branch git-show-index +git-show-ref git-ssh-fetch git-ssh-pull git-ssh-push diff --git a/Documentation/config.txt b/Documentation/config.txt index 026d4cf9a..d9e73da2a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -71,12 +71,16 @@ core.preferSymlinkRefs:: expect HEAD to be a symbolic link. core.logAllRefUpdates:: - If true, `git-update-ref` will append a line to - "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time - of the update. If the file does not exist it will be - created automatically. This information can be used to - determine what commit was the tip of a branch "2 days ago". - This value is false by default (no logging). + Updates to a ref <ref> is logged to the file + "$GIT_DIR/logs/<ref>", by appending the new and old + SHA1, the date/time and the reason of the update, but + only when the file exists. If this configuration + variable is set to true, missing "$GIT_DIR/logs/<ref>" + file is automatically created for branch heads. + + This information can be used to determine what commit + was the tip of a branch "2 days ago". This value is + false by default (no automated creation of log files). core.repositoryFormatVersion:: Internal variable identifying the repository format and layout diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt new file mode 100644 index 000000000..5973a8251 --- /dev/null +++ b/Documentation/git-show-ref.txt @@ -0,0 +1,156 @@ +git-show-ref(1) +=============== + +NAME +---- +git-show-ref - List references in a local repository + +SYNOPSIS +-------- +[verse] +'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] + [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>... + +DESCRIPTION +----------- + +Displays references available in a local repository along with the associated +commit IDs. Results can be filtered using a pattern and tags can be +dereferenced into object IDs. Additionally, it can be used to test whether a +particular ref exists. + +Use of this utility is encouraged in favor of directly accessing files under +in the `.git` directory. + +OPTIONS +------- + +-h, --head:: + + Show the HEAD reference. + +--tags, --heads:: + + Limit to only "refs/heads" and "refs/tags", respectively. These + options are not mutually exclusive; when given both, references stored + in "refs/heads" and "refs/tags" are displayed. + +-d, --dereference:: + + Dereference tags into object IDs as well. They will be shown with "^{}" + appended. + +-s, --hash:: + + Only show the SHA1 hash, not the reference name. When also using + --dereference the dereferenced tag will still be shown after the SHA1. + +--verify:: + + Enable stricter reference checking by requiring an exact ref path. + Aside from returning an error code of 1, it will also print an error + message if '--quiet' was not specified. + +--abbrev, --abbrev=len:: + + Abbreviate the object name. When using `--hash`, you do + not have to say `--hash --abbrev`; `--hash=len` would do. + +-q, --quiet:: + + Do not print any results to stdout. When combined with '--verify' this + can be used to silently check if a reference exists. + +<pattern>:: + + Show references matching one or more patterns. + +OUTPUT +------ + +The output is in the format: '<SHA-1 ID>' '<space>' '<reference name>'. + +----------------------------------------------------------------------------- +$ git show-ref --head --dereference +832e76a9899f560a90ffd62ae2ce83bbeff58f54 HEAD +832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/master +832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/origin +3521017556c5de4159da4615a39fa4d5d2c279b5 refs/tags/v0.99.9c +6ddc0964034342519a87fe013781abf31c6db6ad refs/tags/v0.99.9c^{} +055e4ae3ae6eb344cbabf2a5256a49ea66040131 refs/tags/v1.0rc4 +423325a2d24638ddcc82ce47be5e40be550f4507 refs/tags/v1.0rc4^{} +... +----------------------------------------------------------------------------- + +When using --hash (and not --dereference) the output format is: '<SHA-1 ID>' + +----------------------------------------------------------------------------- +$ git show-ref --heads --hash +2e3ba0114a1f52b47df29743d6915d056be13278 +185008ae97960c8d551adcd9e23565194651b5d1 +03adf42c988195b50e1a1935ba5fcbc39b2b029b +... +----------------------------------------------------------------------------- + +EXAMPLE +------- + +To show all references called "master", whether tags or heads or anything +else, and regardless of how deep in the reference naming hierarchy they are, +use: + +----------------------------------------------------------------------------- + git show-ref master +----------------------------------------------------------------------------- + +This will show "refs/heads/master" but also "refs/remote/other-repo/master", +if such references exists. + +When using the '--verify' flag, the command requires an exact path: + +----------------------------------------------------------------------------- + git show-ref --verify refs/heads/master +----------------------------------------------------------------------------- + +will only match the exact branch called "master". + +If nothing matches, gitlink:git-show-ref[1] will return an error code of 1, +and in the case of verification, it will show an error message. + +For scripting, you can ask it to be quiet with the "--quiet" flag, which +allows you to do things like + +----------------------------------------------------------------------------- + git-show-ref --quiet --verify -- "refs/heads/$headname" || + echo "$headname is not a valid branch" +----------------------------------------------------------------------------- + +to check whether a particular branch exists or not (notice how we don't +actually want to show any results, and we want to use the full refname for it +in order to not trigger the problem with ambiguous partial matches). + +To show only tags, or only proper branch heads, use "--tags" and/or "--heads" +respectively (using both means that it shows tags and heads, but not other +random references under the refs/ subdirectory). + +To do automatic tag object dereferencing, use the "-d" or "--dereference" +flag, so you can do + +----------------------------------------------------------------------------- + git show-ref --tags --dereference +----------------------------------------------------------------------------- + +to get a listing of all tags together with what they dereference. + +SEE ALSO +-------- +gitlink:git-ls-remote[1], gitlink:git-peek-remote[1] + +AUTHORS +------- +Written by Linus Torvalds <torvalds@osdl.org>. +Man page by Jonas Fonseca <fonseca@diku.dk>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index e062030e9..71bcb7954 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely SYNOPSIS -------- -'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>] +'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>]) DESCRIPTION ----------- @@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that the current value of the <ref> matches <oldvalue>. E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>` updates the master branch head to <newvalue> only if its current -value is <oldvalue>. +value is <oldvalue>. You can specify 40 "0" or an empty string +as <oldvalue> to make sure that the ref you are creating does +not exist. It also allows a "ref" file to be a symbolic pointer to another ref file by starting with the four-byte header sequence of @@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a ref symlink to some other tree, if you have copied a whole archive by creating a symlink tree). +With `-d` flag, it deletes the named <ref> after verifying it +still contains <oldvalue>. + + Logging Updates --------------- If config parameter "core.logAllRefUpdates" is true or the file @@ -158,7 +158,7 @@ BASIC_CFLAGS = BASIC_LDFLAGS = SCRIPT_SH = \ - git-bisect.sh git-branch.sh git-checkout.sh \ + git-bisect.sh git-checkout.sh \ git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh \ git-ls-remote.sh \ @@ -270,6 +270,7 @@ BUILTIN_OBJS = \ builtin-annotate.o \ builtin-apply.o \ builtin-archive.o \ + builtin-branch.o \ builtin-cat-file.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ @@ -310,7 +311,9 @@ BUILTIN_OBJS = \ builtin-update-ref.o \ builtin-upload-archive.o \ builtin-verify-pack.o \ - builtin-write-tree.o + builtin-write-tree.o \ + builtin-show-ref.o \ + builtin-pack-refs.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) EXTLIBS = -lz diff --git a/builtin-branch.c b/builtin-branch.c new file mode 100644 index 000000000..e028a5390 --- /dev/null +++ b/builtin-branch.c @@ -0,0 +1,219 @@ +/* + * Builtin "git branch" + * + * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com> + * Based on git-branch.sh by Junio C Hamano. + */ + +#include "cache.h" +#include "refs.h" +#include "commit.h" +#include "builtin.h" + +static const char builtin_branch_usage[] = +"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]"; + + +static const char *head; +static unsigned char head_sha1[20]; + +static int in_merge_bases(const unsigned char *sha1, + struct commit *rev1, + struct commit *rev2) +{ + struct commit_list *bases, *b; + int ret = 0; + + bases = get_merge_bases(rev1, rev2, 1); + for (b = bases; b; b = b->next) { + if (!hashcmp(sha1, b->item->object.sha1)) { + ret = 1; + break; + } + } + + free_commit_list(bases); + return ret; +} + +static void delete_branches(int argc, const char **argv, int force) +{ + struct commit *rev, *head_rev; + unsigned char sha1[20]; + char *name; + int i; + + head_rev = lookup_commit_reference(head_sha1); + for (i = 0; i < argc; i++) { + if (!strcmp(head, argv[i])) + die("Cannot delete the branch you are currently on."); + + name = xstrdup(mkpath("refs/heads/%s", argv[i])); + if (!resolve_ref(name, sha1, 1, NULL)) + die("Branch '%s' not found.", argv[i]); + + rev = lookup_commit_reference(sha1); + if (!rev || !head_rev) + die("Couldn't look up commit objects."); + + /* This checks whether the merge bases of branch and + * HEAD contains branch -- which means that the HEAD + * contains everything in both. + */ + + if (!force && + !in_merge_bases(sha1, rev, head_rev)) { + fprintf(stderr, + "The branch '%s' is not a strict subset of your current HEAD.\n" + "If you are sure you want to delete it, run 'git branch -D %s'.\n", + argv[i], argv[i]); + exit(1); + } + + if (delete_ref(name, sha1)) + printf("Error deleting branch '%s'\n", argv[i]); + else + printf("Deleted branch %s.\n", argv[i]); + + free(name); + } +} + +static int ref_index, ref_alloc; +static char **ref_list; + +static int append_ref(const char *refname, const unsigned char *sha1, int flags, + void *cb_data) +{ + if (ref_index >= ref_alloc) { + ref_alloc = alloc_nr(ref_alloc); + ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *)); + } + + ref_list[ref_index++] = xstrdup(refname); + + return 0; +} + +static int ref_cmp(const void *r1, const void *r2) +{ + return strcmp(*(char **)r1, *(char **)r2); +} + +static void print_ref_list(int remote_only) +{ + int i; + + if (remote_only) + for_each_remote_ref(append_ref, NULL); + else + for_each_branch_ref(append_ref, NULL); + + qsort(ref_list, ref_index, sizeof(char *), ref_cmp); + + for (i = 0; i < ref_index; i++) { + if (!strcmp(ref_list[i], head)) + printf("* %s\n", ref_list[i]); + else + printf(" %s\n", ref_list[i]); + } +} + +static void create_branch(const char *name, const char *start, + int force, int reflog) +{ + struct ref_lock *lock; + struct commit *commit; + unsigned char sha1[20]; + char ref[PATH_MAX], msg[PATH_MAX + 20]; + + snprintf(ref, sizeof ref, "refs/heads/%s", name); + if (check_ref_format(ref)) + die("'%s' is not a valid branch name.", name); + + if (resolve_ref(ref, sha1, 1, NULL)) { + if (!force) + die("A branch named '%s' already exists.", name); + else if (!strcmp(head, name)) + die("Cannot force update the current branch."); + } + + if (get_sha1(start, sha1) || + (commit = lookup_commit_reference(sha1)) == NULL) + die("Not a valid branch point: '%s'.", start); + hashcpy(sha1, commit->object.sha1); + + lock = lock_any_ref_for_update(ref, NULL); + if (!lock) + die("Failed to lock ref for update: %s.", strerror(errno)); + + if (reflog) { + log_all_ref_updates = 1; + snprintf(msg, sizeof msg, "branch: Created from %s", start); + } + + if (write_ref_sha1(lock, sha1, msg) < 0) + die("Failed to write ref: %s.", strerror(errno)); +} + +int cmd_branch(int argc, const char **argv, const char *prefix) +{ + int delete = 0, force_delete = 0, force_create = 0, remote_only = 0; + int reflog = 0; + int i; + + git_config(git_default_config); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-d")) { + delete = 1; + continue; + } + if (!strcmp(arg, "-D")) { + delete = 1; + force_delete = 1; + continue; + } + if (!strcmp(arg, "-f")) { + force_create = 1; + continue; + } + if (!strcmp(arg, "-r")) { + remote_only = 1; + continue; + } + if (!strcmp(arg, "-l")) { + reflog = 1; + continue; + } + usage(builtin_branch_usage); + } + + head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL)); + if (!head) + die("Failed to resolve HEAD as a valid ref."); + if (strncmp(head, "refs/heads/", 11)) + die("HEAD not found below refs/heads!"); + head += 11; + + if (delete) + delete_branches(argc - i, argv + i, force_delete); + else if (i == argc) + print_ref_list(remote_only); + else if (i == argc - 1) + create_branch(argv[i], head, force_create, reflog); + else if (i == argc - 2) + create_branch(argv[i], argv[i + 1], force_create, reflog); + else + usage(builtin_branch_usage); + + return 0; +} diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index c407c033e..3d3097d29 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -249,7 +249,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) FILE *in = stdin; const char *sep = ""; unsigned char head_sha1[20]; - const char *head, *current_branch; + const char *current_branch; git_config(fmt_merge_msg_config); @@ -277,10 +277,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) usage(fmt_merge_msg_usage); /* get current branch */ - head = xstrdup(git_path("HEAD")); - current_branch = resolve_ref(head, head_sha1, 1); - current_branch += strlen(head) - 4; - free((char *)head); + current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); if (!strncmp(current_branch, "refs/heads/", 11)) current_branch += 11; diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 698618b79..93d3d7eef 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -585,24 +585,27 @@ static void get_value(struct refinfo *ref, int atom, struct atom_value **v) *v = &ref->value[atom]; } -static struct refinfo **grab_array; -static const char **grab_pattern; -static int *grab_cnt; +struct grab_ref_cbdata { + struct refinfo **grab_array; + const char **grab_pattern; + int grab_cnt; +}; /* * A call-back given to for_each_ref(). It is unfortunate that we * need to use global variables to pass extra information to this * function. */ -static int grab_single_ref(const char *refname, const unsigned char *sha1) +static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { + struct grab_ref_cbdata *cb = cb_data; struct refinfo *ref; int cnt; - if (*grab_pattern) { + if (*cb->grab_pattern) { const char **pattern; int namelen = strlen(refname); - for (pattern = grab_pattern; *pattern; pattern++) { + for (pattern = cb->grab_pattern; *pattern; pattern++) { const char *p = *pattern; int plen = strlen(p); @@ -626,25 +629,14 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1) ref->refname = xstrdup(refname); hashcpy(ref->objectname, sha1); - cnt = *grab_cnt; - grab_array = xrealloc(grab_array, sizeof(*grab_array) * (cnt + 1)); - grab_array[cnt++] = ref; - *grab_cnt = cnt; + cnt = cb->grab_cnt; + cb->grab_array = xrealloc(cb->grab_array, + sizeof(*cb->grab_array) * (cnt + 1)); + cb->grab_array[cnt++] = ref; + cb->grab_cnt = cnt; return 0; } -static struct refinfo **grab_refs(const char **pattern, int *cnt) -{ - /* Sheesh, we really should make for-each-ref to take - * callback data. - */ - *cnt = 0; - grab_pattern = pattern; - grab_cnt = cnt; - for_each_ref(grab_single_ref); - return grab_array; -} - static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b) { struct atom_value *va, *vb; @@ -784,6 +776,7 @@ int cmd_for_each_ref(int ac, const char **av, char *prefix) int maxcount = 0; int quote_style = -1; /* unspecified yet */ struct refinfo **refs; + struct grab_ref_cbdata cbdata; for (i = 1; i < ac; i++) { const char *arg = av[i]; @@ -855,7 +848,11 @@ int cmd_for_each_ref(int ac, const char **av, char *prefix) verify_format(format); - refs = grab_refs(av + i, &num_refs); + memset(&cbdata, 0, sizeof(cbdata)); + cbdata.grab_pattern = av + i; + for_each_ref(grab_single_ref, &cbdata); + refs = cbdata.grab_array; + num_refs = cbdata.grab_cnt; for (i = 0; i < used_atom_cnt; i++) { if (used_atom[i][0] == '*') { diff --git a/builtin-init-db.c b/builtin-init-db.c index c3ed1ce49..235a0ee48 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -218,8 +218,8 @@ static void create_default_files(const char *git_dir, const char *template_path) * branch, if it does not exist yet. */ strcpy(path + len, "HEAD"); - if (read_ref(path, sha1) < 0) { - if (create_symref(path, "refs/heads/master") < 0) + if (read_ref("HEAD", sha1) < 0) { + if (create_symref("HEAD", "refs/heads/master") < 0) exit(1); } diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 52886b69b..618aa314d 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -75,11 +75,10 @@ copy_data: } } -static int tags_only; - -static int name_ref(const char *path, const unsigned char *sha1) +static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct object *o = parse_object(sha1); + int tags_only = *(int*)cb_data; int deref = 0; if (tags_only && strncmp(path, "refs/tags/", 10)) @@ -131,6 +130,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = { 0, 0, NULL }; int as_is = 0, all = 0, transform_stdin = 0; + int tags_only = 0; git_config(git_default_config); @@ -186,7 +186,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) add_object_array((struct object *)commit, *argv, &revs); } - for_each_ref(name_ref); + for_each_ref(name_ref, &tags_only); if (transform_stdin) { char buffer[2048]; diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c new file mode 100644 index 000000000..108765767 --- /dev/null +++ b/builtin-pack-refs.c @@ -0,0 +1,105 @@ +#include "cache.h" +#include "refs.h" + +static const char builtin_pack_refs_usage[] = +"git-pack-refs [--all] [--prune]"; + +struct ref_to_prune { + struct ref_to_prune *next; + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +struct pack_refs_cb_data { + int prune; + struct ref_to_prune *ref_to_prune; + FILE *refs_file; +}; + +static int do_not_prune(int flags) +{ + /* If it is already packed or if it is a symref, + * do not prune it. + */ + return (flags & (REF_ISSYMREF|REF_ISPACKED)); +} + +static int handle_one_ref(const char *path, const unsigned char *sha1, + int flags, void *cb_data) +{ + struct pack_refs_cb_data *cb = cb_data; + + /* Do not pack the symbolic refs */ + if (!(flags & REF_ISSYMREF)) + fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if (cb->prune && !do_not_prune(flags)) { + int namelen = strlen(path) + 1; + struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); + hashcpy(n->sha1, sha1); + strcpy(n->name, path); + n->next = cb->ref_to_prune; + cb->ref_to_prune = n; + } + return 0; +} + +/* make sure nobody touched the ref, and unlink */ +static void prune_ref(struct ref_to_prune *r) +{ + struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1); + + if (lock) { + unlink(git_path("%s", r->name)); + unlock_ref(lock); + } +} + +static void prune_refs(struct ref_to_prune *r) +{ + while (r) { + prune_ref(r); + r = r->next; + } +} + +static struct lock_file packed; + +int cmd_pack_refs(int argc, const char **argv, const char *prefix) +{ + int fd, i; + struct pack_refs_cb_data cbdata; + int (*iterate_ref)(each_ref_fn, void *) = for_each_tag_ref; + + memset(&cbdata, 0, sizeof(cbdata)); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--prune")) { + cbdata.prune = 1; + continue; + } + if (!strcmp(arg, "--all")) { + iterate_ref = for_each_ref; + continue; + } + /* perhaps other parameters later... */ + break; + } + if (i != argc) + usage(builtin_pack_refs_usage); + + fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1); + cbdata.refs_file = fdopen(fd, "w"); + if (!cbdata.refs_file) + die("unable to create ref-pack file structure (%s)", + strerror(errno)); + iterate_ref(handle_one_ref, &cbdata); + fflush(cbdata.refs_file); + fsync(fd); + fclose(cbdata.refs_file); + if (commit_lock_file(&packed) < 0) + die("unable to overwrite old ref-pack file (%s)", strerror(errno)); + if (cbdata.prune) + prune_refs(cbdata.ref_to_prune); + return 0; +} diff --git a/builtin-prune.c b/builtin-prune.c index 7290e6d9a..d853902c5 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -174,7 +174,7 @@ static void walk_commit_list(struct rev_info *revs) } } -static int add_one_ref(const char *path, const unsigned char *sha1) +static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *object = parse_object(sha1); if (!object) @@ -240,7 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) revs.tree_objects = 1; /* Add all external refs */ - for_each_ref(add_one_ref); + for_each_ref(add_one_ref, NULL); /* Add all refs from the index file */ add_cache_refs(); diff --git a/builtin-push.c b/builtin-push.c index f5150ed82..5f7eccf14 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -27,7 +27,7 @@ static void add_refspec(const char *ref) refspec_nr = nr; } -static int expand_one_ref(const char *ref, const unsigned char *sha1) +static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data) { /* Ignore the "refs/" at the beginning of the refname */ ref += 5; @@ -51,7 +51,7 @@ static void expand_refspecs(void) } if (!tags) return; - for_each_ref(expand_one_ref); + for_each_ref(expand_one_ref, NULL); } static void set_refspecs(const char **refs, int nr) diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index fd3ccc854..3b716fba1 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -137,7 +137,7 @@ static void show_default(void) } } -static int show_reference(const char *refname, const unsigned char *sha1) +static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { show_rev(NORMAL, sha1, refname); return 0; @@ -299,19 +299,19 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--all")) { - for_each_ref(show_reference); + for_each_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--branches")) { - for_each_branch_ref(show_reference); + for_each_branch_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--tags")) { - for_each_tag_ref(show_reference); + for_each_tag_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--remotes")) { - for_each_remote_ref(show_reference); + for_each_remote_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--show-prefix")) { diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 578c9fafd..fb1a4000d 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -346,7 +346,7 @@ static void sort_ref_range(int bottom, int top) compare_ref_name); } -static int append_ref(const char *refname, const unsigned char *sha1) +static int append_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); int i; @@ -369,7 +369,7 @@ static int append_ref(const char *refname, const unsigned char *sha1) return 0; } -static int append_head_ref(const char *refname, const unsigned char *sha1) +static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { unsigned char tmp[20]; int ofs = 11; @@ -380,14 +380,14 @@ static int append_head_ref(const char *refname, const unsigned char *sha1) */ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) ofs = 5; - return append_ref(refname + ofs, sha1); + return append_ref(refname + ofs, sha1, flag, cb_data); } -static int append_tag_ref(const char *refname, const unsigned char *sha1) +static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { if (strncmp(refname, "refs/tags/", 10)) return 0; - return append_ref(refname + 5, sha1); + return append_ref(refname + 5, sha1, flag, cb_data); } static const char *match_ref_pattern = NULL; @@ -401,7 +401,7 @@ static int count_slash(const char *s) return cnt; } -static int append_matching_ref(const char *refname, const unsigned char *sha1) +static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { /* we want to allow pattern hold/<asterisk> to show all * branches under refs/heads/hold/, and v0.99.9? to show @@ -417,41 +417,39 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1) if (fnmatch(match_ref_pattern, tail, 0)) return 0; if (!strncmp("refs/heads/", refname, 11)) - return append_head_ref(refname, sha1); + return append_head_ref(refname, sha1, flag, cb_data); if (!strncmp("refs/tags/", refname, 10)) - return append_tag_ref(refname, sha1); - return append_ref(refname, sha1); + return append_tag_ref(refname, sha1, flag, cb_data); + return append_ref(refname, sha1, flag, cb_data); } static void snarf_refs(int head, int tag) { if (head) { int orig_cnt = ref_name_cnt; - for_each_ref(append_head_ref); + for_each_ref(append_head_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } if (tag) { int orig_cnt = ref_name_cnt; - for_each_ref(append_tag_ref); + for_each_ref(append_tag_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } } -static int rev_is_head(char *head_path, int headlen, char *name, +static int rev_is_head(char *head, int headlen, char *name, unsigned char *head_sha1, unsigned char *sha1) { - int namelen; - if ((!head_path[0]) || + if ((!head[0]) || (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; - namelen = strlen(name); - if ((headlen < namelen) || - memcmp(head_path + headlen - namelen, name, namelen)) - return 0; - if (headlen == namelen || - head_path[headlen - namelen - 1] == '/') - return 1; - return 0; + if (!strncmp(head, "refs/heads/", 11)) + head += 11; + if (!strncmp(name, "refs/heads/", 11)) + name += 11; + else if (!strncmp(name, "heads/", 6)) + name += 6; + return !strcmp(head, name); } static int show_merge_base(struct commit_list *seen, int num_rev) @@ -495,7 +493,7 @@ static void append_one_rev(const char *av) { unsigned char revkey[20]; if (!get_sha1(av, revkey)) { - append_ref(av, revkey); + append_ref(av, revkey, 0, NULL); return; } if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { @@ -503,7 +501,7 @@ static void append_one_rev(const char *av) int saved_matches = ref_name_cnt; match_ref_pattern = av; match_ref_slash = count_slash(av); - for_each_ref(append_matching_ref); + for_each_ref(append_matching_ref, NULL); if (saved_matches == ref_name_cnt && ref_name_cnt < MAX_REVS) error("no matching refs with %s", av); @@ -559,9 +557,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int all_heads = 0, all_tags = 0; int all_mask, all_revs; int lifo = 1; - char head_path[128]; - const char *head_path_p; - int head_path_len; + char head[128]; + const char *head_p; + int head_len; unsigned char head_sha1[20]; int merge_base = 0; int independent = 0; @@ -638,31 +636,31 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) ac--; av++; } - head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1); - if (head_path_p) { - head_path_len = strlen(head_path_p); - memcpy(head_path, head_path_p, head_path_len + 1); + head_p = resolve_ref("HEAD", head_sha1, 1, NULL); + if (head_p) { + head_len = strlen(head_p); + memcpy(head, head_p, head_len + 1); } else { - head_path_len = 0; - head_path[0] = 0; + head_len = 0; + head[0] = 0; } - if (with_current_branch && head_path_p) { + if (with_current_branch && head_p) { int has_head = 0; for (i = 0; !has_head && i < ref_name_cnt; i++) { /* We are only interested in adding the branch * HEAD points at. */ - if (rev_is_head(head_path, - head_path_len, + if (rev_is_head(head, + head_len, ref_name[i], head_sha1, NULL)) has_head++; } if (!has_head) { - int pfxlen = strlen(git_path("refs/heads/")); - append_one_rev(head_path + pfxlen); + int pfxlen = strlen("refs/heads/"); + append_one_rev(head + pfxlen); } } @@ -713,8 +711,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (1 < num_rev || extra < 0) { for (i = 0; i < num_rev; i++) { int j; - int is_head = rev_is_head(head_path, - head_path_len, + int is_head = rev_is_head(head, + head_len, ref_name[i], head_sha1, rev[i]->object.sha1); diff --git a/builtin-show-ref.c b/builtin-show-ref.c new file mode 100644 index 000000000..06ec400d7 --- /dev/null +++ b/builtin-show-ref.c @@ -0,0 +1,147 @@ +#include "cache.h" +#include "refs.h" +#include "object.h" +#include "tag.h" + +static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]"; + +static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, + found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0; +static const char **pattern; + +static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata) +{ + struct object *obj; + const char *hex; + + if (tags_only || heads_only) { + int match; + + match = heads_only && !strncmp(refname, "refs/heads/", 11); + match |= tags_only && !strncmp(refname, "refs/tags/", 10); + if (!match) + return 0; + } + if (pattern) { + int reflen = strlen(refname); + const char **p = pattern, *m; + while ((m = *p++) != NULL) { + int len = strlen(m); + if (len > reflen) + continue; + if (memcmp(m, refname + reflen - len, len)) + continue; + if (len == reflen) + goto match; + /* "--verify" requires an exact match */ + if (verify) + continue; + if (refname[reflen - len - 1] == '/') + goto match; + } + return 0; + } + +match: + found_match++; + obj = parse_object(sha1); + if (!obj) { + if (quiet) + return 0; + die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1)); + } + if (quiet) + return 0; + + hex = find_unique_abbrev(sha1, abbrev); + if (hash_only) + printf("%s\n", hex); + else + printf("%s %s\n", hex, refname); + if (deref_tags && obj->type == OBJ_TAG) { + obj = deref_tag(obj, refname, 0); + hex = find_unique_abbrev(obj->sha1, abbrev); + printf("%s %s^{}\n", hex, refname); + } + return 0; +} + +int cmd_show_ref(int argc, const char **argv, const char *prefix) +{ + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (*arg != '-') { + pattern = argv + i; + break; + } + if (!strcmp(arg, "--")) { + pattern = argv + i + 1; + if (!*pattern) + pattern = NULL; + break; + } + if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) { + quiet = 1; + continue; + } + if (!strcmp(arg, "-h") || !strcmp(arg, "--head")) { + show_head = 1; + continue; + } + if (!strcmp(arg, "-d") || !strcmp(arg, "--dereference")) { + deref_tags = 1; + continue; + } + if (!strcmp(arg, "-s") || !strcmp(arg, "--hash")) { + hash_only = 1; + continue; + } + if (!strncmp(arg, "--hash=", 7) || + (!strncmp(arg, "--abbrev", 8) && + (arg[8] == '=' || arg[8] == '\0'))) { + if (arg[3] != 'h' && !arg[8]) + /* --abbrev only */ + abbrev = DEFAULT_ABBREV; + else { + /* --hash= or --abbrev= */ + char *end; + if (arg[3] == 'h') { + hash_only = 1; + arg += 7; + } + else + arg += 9; + abbrev = strtoul(arg, &end, 10); + if (*end || abbrev > 40) + usage(show_ref_usage); + if (abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + } + continue; + } + if (!strcmp(arg, "--verify")) { + verify = 1; + continue; + } + if (!strcmp(arg, "--tags")) { + tags_only = 1; + continue; + } + if (!strcmp(arg, "--heads")) { + heads_only = 1; + continue; + } + usage(show_ref_usage); + } + if (show_head) + head_ref(show_ref, NULL); + for_each_ref(show_ref, NULL); + if (!found_match) { + if (verify && !quiet) + die("No match"); + return 1; + } + return 0; +} diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 1d3a5e229..d8be0527f 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "refs.h" static const char git_symbolic_ref_usage[] = "git-symbolic-ref name [ref]"; @@ -7,15 +8,14 @@ static const char git_symbolic_ref_usage[] = static void check_symref(const char *HEAD) { unsigned char sha1[20]; - const char *git_HEAD = xstrdup(git_path("%s", HEAD)); - const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0); - if (git_refs_heads_master) { - /* we want to strip the .git/ part */ - int pfxlen = strlen(git_HEAD) - strlen(HEAD); - puts(git_refs_heads_master + pfxlen); - } - else + int flag; + const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag); + + if (!refs_heads_master) die("No such ref: %s", HEAD); + else if (!(flag & REF_ISSYMREF)) + die("ref %s is not a symbolic ref", HEAD); + puts(refs_heads_master); } int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) @@ -26,7 +26,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) check_symref(argv[1]); break; case 3: - create_symref(xstrdup(git_path("%s", argv[1])), argv[2]); + create_symref(argv[1], argv[2]); break; default: usage(git_symbolic_ref_usage); diff --git a/builtin-update-index.c b/builtin-update-index.c index a3c0a455a..7f9c63846 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -406,9 +406,9 @@ static int unresolve_one(const char *path) static void read_head_pointers(void) { - if (read_ref(git_path("HEAD"), head_sha1)) + if (read_ref("HEAD", head_sha1)) die("No HEAD -- no initial commit yet?\n"); - if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) { + if (read_ref("MERGE_HEAD", merge_head_sha1)) { fprintf(stderr, "Not in the middle of a merge.\n"); exit(0); } @@ -445,7 +445,7 @@ static int do_reupdate(int ac, const char **av, int has_head = 1; const char **pathspec = get_pathspec(prefix, av + 1); - if (read_ref(git_path("HEAD"), head_sha1)) + if (read_ref("HEAD", head_sha1)) /* If there is no HEAD, that means it is an initial * commit. Update everything in the index. */ diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 90a3da53a..b34e5987d 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -3,15 +3,16 @@ #include "builtin.h" static const char git_update_ref_usage[] = -"git-update-ref <refname> <value> [<oldval>] [-m <reason>]"; +"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])"; int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL; struct ref_lock *lock; unsigned char sha1[20], oldsha1[20]; - int i; + int i, delete; + delete = 0; setup_ident(); git_config(git_default_config); @@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("Refusing to perform update with \\n in message."); continue; } + if (!strcmp("-d", argv[i])) { + delete = 1; + continue; + } if (!refname) { refname = argv[i]; continue; @@ -44,11 +49,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (get_sha1(value, sha1)) die("%s: not a valid SHA1", value); + + if (delete) { + if (oldval) + usage(git_update_ref_usage); + return delete_ref(refname, sha1); + } + hashclr(oldsha1); - if (oldval && get_sha1(oldval, oldsha1)) + if (oldval && *oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); - lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0); + lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL); if (!lock) return 1; if (write_ref_sha1(lock, sha1, msg) < 0) @@ -17,6 +17,7 @@ extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); extern int cmd_archive(int argc, const char **argv, const char *prefix); +extern int cmd_branch(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); @@ -65,5 +66,7 @@ extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); +extern int cmd_show_ref(int argc, const char **argv, const char *prefix); +extern int cmd_pack_refs(int argc, const char **argv, const char *prefix); #endif @@ -179,6 +179,7 @@ struct lock_file { extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); extern void rollback_lock_file(struct lock_file *); +extern int delete_ref(const char *, unsigned char *sha1); /* Environment bits from configuration mechanism */ extern int use_legacy_headers; @@ -288,9 +289,9 @@ extern int get_sha1(const char *str, unsigned char *sha1); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); -extern const char *resolve_ref(const char *path, unsigned char *sha1, int); -extern int create_symref(const char *git_HEAD, const char *refs_heads_master); -extern int validate_symref(const char *git_HEAD); +extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); +extern int create_symref(const char *ref, const char *refs_heads_master); +extern int validate_symref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); diff --git a/describe.c b/describe.c index ab192f83a..f4029ee74 100644 --- a/describe.c +++ b/describe.c @@ -53,7 +53,7 @@ static void add_to_known_names(const char *path, names = ++idx; } -static int get_name(const char *path, const unsigned char *sha1) +static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); struct object *object; @@ -113,7 +113,7 @@ static void describe(const char *arg, int last_one) if (!initialized) { initialized = 1; - for_each_ref(get_name); + for_each_ref(get_name, NULL); qsort(name_array, names, sizeof(*name_array), compare_names); } diff --git a/fetch-pack.c b/fetch-pack.c index 474d54520..90b79407c 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -42,7 +42,7 @@ static void rev_list_push(struct commit *commit, int mark) } } -static int rev_list_insert_ref(const char *path, const unsigned char *sha1) +static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = deref_tag(parse_object(sha1), path, 0); @@ -143,7 +143,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, unsigned in_vain = 0; int got_continue = 0; - for_each_ref(rev_list_insert_ref); + for_each_ref(rev_list_insert_ref, NULL); fetching = 0; for ( ; refs ; refs = refs->next) { @@ -254,7 +254,7 @@ done: static struct commit_list *complete; -static int mark_complete(const char *path, const unsigned char *sha1) +static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = parse_object(sha1); @@ -366,7 +366,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) } } - for_each_ref(mark_complete); + for_each_ref(mark_complete, NULL); if (cutoff) mark_recent_complete_commits(cutoff); @@ -201,7 +201,7 @@ static int interpret_target(char *target, unsigned char *sha1) return -1; } -static int mark_complete(const char *path, const unsigned char *sha1) +static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); if (commit) { @@ -266,7 +266,7 @@ int pull(int targets, char **target, const char **write_ref, if (!write_ref || !write_ref[i]) continue; - lock[i] = lock_ref_sha1(write_ref[i], NULL, 0); + lock[i] = lock_ref_sha1(write_ref[i], NULL); if (!lock[i]) { error("Can't lock ref %s", write_ref[i]); goto unlock_and_fail; @@ -274,7 +274,7 @@ int pull(int targets, char **target, const char **write_ref, } if (!get_recover) - for_each_ref(mark_complete); + for_each_ref(mark_complete, NULL); for (i = 0; i < targets; i++) { if (interpret_target(target[i], &sha1[20 * i])) { diff --git a/fsck-objects.c b/fsck-objects.c index 4d994f3fc..46b628cb9 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -402,7 +402,7 @@ static void fsck_dir(int i, char *path) static int default_refs; -static int fsck_handle_ref(const char *refname, const unsigned char *sha1) +static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; @@ -424,7 +424,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1) static void get_default_heads(void) { - for_each_ref(fsck_handle_ref); + for_each_ref(fsck_handle_ref, NULL); /* * Not having any default heads isn't really fatal, but @@ -458,15 +458,14 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { unsigned char sha1[20]; - const char *git_HEAD = xstrdup(git_path("HEAD")); - const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1); - int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */ + int flag; + const char *head_points_at = resolve_ref("HEAD", sha1, 1, &flag); - if (!git_refs_heads_master) + if (!head_points_at || !(flag & REF_ISSYMREF)) return error("HEAD is not a symbolic ref"); - if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11)) + if (strncmp(head_points_at, "refs/heads/", 11)) return error("HEAD points to something strange (%s)", - git_refs_heads_master + pfxlen); + head_points_at); if (is_null_sha1(sha1)) return error("HEAD: not a valid git pointer"); return 0; diff --git a/git-branch.sh b/git-branch.sh deleted file mode 100755 index f823c788f..000000000 --- a/git-branch.sh +++ /dev/null @@ -1,140 +0,0 @@ -#!/bin/sh - -USAGE='[-l] [-f] <branchname> [<start-point>] | (-d | -D) <branchname> | [-r]' -LONG_USAGE='If no arguments, show available branches and mark current branch with a star. -If one argument, create a new branch <branchname> based off of current HEAD. -If two arguments, create a new branch <branchname> based off of <start-point>.' - -SUBDIRECTORY_OK='Yes' -. git-sh-setup - -headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||') - -delete_branch () { - option="$1" - shift - for branch_name - do - case ",$headref," in - ",$branch_name,") - die "Cannot delete the branch you are on." ;; - ,,) - die "What branch are you on anyway?" ;; - esac - branch=$(cat "$GIT_DIR/refs/heads/$branch_name") && - branch=$(git-rev-parse --verify "$branch^0") || - die "Seriously, what branch are you talking about?" - case "$option" in - -D) - ;; - *) - mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ') - case " $mbs " in - *' '$branch' '*) - # the merge base of branch and HEAD contains branch -- - # which means that the HEAD contains everything in both. - ;; - *) - echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD. -If you are sure you want to delete it, run 'git branch -D $branch_name'." - exit 1 - ;; - esac - ;; - esac - rm -f "$GIT_DIR/logs/refs/heads/$branch_name" - rm -f "$GIT_DIR/refs/heads/$branch_name" - echo "Deleted branch $branch_name." - done - exit 0 -} - -ls_remote_branches () { - git-rev-parse --symbolic --all | - sed -ne 's|^refs/\(remotes/\)|\1|p' | - sort -} - -force= -create_log= -while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac -do - case "$1" in - -d | -D) - delete_branch "$@" - exit - ;; - -r) - ls_remote_branches - exit - ;; - -f) - force="$1" - ;; - -l) - create_log="yes" - ;; - --) - shift - break - ;; - -*) - usage - ;; - esac - shift -done - -case "$#" in -0) - git-rev-parse --symbolic --branches | - sort | - while read ref - do - if test "$headref" = "$ref" - then - pfx='*' - else - pfx=' ' - fi - echo "$pfx $ref" - done - exit 0 ;; -1) - head=HEAD ;; -2) - head="$2^0" ;; -esac -branchname="$1" - -rev=$(git-rev-parse --verify "$head") || exit - -git-check-ref-format "heads/$branchname" || - die "we do not like '$branchname' as a branch name." - -if [ -d "$GIT_DIR/refs/heads/$branchname" ] -then - for refdir in `cd "$GIT_DIR" && \ - find "refs/heads/$branchname" -type d | sort -r` - do - rmdir "$GIT_DIR/$refdir" || \ - die "Could not delete '$refdir', there may still be a ref there." - done -fi - -if [ -e "$GIT_DIR/refs/heads/$branchname" ] -then - if test '' = "$force" - then - die "$branchname already exists." - elif test "$branchname" = "$headref" - then - die "cannot force-update the current branch." - fi -fi -if test "$create_log" = 'yes' -then - mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname") - touch "$GIT_DIR/logs/refs/heads/$branchname" -fi -git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev diff --git a/git-checkout.sh b/git-checkout.sh index dd477245f..119bca1ff 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do shift [ -z "$newbranch" ] && die "git checkout: -b needs a branch name" - [ -e "$GIT_DIR/refs/heads/$newbranch" ] && + git-show-ref --verify --quiet -- "refs/heads/$newbranch" && die "git checkout: branch $newbranch already exists" git-check-ref-format "heads/$newbranch" || die "git checkout: we do not like '$newbranch' as a branch name." @@ -51,7 +51,8 @@ while [ "$#" != "0" ]; do fi new="$rev" new_name="$arg^0" - if [ -f "$GIT_DIR/refs/heads/$arg" ]; then + if git-show-ref --verify --quiet -- "refs/heads/$arg" + then branch="$arg" fi elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null) diff --git a/git-commit.sh b/git-commit.sh index 5b1cf8582..81c3a0cb6 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -441,7 +441,7 @@ then elif test "$use_commit" != "" then git-cat-file commit "$use_commit" | sed -e '1,/^$/d' -elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG" +elif test -f "$GIT_DIR/MERGE_MSG" then cat "$GIT_DIR/MERGE_MSG" elif test -f "$GIT_DIR/SQUASH_MSG" @@ -522,15 +522,15 @@ then PARENTS=$(git-cat-file commit HEAD | sed -n -e '/^$/q' -e 's/^parent /-p /p') fi - current=$(git-rev-parse --verify HEAD) + current="$(git-rev-parse --verify HEAD)" else if [ -z "$(git-ls-files)" ]; then echo >&2 Nothing to commit exit 1 fi PARENTS="" - current= rloga='commit (initial)' + current='' fi if test -z "$no_edit" @@ -606,8 +606,8 @@ then fi && commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) && rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) && - git-update-ref -m "$rloga: $rlogm" HEAD $commit $current && - rm -f -- "$GIT_DIR/MERGE_HEAD" && + git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" && + rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" && if test -f "$NEXT_INDEX" then mv "$NEXT_INDEX" "$THIS_INDEX" diff --git a/git-fetch.sh b/git-fetch.sh index 7dc1f3368..fa73ad226 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -427,7 +427,7 @@ case "$no_tags$tags" in sed -ne 's|^\([0-9a-f]*\)[ ]\(refs/tags/.*\)^{}$|\1 \2|p' | while read sha1 name do - test -f "$GIT_DIR/$name" && continue + git-show-ref --verify --quiet -- $name && continue git-check-ref-format "$name" || { echo >&2 "warning: tag ${name} ignored" continue diff --git a/git-revert.sh b/git-revert.sh index 4fd81b6ed..6eab3c72d 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -145,9 +145,18 @@ git-read-tree -m -u --aggressive $base $head $next && result=$(git-write-tree 2>/dev/null) || { echo >&2 "Simple $me fails; trying Automatic $me." git-merge-index -o git-merge-one-file -a || { + mv -f .msg "$GIT_DIR/MERGE_MSG" + { + echo ' +Conflicts: +' + git ls-files --unmerged | + sed -e 's/^[^ ]* / /' | + uniq + } >>"$GIT_DIR/MERGE_MSG" echo >&2 "Automatic $me failed. After resolving the conflicts," echo >&2 "mark the corrected paths with 'git-update-index <paths>'" - echo >&2 "and commit with 'git commit -F .msg'" + echo >&2 "and commit the result." case "$me" in cherry-pick) echo >&2 "You may choose to use the following when making" diff --git a/git-tag.sh b/git-tag.sh index a0afa2582..ac269e327 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -47,8 +47,10 @@ do -d) shift tag_name="$1" - rm "$GIT_DIR/refs/tags/$tag_name" && \ - echo "Deleted tag $tag_name." + tag=$(git-show-ref --verify --hash -- "refs/tags/$tag_name") || + die "Seriously, what tag are you talking about?" + git-update-ref -m 'tag: delete' -d "refs/tags/$tag_name" "$tag" && + echo "Deleted tag $tag_name." exit $? ;; -*) @@ -63,8 +65,11 @@ done name="$1" [ "$name" ] || usage -if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then - die "tag '$name' already exists" +prev=0000000000000000000000000000000000000000 +if git-show-ref --verify --quiet -- "refs/tags/$name" +then + test -n "$force" || die "tag '$name' already exists" + prev=`git rev-parse "refs/tags/$name"` fi shift git-check-ref-format "tags/$name" || @@ -107,6 +112,5 @@ if [ "$annotate" ]; then object=$(git-mktag < "$GIT_DIR"/TAG_TMP) fi -leading=`expr "refs/tags/$name" : '\(.*\)/'` && -mkdir -p "$GIT_DIR/$leading" && -echo $object > "$GIT_DIR/refs/tags/$name" +git update-ref "refs/tags/$name" "$object" "$prev" + @@ -222,6 +222,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "annotate", cmd_annotate, }, { "apply", cmd_apply }, { "archive", cmd_archive }, + { "branch", cmd_branch }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, @@ -269,6 +270,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, { "verify-pack", cmd_verify_pack }, + { "show-ref", cmd_show_ref, RUN_SETUP }, + { "pack-refs", cmd_pack_refs, RUN_SETUP }, }; int i; diff --git a/http-push.c b/http-push.c index 670ff007b..ecefdfd4f 100644 --- a/http-push.c +++ b/http-push.c @@ -1864,7 +1864,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) static struct ref *local_refs, **local_tail; static struct ref *remote_refs, **remote_tail; -static int one_local_ref(const char *refname, const unsigned char *sha1) +static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct ref *ref; int len = strlen(refname) + 1; @@ -1913,7 +1913,7 @@ static void one_remote_ref(char *refname) static void get_local_heads(void) { local_tail = &local_refs; - for_each_ref(one_local_ref); + for_each_ref(one_local_ref, NULL); } static void get_dav_remote_heads(void) diff --git a/receive-pack.c b/receive-pack.c index f2b1c29bd..de1d6a4b1 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -28,7 +28,7 @@ static int receive_pack_config(const char *var, const char *value) return 0; } -static int show_ref(const char *path, const unsigned char *sha1) +static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { if (capabilities_sent) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); @@ -41,9 +41,9 @@ static int show_ref(const char *path, const unsigned char *sha1) static void write_head_info(void) { - for_each_ref(show_ref); + for_each_ref(show_ref, NULL); if (!capabilities_sent) - show_ref("capabilities^{}", null_sha1); + show_ref("capabilities^{}", null_sha1, 0, NULL); } @@ -57,34 +57,6 @@ struct command { static struct command *commands; -static int is_all_zeroes(const char *hex) -{ - int i; - for (i = 0; i < 40; i++) - if (*hex++ != '0') - return 0; - return 1; -} - -static int verify_old_ref(const char *name, char *hex_contents) -{ - int fd, ret; - char buffer[60]; - - if (is_all_zeroes(hex_contents)) - return 0; - fd = open(name, O_RDONLY); - if (fd < 0) - return -1; - ret = read(fd, buffer, 40); - close(fd); - if (ret != 40) - return -1; - if (memcmp(buffer, hex_contents, 40)) - return -1; - return 0; -} - static char update_hook[] = "hooks/update"; static int run_update_hook(const char *refname, @@ -121,8 +93,8 @@ static int update(struct command *cmd) const char *name = cmd->ref_name; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; - char new_hex[60], *old_hex, *lock_name; - int newfd, namelen, written; + char new_hex[41], old_hex[41]; + struct ref_lock *lock; cmd->error_string = NULL; if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) { @@ -131,13 +103,8 @@ static int update(struct command *cmd) name); } - namelen = strlen(name); - lock_name = xmalloc(namelen + 10); - memcpy(lock_name, name, namelen); - memcpy(lock_name + namelen, ".lock", 6); - strcpy(new_hex, sha1_to_hex(new_sha1)); - old_hex = sha1_to_hex(old_sha1); + strcpy(old_hex, sha1_to_hex(old_sha1)); if (!has_sha1_file(new_sha1)) { cmd->error_string = "bad pack"; return error("unpack should have generated %s, " @@ -158,47 +125,20 @@ static int update(struct command *cmd) return error("denying non-fast forward;" " you should pull first"); } - safe_create_leading_directories(lock_name); - - newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (newfd < 0) { - cmd->error_string = "can't lock"; - return error("unable to create %s (%s)", - lock_name, strerror(errno)); - } - - /* Write the ref with an ending '\n' */ - new_hex[40] = '\n'; - new_hex[41] = 0; - written = write(newfd, new_hex, 41); - /* Remove the '\n' again */ - new_hex[40] = 0; - - close(newfd); - if (written != 41) { - unlink(lock_name); - cmd->error_string = "can't write"; - return error("unable to write %s", lock_name); - } - if (verify_old_ref(name, old_hex) < 0) { - unlink(lock_name); - cmd->error_string = "raced"; - return error("%s changed during push", name); - } if (run_update_hook(name, old_hex, new_hex)) { - unlink(lock_name); cmd->error_string = "hook declined"; return error("hook declined to update %s", name); } - else if (rename(lock_name, name) < 0) { - unlink(lock_name); - cmd->error_string = "can't rename"; - return error("unable to replace %s", name); - } - else { - fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); - return 0; + + lock = lock_any_ref_for_update(name, old_sha1); + if (!lock) { + cmd->error_string = "failed to lock"; + return error("failed to lock %s", name); } + write_ref_sha1(lock, new_sha1, "push"); + + fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); + return 0; } static char update_post_hook[] = "hooks/post-update"; @@ -349,9 +289,10 @@ int main(int argc, char **argv) if (!dir) usage(receive_pack_usage); - if(!enter_repo(dir, 0)) + if (!enter_repo(dir, 0)) die("'%s': unable to chdir or not a git archive", dir); + setup_ident(); git_config(receive_pack_config); write_head_info(); @@ -3,15 +3,193 @@ #include <errno.h> +struct ref_list { + struct ref_list *next; + unsigned char flag; /* ISSYMREF? ISPACKED? */ + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +static const char *parse_ref_line(char *line, unsigned char *sha1) +{ + /* + * 42: the answer to everything. + * + * In this case, it happens to be the answer to + * 40 (length of sha1 hex representation) + * +1 (space in between hex and name) + * +1 (newline at the end of the line) + */ + int len = strlen(line) - 42; + + if (len <= 0) + return NULL; + if (get_sha1_hex(line, sha1) < 0) + return NULL; + if (!isspace(line[40])) + return NULL; + line += 41; + if (isspace(*line)) + return NULL; + if (line[len] != '\n') + return NULL; + line[len] = 0; + return line; +} + +static struct ref_list *add_ref(const char *name, const unsigned char *sha1, + int flag, struct ref_list *list) +{ + int len; + struct ref_list **p = &list, *entry; + + /* Find the place to insert the ref into.. */ + while ((entry = *p) != NULL) { + int cmp = strcmp(entry->name, name); + if (cmp > 0) + break; + + /* Same as existing entry? */ + if (!cmp) + return list; + p = &entry->next; + } + + /* Allocate it and add it in.. */ + len = strlen(name) + 1; + entry = xmalloc(sizeof(struct ref_list) + len); + hashcpy(entry->sha1, sha1); + memcpy(entry->name, name, len); + entry->flag = flag; + entry->next = *p; + *p = entry; + return list; +} + +/* + * Future: need to be in "struct repository" + * when doing a full libification. + */ +struct cached_refs { + char did_loose; + char did_packed; + struct ref_list *loose; + struct ref_list *packed; +} cached_refs; + +static void free_ref_list(struct ref_list *list) +{ + struct ref_list *next; + for ( ; list; list = next) { + next = list->next; + free(list); + } +} + +static void invalidate_cached_refs(void) +{ + struct cached_refs *ca = &cached_refs; + + if (ca->did_loose && ca->loose) + free_ref_list(ca->loose); + if (ca->did_packed && ca->packed) + free_ref_list(ca->packed); + ca->loose = ca->packed = NULL; + ca->did_loose = ca->did_packed = 0; +} + +static struct ref_list *get_packed_refs(void) +{ + if (!cached_refs.did_packed) { + struct ref_list *refs = NULL; + FILE *f = fopen(git_path("packed-refs"), "r"); + if (f) { + struct ref_list *list = NULL; + char refline[PATH_MAX]; + while (fgets(refline, sizeof(refline), f)) { + unsigned char sha1[20]; + const char *name = parse_ref_line(refline, sha1); + if (!name) + continue; + list = add_ref(name, sha1, REF_ISPACKED, list); + } + fclose(f); + refs = list; + } + cached_refs.packed = refs; + cached_refs.did_packed = 1; + } + return cached_refs.packed; +} + +static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) +{ + DIR *dir = opendir(git_path("%s", base)); + + if (dir) { + struct dirent *de; + int baselen = strlen(base); + char *ref = xmalloc(baselen + 257); + + memcpy(ref, base, baselen); + if (baselen && base[baselen-1] != '/') + ref[baselen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + unsigned char sha1[20]; + struct stat st; + int flag; + int namelen; + + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if (namelen > 255) + continue; + if (has_extension(de->d_name, ".lock")) + continue; + memcpy(ref + baselen, de->d_name, namelen+1); + if (stat(git_path("%s", ref), &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) { + list = get_ref_dir(ref, list); + continue; + } + if (!resolve_ref(ref, sha1, 1, &flag)) { + error("%s points nowhere!", ref); + continue; + } + list = add_ref(ref, sha1, flag, list); + } + free(ref); + closedir(dir); + } + return list; +} + +static struct ref_list *get_loose_refs(void) +{ + if (!cached_refs.did_loose) { + cached_refs.loose = get_ref_dir("refs", NULL); + cached_refs.did_loose = 1; + } + return cached_refs.loose; +} + /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -const char *resolve_ref(const char *path, unsigned char *sha1, int reading) +const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag) { int depth = MAXDEPTH, len; char buffer[256]; + static char ref_buffer[256]; + + if (flag) + *flag = 0; for (;;) { + const char *path = git_path("%s", ref); struct stat st; char *buf; int fd; @@ -27,17 +205,31 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) * reading. */ if (lstat(path, &st) < 0) { + struct ref_list *list = get_packed_refs(); + while (list) { + if (!strcmp(ref, list->name)) { + hashcpy(sha1, list->sha1); + if (flag) + *flag |= REF_ISPACKED; + return ref; + } + list = list->next; + } if (reading || errno != ENOENT) return NULL; hashclr(sha1); - return path; + return ref; } /* Follow "normalized" - ie "refs/.." symlinks by hand */ if (S_ISLNK(st.st_mode)) { len = readlink(path, buffer, sizeof(buffer)-1); if (len >= 5 && !memcmp("refs/", buffer, 5)) { - path = git_path("%.*s", len, buffer); + buffer[len] = 0; + strcpy(ref_buffer, buffer); + ref = ref_buffer; + if (flag) + *flag |= REF_ISSYMREF; continue; } } @@ -68,19 +260,24 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) while (len && isspace(*buf)) buf++, len--; while (len && isspace(buf[len-1])) - buf[--len] = 0; - path = git_path("%.*s", len, buf); + len--; + buf[len] = 0; + memcpy(ref_buffer, buf, len + 1); + ref = ref_buffer; + if (flag) + *flag |= REF_ISSYMREF; } if (len < 40 || get_sha1_hex(buffer, sha1)) return NULL; - return path; + return ref; } -int create_symref(const char *git_HEAD, const char *refs_heads_master) +int create_symref(const char *ref_target, const char *refs_heads_master) { const char *lockpath; char ref[1000]; int fd, len, written; + const char *git_HEAD = git_path("%s", ref_target); #ifndef NO_SYMLINK_HEAD if (prefer_symlink_refs) { @@ -118,104 +315,101 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master) return 0; } -int read_ref(const char *filename, unsigned char *sha1) +int read_ref(const char *ref, unsigned char *sha1) { - if (resolve_ref(filename, sha1, 1)) + if (resolve_ref(ref, sha1, 1, NULL)) return 0; return -1; } -static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim) +static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, + void *cb_data) { - int retval = 0; - DIR *dir = opendir(git_path("%s", base)); - - if (dir) { - struct dirent *de; - int baselen = strlen(base); - char *path = xmalloc(baselen + 257); - - if (!strncmp(base, "./", 2)) { - base += 2; - baselen -= 2; + int retval; + struct ref_list *packed = get_packed_refs(); + struct ref_list *loose = get_loose_refs(); + + while (packed && loose) { + struct ref_list *entry; + int cmp = strcmp(packed->name, loose->name); + if (!cmp) { + packed = packed->next; + continue; } - memcpy(path, base, baselen); - if (baselen && base[baselen-1] != '/') - path[baselen++] = '/'; - - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; - struct stat st; - int namelen; + if (cmp > 0) { + entry = loose; + loose = loose->next; + } else { + entry = packed; + packed = packed->next; + } + if (strncmp(base, entry->name, trim)) + continue; + if (is_null_sha1(entry->sha1)) + continue; + if (!has_sha1_file(entry->sha1)) { + error("%s does not point to a valid object!", entry->name); + continue; + } + retval = fn(entry->name + trim, entry->sha1, + entry->flag, cb_data); + if (retval) + return retval; + } - if (de->d_name[0] == '.') - continue; - namelen = strlen(de->d_name); - if (namelen > 255) - continue; - if (has_extension(de->d_name, ".lock")) - continue; - memcpy(path + baselen, de->d_name, namelen+1); - if (stat(git_path("%s", path), &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) { - retval = do_for_each_ref(path, fn, trim); - if (retval) - break; - continue; - } - if (read_ref(git_path("%s", path), sha1) < 0) { - error("%s points nowhere!", path); - continue; - } - if (!has_sha1_file(sha1)) { - error("%s does not point to a valid " - "commit object!", path); - continue; - } - retval = fn(path + trim, sha1); + packed = packed ? packed : loose; + while (packed) { + if (!strncmp(base, packed->name, trim)) { + retval = fn(packed->name + trim, packed->sha1, + packed->flag, cb_data); if (retval) - break; + return retval; } - free(path); - closedir(dir); + packed = packed->next; } - return retval; + return 0; } -int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int head_ref(each_ref_fn fn, void *cb_data) { unsigned char sha1[20]; - if (!read_ref(git_path("HEAD"), sha1)) - return fn("HEAD", sha1); + int flag; + + if (resolve_ref("HEAD", sha1, 1, &flag)) + return fn("HEAD", sha1, flag, cb_data); return 0; } -int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs", fn, 0); + return do_for_each_ref("refs/", fn, 0, cb_data); } -int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_tag_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/tags", fn, 10); + return do_for_each_ref("refs/tags/", fn, 10, cb_data); } -int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_branch_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/heads", fn, 11); + return do_for_each_ref("refs/heads/", fn, 11, cb_data); } -int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_remote_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/remotes", fn, 13); + return do_for_each_ref("refs/remotes/", fn, 13, cb_data); } +/* NEEDSWORK: This is only used by ssh-upload and it should go; the + * caller should do resolve_ref or read_ref like everybody else. Or + * maybe everybody else should use get_ref_sha1() instead of doing + * read_ref(). + */ int get_ref_sha1(const char *ref, unsigned char *sha1) { if (check_ref_format(ref)) return -1; - return read_ref(git_path("refs/%s", ref), sha1); + return read_ref(mkpath("refs/%s", ref), sha1); } /* @@ -273,22 +467,13 @@ int check_ref_format(const char *ref) static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { - char buf[40]; - int nr, fd = open(lock->ref_file, O_RDONLY); - if (fd < 0 && (mustexist || errno != ENOENT)) { - error("Can't verify ref %s", lock->ref_file); - unlock_ref(lock); - return NULL; - } - nr = read(fd, buf, 40); - close(fd); - if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) { - error("Can't verify ref %s", lock->ref_file); + if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) { + error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); return NULL; } if (hashcmp(lock->old_sha1, old_sha1)) { - error("Ref %s is at %s but expected %s", lock->ref_file, + error("Ref %s is at %s but expected %s", lock->ref_name, sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); unlock_ref(lock); return NULL; @@ -296,54 +481,223 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } -static struct ref_lock *lock_ref_sha1_basic(const char *path, - int plen, - const unsigned char *old_sha1, int mustexist) +static int remove_empty_dir_recursive(char *path, int len) +{ + DIR *dir = opendir(path); + struct dirent *e; + int ret = 0; + + if (!dir) + return -1; + if (path[len-1] != '/') + path[len++] = '/'; + while ((e = readdir(dir)) != NULL) { + struct stat st; + int namlen; + if ((e->d_name[0] == '.') && + ((e->d_name[1] == 0) || + ((e->d_name[1] == '.') && e->d_name[2] == 0))) + continue; /* "." and ".." */ + + namlen = strlen(e->d_name); + if ((len + namlen < PATH_MAX) && + strcpy(path + len, e->d_name) && + !lstat(path, &st) && + S_ISDIR(st.st_mode) && + !remove_empty_dir_recursive(path, len + namlen)) + continue; /* happy */ + + /* path too long, stat fails, or non-directory still exists */ + ret = -1; + break; + } + closedir(dir); + if (!ret) { + path[len] = 0; + ret = rmdir(path); + } + return ret; +} + +static int remove_empty_directories(char *file) { - const char *orig_path = path; + /* 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. + */ + char path[PATH_MAX]; + int len = strlen(file); + + if (len >= PATH_MAX) /* path too long ;-) */ + return -1; + strcpy(path, file); + return remove_empty_dir_recursive(path, len); +} + +static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag) +{ + char *ref_file; + const char *orig_ref = ref; struct ref_lock *lock; struct stat st; + int last_errno = 0; + int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - plen = strlen(path) - plen; - path = resolve_ref(path, lock->old_sha1, mustexist); - if (!path) { - int last_errno = errno; + ref = resolve_ref(ref, lock->old_sha1, mustexist, flag); + if (!ref && errno == EISDIR) { + /* 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_ref); + if (remove_empty_directories(ref_file)) { + last_errno = errno; + error("there are still refs under '%s'", orig_ref); + goto error_return; + } + ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag); + } + if (!ref) { + last_errno = errno; error("unable to resolve reference %s: %s", - orig_path, strerror(errno)); - unlock_ref(lock); - errno = last_errno; - return NULL; + orig_ref, strerror(errno)); + goto error_return; + } + if (is_null_sha1(lock->old_sha1)) { + /* The ref did not exist and we are creating it. + * Make sure there is no existing ref that is packed + * whose name begins with our refname, nor a ref whose + * name is a proper prefix of our refname. + */ + int namlen = strlen(ref); /* e.g. 'foo/bar' */ + struct ref_list *list = get_packed_refs(); + while (list) { + /* list->name could be 'foo' or 'foo/bar/baz' */ + int len = strlen(list->name); + int cmplen = (namlen < len) ? namlen : len; + const char *lead = (namlen < len) ? list->name : ref; + + if (!strncmp(ref, list->name, cmplen) && + lead[cmplen] == '/') { + error("'%s' exists; cannot create '%s'", + list->name, ref); + goto error_return; + } + list = list->next; + } } + lock->lk = xcalloc(1, sizeof(struct lock_file)); - lock->ref_file = xstrdup(path); - lock->log_file = xstrdup(git_path("logs/%s", lock->ref_file + plen)); - lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT; + lock->ref_name = xstrdup(ref); + lock->log_file = xstrdup(git_path("logs/%s", ref)); + ref_file = git_path("%s", ref); + lock->force_write = lstat(ref_file, &st) && errno == ENOENT; - if (safe_create_leading_directories(lock->ref_file)) - die("unable to create directory for %s", lock->ref_file); - lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1); + if (safe_create_leading_directories(ref_file)) { + last_errno = errno; + error("unable to create directory for %s", ref_file); + goto error_return; + } + lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1); return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; + + error_return: + unlock_ref(lock); + errno = last_errno; + return NULL; } -struct ref_lock *lock_ref_sha1(const char *ref, - const unsigned char *old_sha1, int mustexist) +struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) { + char refpath[PATH_MAX]; if (check_ref_format(ref)) return NULL; - return lock_ref_sha1_basic(git_path("refs/%s", ref), - 5 + strlen(ref), old_sha1, mustexist); + strcpy(refpath, mkpath("refs/%s", ref)); + return lock_ref_sha1_basic(refpath, old_sha1, NULL); } -struct ref_lock *lock_any_ref_for_update(const char *ref, - const unsigned char *old_sha1, int mustexist) +struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1) +{ + return lock_ref_sha1_basic(ref, old_sha1, NULL); +} + +static struct lock_file packlock; + +static int repack_without_ref(const char *refname) { - return lock_ref_sha1_basic(git_path("%s", ref), - strlen(ref), old_sha1, mustexist); + struct ref_list *list, *packed_ref_list; + int fd; + int found = 0; + + packed_ref_list = get_packed_refs(); + for (list = packed_ref_list; list; list = list->next) { + if (!strcmp(refname, list->name)) { + found = 1; + break; + } + } + if (!found) + return 0; + memset(&packlock, 0, sizeof(packlock)); + fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0); + if (fd < 0) + return error("cannot delete '%s' from packed refs", refname); + + for (list = packed_ref_list; list; list = list->next) { + char line[PATH_MAX + 100]; + int len; + + if (!strcmp(refname, list->name)) + continue; + len = snprintf(line, sizeof(line), "%s %s\n", + sha1_to_hex(list->sha1), list->name); + /* this should not happen but just being defensive */ + if (len > sizeof(line)) + die("too long a refname '%s'", list->name); + write_or_die(fd, line, len); + } + return commit_lock_file(&packlock); +} + +int delete_ref(const char *refname, unsigned char *sha1) +{ + struct ref_lock *lock; + int err, i, ret = 0, flag = 0; + + lock = lock_ref_sha1_basic(refname, sha1, &flag); + if (!lock) + return 1; + if (!(flag & REF_ISPACKED)) { + /* loose */ + i = strlen(lock->lk->filename) - 5; /* .lock */ + lock->lk->filename[i] = 0; + err = unlink(lock->lk->filename); + if (err) { + ret = 1; + error("unlink(%s) failed: %s", + lock->lk->filename, strerror(errno)); + } + lock->lk->filename[i] = '.'; + } + /* removing the loose one could have resurrected an earlier + * packed one. Also, if it was not loose we need to repack + * without it. + */ + ret |= repack_without_ref(refname); + + err = unlink(lock->log_file); + if (err && errno != ENOENT) + fprintf(stderr, "warning: unlink(%s) failed: %s", + lock->log_file, strerror(errno)); + invalidate_cached_refs(); + unlock_ref(lock); + return ret; } void unlock_ref(struct ref_lock *lock) @@ -354,7 +708,7 @@ void unlock_ref(struct ref_lock *lock) if (lock->lk) rollback_lock_file(lock->lk); } - free(lock->ref_file); + free(lock->ref_name); free(lock->log_file); free(lock); } @@ -367,7 +721,8 @@ static int log_ref_write(struct ref_lock *lock, char *logrec; const char *committer; - if (log_all_ref_updates) { + if (log_all_ref_updates && + !strncmp(lock->ref_name, "refs/heads/", 11)) { if (safe_create_leading_directories(lock->log_file) < 0) return error("unable to create directory for %s", lock->log_file); @@ -376,10 +731,20 @@ static int log_ref_write(struct ref_lock *lock, logfd = open(lock->log_file, oflags, 0666); if (logfd < 0) { - if (!log_all_ref_updates && errno == ENOENT) + if (!(oflags & O_CREAT) && errno == ENOENT) return 0; - return error("Unable to append to %s: %s", - lock->log_file, strerror(errno)); + + if ((oflags & O_CREAT) && errno == EISDIR) { + if (remove_empty_directories(lock->log_file)) { + return error("There are still logs under '%s'", + lock->log_file); + } + logfd = open(lock->log_file, oflags, 0666); + } + + if (logfd < 0) + return error("Unable to append to %s: %s", + lock->log_file, strerror(errno)); } committer = git_committer_info(1); @@ -426,12 +791,13 @@ int write_ref_sha1(struct ref_lock *lock, unlock_ref(lock); return -1; } + invalidate_cached_refs(); if (log_ref_write(lock, sha1, logmsg) < 0) { unlock_ref(lock); return -1; } if (commit_lock_file(lock->lk)) { - error("Couldn't set %s", lock->ref_file); + error("Couldn't set %s", lock->ref_name); unlock_ref(lock); return -1; } @@ -440,7 +806,7 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } -int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) +int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1) { const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; char *tz_c; @@ -473,7 +839,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) if (!lastgt) die("Log %s is corrupt.", logfile); date = strtoul(lastgt + 1, &tz_c, 10); - if (date <= at_time) { + if (date <= at_time || cnt == 0) { if (lastrec) { if (get_sha1_hex(lastrec, logged_sha1)) die("Log %s is corrupt.", logfile); @@ -504,6 +870,8 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) return 0; } lastrec = rec; + if (cnt > 0) + cnt--; } rec = logdata; @@ -2,7 +2,7 @@ #define REFS_H struct ref_lock { - char *ref_file; + char *ref_name; char *log_file; struct lock_file *lk; unsigned char old_sha1[20]; @@ -14,20 +14,23 @@ struct ref_lock { * Calls the specified function for each ref file until it returns nonzero, * and returns the value */ -extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)); +#define REF_ISSYMREF 01 +#define REF_ISPACKED 02 +typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); +extern int head_ref(each_ref_fn, void *); +extern int for_each_ref(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 *); /** Reads the refs file specified into sha1 **/ extern int get_ref_sha1(const char *ref, unsigned char *sha1); /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ -extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist); +extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1); /** Locks any ref (for 'HEAD' type refs). */ -extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist); +extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1); /** Release any lock taken but not written. **/ extern void unlock_ref(struct ref_lock *lock); @@ -36,7 +39,7 @@ extern void unlock_ref(struct ref_lock *lock); extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); /** Reads log for the value of ref during at_time. **/ -extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1); +extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1); /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); diff --git a/revision.c b/revision.c index 04fa7e517..3dbc26c49 100644 --- a/revision.c +++ b/revision.c @@ -465,7 +465,7 @@ static void limit_list(struct rev_info *revs) static int all_flags; static struct rev_info *all_revs; -static int handle_one_ref(const char *path, const unsigned char *sha1) +static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *object = get_reference(all_revs, path, sha1, all_flags); add_pending_object(all_revs, object, ""); @@ -476,7 +476,7 @@ static void handle_all(struct rev_info *revs, unsigned flags) { all_revs = revs; all_flags = flags; - for_each_ref(handle_one_ref); + for_each_ref(handle_one_ref, NULL); } static int add_parents_only(struct rev_info *revs, const char *arg, int flags) diff --git a/send-pack.c b/send-pack.c index 5bb123a37..fbd792ccf 100644 --- a/send-pack.c +++ b/send-pack.c @@ -215,7 +215,7 @@ static int ref_newer(const unsigned char *new_sha1, static struct ref *local_refs, **local_tail; static struct ref *remote_refs, **remote_tail; -static int one_local_ref(const char *refname, const unsigned char *sha1) +static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct ref *ref; int len = strlen(refname) + 1; @@ -230,7 +230,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) static void get_local_heads(void) { local_tail = &local_refs; - for_each_ref(one_local_ref); + for_each_ref(one_local_ref, NULL); } static int receive_status(int in) diff --git a/server-info.c b/server-info.c index 2fb8f5710..6cd38be32 100644 --- a/server-info.c +++ b/server-info.c @@ -7,7 +7,7 @@ /* refs */ static FILE *info_ref_fp; -static int add_info_ref(const char *path, const unsigned char *sha1) +static int add_info_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = parse_object(sha1); @@ -34,7 +34,7 @@ static int update_info_refs(int force) info_ref_fp = fopen(path1, "w"); if (!info_ref_fp) return error("unable to update %s", path0); - for_each_ref(add_info_ref); + for_each_ref(add_info_ref, NULL); fclose(info_ref_fp); rename(path1, path0); free(path0); diff --git a/sha1_name.c b/sha1_name.c index 6ffee2208..6d7cd7838 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -247,26 +247,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) NULL }; static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; - const char **p, *pathname; - char *real_path = NULL; - int refs_found = 0, am; - unsigned long at_time = (unsigned long)-1; + const char **p, *ref; + char *real_ref = NULL; + int refs_found = 0; + int at, reflog_len; unsigned char *this_result; unsigned char sha1_from_ref[20]; if (len == 40 && !get_sha1_hex(str, sha1)) return 0; - /* At a given period of time? "@{2 hours ago}" */ - for (am = 1; am < len - 1; am++) { - if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') { - int date_len = len - am - 3; - char *date_spec = xmalloc(date_len + 1); - strlcpy(date_spec, str + am + 2, date_len + 1); - at_time = approxidate(date_spec); - free(date_spec); - len = am; - break; + /* basic@{time or number} format to query ref-log */ + reflog_len = at = 0; + if (str[len-1] == '}') { + for (at = 1; at < len - 1; at++) { + if (str[at] == '@' && str[at+1] == '{') { + reflog_len = (len-1) - (at+2); + len = at; + break; + } } } @@ -276,10 +275,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) for (p = fmt; *p; p++) { this_result = refs_found ? sha1_from_ref : sha1; - pathname = resolve_ref(git_path(*p, len, str), this_result, 1); - if (pathname) { + ref = resolve_ref(mkpath(*p, len, str), this_result, 1, NULL); + if (ref) { if (!refs_found++) - real_path = xstrdup(pathname); + real_ref = xstrdup(ref); if (!warn_ambiguous_refs) break; } @@ -291,14 +290,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (warn_ambiguous_refs && refs_found > 1) fprintf(stderr, warning, len, str); - if (at_time != (unsigned long)-1) { - read_ref_at( - real_path + strlen(git_path(".")) - 1, - at_time, - sha1); + if (reflog_len) { + /* Is it asking for N-th entry, or approxidate? */ + int nth, i; + unsigned long at_time; + for (i = nth = 0; 0 <= nth && i < reflog_len; i++) { + char ch = str[at+2+i]; + if ('0' <= ch && ch <= '9') + nth = nth * 10 + ch - '0'; + else + nth = -1; + } + if (0 <= nth) + at_time = 0; + else + at_time = approxidate(str + at + 2); + read_ref_at(real_ref, at_time, nth, sha1); } - free(real_path); + free(real_ref); return 0; } diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b3b920edb..6a917f2ff 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -30,11 +30,8 @@ rm -f .git/$m test_expect_success \ "fail to create $n" \ "touch .git/$n_dir - git-update-ref $n $A >out 2>err - test "'$? = 1 && - test "" = "$(cat out)" && - grep "error: unable to resolve reference" err && - grep '"$n err" + git-update-ref $n $A >out 2>err"' + test $? != 0' rm -f .git/$n_dir out err test_expect_success \ diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 6907cbcd2..acb54b6a0 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -17,13 +17,10 @@ test_expect_success \ git-commit -m "Initial commit." && HEAD=$(git-rev-parse --verify HEAD)' -test_expect_success \ - 'git branch --help should return success now.' \ - 'git-branch --help' - test_expect_failure \ 'git branch --help should not have created a bogus branch' \ - 'test -f .git/refs/heads/--help' + 'git-branch --help </dev/null >/dev/null 2>/dev/null || : + test -f .git/refs/heads/--help' test_expect_success \ 'git branch abc should create a branch' \ @@ -34,7 +31,7 @@ test_expect_success \ 'git-branch a/b/c && test -f .git/refs/heads/a/b/c' cat >expect <<EOF -0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from HEAD +0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF test_expect_success \ 'git branch -l d/e/f should create a branch and a log' \ diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh new file mode 100755 index 000000000..b1e9f2eed --- /dev/null +++ b/t/t3210-pack-refs.sh @@ -0,0 +1,99 @@ +#!/bin/sh +# +# Copyright (c) 2005 Amos Waterland +# Copyright (c) 2006 Christian Couder +# + +test_description='git pack-refs should not change the branch semantic + +This test runs git pack-refs and git show-ref and checks that the branch +semantic is still the same. +' +. ./test-lib.sh + +echo '[core] logallrefupdates = true' >>.git/config + +test_expect_success \ + 'prepare a trivial repository' \ + 'echo Hello > A && + git-update-index --add A && + git-commit -m "Initial commit." && + HEAD=$(git-rev-parse --verify HEAD)' + +SHA1= + +test_expect_success \ + 'see if git show-ref works as expected' \ + 'git-branch a && + SHA1=`cat .git/refs/heads/a` && + echo "$SHA1 refs/heads/a" >expect && + git-show-ref a >result && + diff expect result' + +test_expect_success \ + 'see if a branch still exists when packed' \ + 'git-branch b && + git-pack-refs --all && + rm .git/refs/heads/b && + echo "$SHA1 refs/heads/b" >expect && + git-show-ref b >result && + diff expect result' + +test_expect_failure \ + 'git branch c/d should barf if branch c exists' \ + 'git-branch c && + git-pack-refs --all && + rm .git/refs/heads/c && + git-branch c/d' + +test_expect_success \ + 'see if a branch still exists after git pack-refs --prune' \ + 'git-branch e && + git-pack-refs --all --prune && + echo "$SHA1 refs/heads/e" >expect && + git-show-ref e >result && + diff expect result' + +test_expect_failure \ + 'see if git pack-refs --prune remove ref files' \ + 'git-branch f && + git-pack-refs --all --prune && + ls .git/refs/heads/f' + +test_expect_success \ + 'git branch g should work when git branch g/h has been deleted' \ + 'git-branch g/h && + git-pack-refs --all --prune && + git-branch -d g/h && + git-branch g && + git-pack-refs --all && + git-branch -d g' + +test_expect_failure \ + 'git branch i/j/k should barf if branch i exists' \ + 'git-branch i && + git-pack-refs --all --prune && + git-branch i/j/k' + +test_expect_success \ + 'test git branch k after branch k/l/m and k/lm have been deleted' \ + 'git-branch k/l && + git-branch k/lm && + git-branch -d k/l && + git-branch k/l/m && + git-branch -d k/l/m && + git-branch -d k/lm && + git-branch k' + +test_expect_success \ + 'test git branch n after some branch deletion and pruning' \ + 'git-branch n/o && + git-branch n/op && + git-branch -d n/o && + git-branch n/o/p && + git-branch -d n/op && + git-pack-refs --all --prune && + git-branch -d n/o/p && + git-branch n' + +test_done diff --git a/upload-pack.c b/upload-pack.c index 9ec377504..ddaa72f0a 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -420,7 +420,7 @@ static void receive_needs(void) } } -static int send_ref(const char *refname, const unsigned char *sha1) +static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { static const char *capabilities = "multi_ack thin-pack side-band side-band-64k ofs-delta"; struct object *o = parse_object(sha1); @@ -448,8 +448,8 @@ static int send_ref(const char *refname, const unsigned char *sha1) static void upload_pack(void) { reset_timeout(); - head_ref(send_ref); - for_each_ref(send_ref); + head_ref(send_ref, NULL); + for_each_ref(send_ref, NULL); packet_flush(1); receive_needs(); if (want_obj.nr) { diff --git a/wt-status.c b/wt-status.c index 3952809c2..7dd68575d 100644 --- a/wt-status.c +++ b/wt-status.c @@ -41,10 +41,8 @@ void wt_status_prepare(struct wt_status *s) s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0; - head = resolve_ref(git_path("HEAD"), sha1, 0); - s->branch = head ? - strdup(head + strlen(get_git_dir()) + 1) : - NULL; + head = resolve_ref("HEAD", sha1, 0, NULL); + s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; s->amend = 0; |