Merge branch 'kn/for-each-tag'
authorJunio C Hamano <gitster@pobox.com>
Mon, 5 Oct 2015 19:30:18 +0000 (12:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 5 Oct 2015 19:30:18 +0000 (12:30 -0700)
The "ref-filter" code was taught about many parts of what "tag -l"
does and then "tag -l" is being reimplemented in terms of "ref-filter".

* kn/for-each-tag:
tag.c: implement '--merged' and '--no-merged' options
tag.c: implement '--format' option
tag.c: use 'ref-filter' APIs
tag.c: use 'ref-filter' data structures
ref-filter: add option to match literal pattern
ref-filter: add support to sort by version
ref-filter: add support for %(contents:lines=X)
ref-filter: add option to filter out tags, branches and remotes
ref-filter: implement an `align` atom
ref-filter: introduce match_atom_name()
ref-filter: introduce handler function for each atom
utf8: add function to align a string into given strbuf
ref-filter: introduce ref_formatting_state and ref_formatting_stack
ref-filter: move `struct atom_value` to ref-filter.c
strtoul_ui: reject negative values

1  2 
Documentation/git-for-each-ref.txt
builtin/tag.c
git-compat-util.h
refs.c
refs.h
index d6a1abcca5e8bb451595f4a2bc0d9852496584b7,16b4ac561438c259279397bcb89da54cbb33af2d..c6f073cea42a2a91fba1aeb17505fe6cd7f46ac8
@@@ -127,6 -127,17 +127,17 @@@ color:
        Change output color.  Followed by `:<colorname>`, where names
        are described in `color.branch.*`.
  
+ align::
+       Left-, middle-, or right-align the content between
+       %(align:...) and %(end). The "align:" is followed by `<width>`
+       and `<position>` in any order separated by a comma, where the
+       `<position>` is either left, right or middle, default being
+       left and `<width>` is the total length of the content with
+       alignment. If the contents length is more than the width then
+       no alignment is performed. If used with '--quote' everything
+       in between %(align:...) and %(end) is quoted, but if nested
+       then only the topmost level performs quoting.
  In addition to the above, for commit and tag objects, the header
  field names (`tree`, `parent`, `object`, `type`, and `tag`) can
  be used to specify the value in the header field.
@@@ -139,19 -150,24 +150,23 @@@ The complete message in a commit and ta
  Its first line is `contents:subject`, where subject is the concatenation
  of all lines of the commit message up to the first blank line.  The next
  line is 'contents:body', where body is all of the lines after the first
- blank line.  Finally, the optional GPG signature is `contents:signature`.
+ blank line.  The optional GPG signature is `contents:signature`.  The
+ first `N` lines of the message is obtained using `contents:lines=N`.
  
  For sorting purposes, fields with numeric values sort in numeric
  order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
  All other fields are used to sort in their byte-value order.
  
+ There is also an option to sort by versions, this can be done by using
+ the fieldname `version:refname` or its alias `v:refname`.
  In any case, a field name that refers to a field inapplicable to
  the object referred by the ref does not cause an error.  It
  returns an empty string instead.
  
  As a special case for the date-type fields, you may specify a format for
 -the date by adding one of `:default`, `:relative`, `:short`, `:local`,
 -`:iso8601`, `:rfc2822` or `:raw` to the end of the fieldname; e.g.
 -`%(taggerdate:relative)`.
 +the date by adding `:` followed by date format name (see the
 +values the `--date` option to linkgit::git-rev-list[1] takes).
  
  
  EXAMPLES
diff --combined builtin/tag.c
index aaae358c2e212968446659200bd468dd7989c54a,b2e4ddca07ce7a812203259cfdfc32c187d4d2e4..9e17dca7cac30738c966d0e23258f56c0cb9bc70
  #include "gpg-interface.h"
  #include "sha1-array.h"
  #include "column.h"
+ #include "ref-filter.h"
  
  static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
-               "\n\t\t[<pattern>...]"),
+               "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
        N_("git tag -v <tagname>..."),
        NULL
  };
  
- #define STRCMP_SORT     0     /* must be zero */
- #define VERCMP_SORT     1
- #define SORT_MASK       0x7fff
- #define REVERSE_SORT    0x8000
- static int tag_sort;
- struct tag_filter {
-       const char **patterns;
-       int lines;
-       int sort;
-       struct string_list tags;
-       struct commit_list *with_commit;
- };
- static struct sha1_array points_at;
  static unsigned int colopts;
  
- static int match_pattern(const char **patterns, const char *ref)
- {
-       /* no pattern means match everything */
-       if (!*patterns)
-               return 1;
-       for (; *patterns; patterns++)
-               if (!wildmatch(*patterns, ref, 0, NULL))
-                       return 1;
-       return 0;
- }
- /*
-  * This is currently duplicated in ref-filter.c, and will eventually be
-  * removed as we port tag.c to use the ref-filter APIs.
-  */
- static const unsigned char *match_points_at(const char *refname,
-                                           const unsigned char *sha1)
- {
-       const unsigned char *tagged_sha1 = NULL;
-       struct object *obj;
-       if (sha1_array_lookup(&points_at, sha1) >= 0)
-               return sha1;
-       obj = parse_object(sha1);
-       if (!obj)
-               die(_("malformed object at '%s'"), refname);
-       if (obj->type == OBJ_TAG)
-               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
-       if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
-               return tagged_sha1;
-       return NULL;
- }
- static int in_commit_list(const struct commit_list *want, struct commit *c)
- {
-       for (; want; want = want->next)
-               if (!hashcmp(want->item->object.sha1, c->object.sha1))
-                       return 1;
-       return 0;
- }
- /*
-  * The entire code segment for supporting the --contains option has been
-  * copied over to ref-filter.{c,h}. This will be deleted evetually when
-  * we port tag.c to use ref-filter APIs.
-  */
- enum contains_result {
-       CONTAINS_UNKNOWN = -1,
-       CONTAINS_NO = 0,
-       CONTAINS_YES = 1
- };
- /*
-  * Test whether the candidate or one of its parents is contained in the list.
-  * Do not recurse to find out, though, but return -1 if inconclusive.
-  */
- static enum contains_result contains_test(struct commit *candidate,
-                           const struct commit_list *want)
- {
-       /* was it previously marked as containing a want commit? */
-       if (candidate->object.flags & TMP_MARK)
-               return 1;
-       /* or marked as not possibly containing a want commit? */
-       if (candidate->object.flags & UNINTERESTING)
-               return 0;
-       /* or are we it? */
-       if (in_commit_list(want, candidate)) {
-               candidate->object.flags |= TMP_MARK;
-               return 1;
-       }
-       if (parse_commit(candidate) < 0)
-               return 0;
-       return -1;
- }
- /*
-  * Mimicking the real stack, this stack lives on the heap, avoiding stack
-  * overflows.
-  *
-  * At each recursion step, the stack items points to the commits whose
-  * ancestors are to be inspected.
-  */
- struct stack {
-       int nr, alloc;
-       struct stack_entry {
-               struct commit *commit;
-               struct commit_list *parents;
-       } *stack;
- };
- static void push_to_stack(struct commit *candidate, struct stack *stack)
- {
-       int index = stack->nr++;
-       ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
-       stack->stack[index].commit = candidate;
-       stack->stack[index].parents = candidate->parents;
- }
- static enum contains_result contains(struct commit *candidate,
-               const struct commit_list *want)
- {
-       struct stack stack = { 0, 0, NULL };
-       int result = contains_test(candidate, want);
-       if (result != CONTAINS_UNKNOWN)
-               return result;
-       push_to_stack(candidate, &stack);
-       while (stack.nr) {
-               struct stack_entry *entry = &stack.stack[stack.nr - 1];
-               struct commit *commit = entry->commit;
-               struct commit_list *parents = entry->parents;
-               if (!parents) {
-                       commit->object.flags |= UNINTERESTING;
-                       stack.nr--;
-               }
-               /*
-                * If we just popped the stack, parents->item has been marked,
-                * therefore contains_test will return a meaningful 0 or 1.
-                */
-               else switch (contains_test(parents->item, want)) {
-               case CONTAINS_YES:
-                       commit->object.flags |= TMP_MARK;
-                       stack.nr--;
-                       break;
-               case CONTAINS_NO:
-                       entry->parents = parents->next;
-                       break;
-               case CONTAINS_UNKNOWN:
-                       push_to_stack(parents->item, &stack);
-                       break;
-               }
-       }
-       free(stack.stack);
-       return contains_test(candidate, want);
- }
- static void show_tag_lines(const struct object_id *oid, int lines)
+ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
  {
+       struct ref_array array;
+       char *to_free = NULL;
        int i;
-       unsigned long size;
-       enum object_type type;
-       char *buf, *sp, *eol;
-       size_t len;
-       buf = read_sha1_file(oid->hash, &type, &size);
-       if (!buf)
-               die_errno("unable to read object %s", oid_to_hex(oid));
-       if (type != OBJ_COMMIT && type != OBJ_TAG)
-               goto free_return;
-       if (!size)
-               die("an empty %s object %s?",
-                   typename(type), oid_to_hex(oid));
-       /* skip header */
-       sp = strstr(buf, "\n\n");
-       if (!sp)
-               goto free_return;
-       /* only take up to "lines" lines, and strip the signature from a tag */
-       if (type == OBJ_TAG)
-               size = parse_signature(buf, size);
-       for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
-               if (i)
-                       printf("\n    ");
-               eol = memchr(sp, '\n', size - (sp - buf));
-               len = eol ? eol - sp : size - (sp - buf);
-               fwrite(sp, len, 1, stdout);
-               if (!eol)
-                       break;
-               sp = eol + 1;
-       }
- free_return:
-       free(buf);
- }
- static int show_reference(const char *refname, const struct object_id *oid,
-                         int flag, void *cb_data)
- {
-       struct tag_filter *filter = cb_data;
  
-       if (match_pattern(filter->patterns, refname)) {
-               if (filter->with_commit) {
-                       struct commit *commit;
+       memset(&array, 0, sizeof(array));
  
-                       commit = lookup_commit_reference_gently(oid->hash, 1);
-                       if (!commit)
-                               return 0;
-                       if (!contains(commit, filter->with_commit))
-                               return 0;
-               }
-               if (points_at.nr && !match_points_at(refname, oid->hash))
-                       return 0;
+       if (filter->lines == -1)
+               filter->lines = 0;
  
-               if (!filter->lines) {
-                       if (filter->sort)
-                               string_list_append(&filter->tags, refname);
-                       else
-                               printf("%s\n", refname);
-                       return 0;
-               }
-               printf("%-15s ", refname);
-               show_tag_lines(oid, filter->lines);
-               putchar('\n');
+       if (!format) {
+               if (filter->lines) {
+                       to_free = xstrfmt("%s %%(contents:lines=%d)",
+                                         "%(align:15)%(refname:short)%(end)",
+                                         filter->lines);
+                       format = to_free;
+               } else
+                       format = "%(refname:short)";
        }
  
-       return 0;
- }
+       verify_ref_format(format);
+       filter_refs(&array, filter, FILTER_REFS_TAGS);
+       ref_array_sort(sorting, &array);
  
- static int sort_by_version(const void *a_, const void *b_)
- {
-       const struct string_list_item *a = a_;
-       const struct string_list_item *b = b_;
-       return versioncmp(a->string, b->string);
- }
+       for (i = 0; i < array.nr; i++)
+               show_ref_array_item(array.items[i], format, 0);
+       ref_array_clear(&array);
+       free(to_free);
  
- static int list_tags(const char **patterns, int lines,
-                    struct commit_list *with_commit, int sort)
- {
-       struct tag_filter filter;
-       filter.patterns = patterns;
-       filter.lines = lines;
-       filter.sort = sort;
-       filter.with_commit = with_commit;
-       memset(&filter.tags, 0, sizeof(filter.tags));
-       filter.tags.strdup_strings = 1;
-       for_each_tag_ref(show_reference, (void *)&filter);
-       if (sort) {
-               int i;
-               if ((sort & SORT_MASK) == VERCMP_SORT)
-                       qsort(filter.tags.items, filter.tags.nr,
-                             sizeof(struct string_list_item), sort_by_version);
-               if (sort & REVERSE_SORT)
-                       for (i = filter.tags.nr - 1; i >= 0; i--)
-                               printf("%s\n", filter.tags.items[i].string);
-               else
-                       for (i = 0; i < filter.tags.nr; i++)
-                               printf("%s\n", filter.tags.items[i].string);
-               string_list_clear(&filter.tags, 0);
-       }
        return 0;
  }
  
@@@ -357,35 -126,26 +126,26 @@@ static const char tag_template_nocleanu
        "Lines starting with '%c' will be kept; you may remove them"
        " yourself if you want to.\n");
  
- /*
-  * Parse a sort string, and return 0 if parsed successfully. Will return
-  * non-zero when the sort string does not parse into a known type. If var is
-  * given, the error message becomes a warning and includes information about
-  * the configuration value.
-  */
- static int parse_sort_string(const char *var, const char *arg, int *sort)
+ /* Parse arg given and add it the ref_sorting array */
+ static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
  {
-       int type = 0, flags = 0;
-       if (skip_prefix(arg, "-", &arg))
-               flags |= REVERSE_SORT;
+       struct ref_sorting *s;
+       int len;
  
-       if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
-               type = VERCMP_SORT;
-       else
-               type = STRCMP_SORT;
+       s = xcalloc(1, sizeof(*s));
+       s->next = *sorting_tail;
+       *sorting_tail = s;
  
-       if (strcmp(arg, "refname")) {
-               if (!var)
-                       return error(_("unsupported sort specification '%s'"), arg);
-               else {
-                       warning(_("unsupported sort specification '%s' in variable '%s'"),
-                               var, arg);
-                       return -1;
-               }
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
        }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
  
-       *sort = (type | flags);
+       len = strlen(arg);
+       s->atom = parse_ref_filter_atom(arg, arg+len);
  
        return 0;
  }
  static int git_tag_config(const char *var, const char *value, void *cb)
  {
        int status;
+       struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
  
        if (!strcmp(var, "tag.sort")) {
                if (!value)
                        return config_error_nonbool(var);
-               parse_sort_string(var, value, &tag_sort);
+               parse_sorting_string(value, sorting_tail);
                return 0;
        }
  
@@@ -555,13 -316,6 +316,6 @@@ static int strbuf_check_tag_ref(struct 
        return check_refname_format(sb->buf, 0);
  }
  
- static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
- {
-       int *sort = opt->value;
-       return parse_sort_string(NULL, arg, sort);
- }
  int cmd_tag(int argc, const char **argv, const char *prefix)
  {
        struct strbuf buf = STRBUF_INIT;
        const char *object_ref, *tag;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
-       int annotate = 0, force = 0, lines = -1;
        int create_reflog = 0;
+       int annotate = 0, force = 0;
        int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
-       struct commit_list *with_commit = NULL;
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
+       struct ref_filter filter;
+       static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+       const char *format = NULL;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
-               { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
+               { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
                                N_("print <n> lines of each tag message"),
                                PARSE_OPT_OPTARG, NULL, 1 },
                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
                OPT_STRING('u', "local-user", &keyid, N_("key-id"),
                                        N_("use another key to sign the tag")),
                OPT__FORCE(&force, N_("replace the tag if exists")),
 -              OPT_BOOL(0, "create-reflog", &create_reflog, N_("create_reflog")),
 +              OPT_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")),
  
                OPT_GROUP(N_("Tag listing options")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
-               OPT_CONTAINS(&with_commit, N_("print only tags that contain the commit")),
-               OPT_WITH(&with_commit, N_("print only tags that contain the commit")),
-               {
-                       OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
-                       PARSE_OPT_NONEG, parse_opt_sort
-               },
+               OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_MERGED(&filter, N_("print only tags that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
+               OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+                            N_("field name to sort on"), &parse_opt_ref_sorting),
                {
-                       OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"),
+                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
                        N_("print only tags of the object"), 0, parse_opt_object_name
                },
+               OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_END()
        };
  
-       git_config(git_tag_config, NULL);
+       git_config(git_tag_config, sorting_tail);
  
        memset(&opt, 0, sizeof(opt));
+       memset(&filter, 0, sizeof(filter));
+       filter.lines = -1;
  
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
  
                usage_with_options(git_tag_usage, options);
  
        finalize_colopts(&colopts, -1);
-       if (cmdmode == 'l' && lines != -1) {
+       if (cmdmode == 'l' && filter.lines != -1) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and -n are incompatible"));
                colopts = 0;
        }
+       if (!sorting)
+               sorting = ref_default_sorting();
        if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
-               if (lines != -1 && tag_sort)
-                       die(_("--sort and -n are incompatible"));
-               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
+               filter.name_patterns = argv;
+               ret = list_tags(&filter, sorting, format);
                if (column_active(colopts))
                        stop_column_filter();
                return ret;
        }
-       if (lines != -1)
+       if (filter.lines != -1)
                die(_("-n option is only allowed with -l."));
-       if (with_commit)
+       if (filter.with_commit)
                die(_("--contains option is only allowed with -l."));
-       if (points_at.nr)
+       if (filter.points_at.nr)
                die(_("--points-at option is only allowed with -l."));
+       if (filter.merge_commit)
+               die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
                return for_each_tag_name(argv, delete_tag);
        if (cmdmode == 'v')
diff --combined git-compat-util.h
index f649e81f1107722f4c7d051201920ae2a0e7846a,4515c494ee3873c0668be9d52892b14386390b7e..1df82fa598daa131c9e84023083a014ac3ee3f9f
@@@ -389,6 -389,7 +389,6 @@@ struct strbuf
  
  /* General helper functions */
  extern void vreportf(const char *prefix, const char *err, va_list params);
 -extern void vwritef(int fd, const char *prefix, const char *err, va_list params);
  extern NORETURN void usage(const char *err);
  extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2)));
  extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));
@@@ -424,7 -425,6 +424,7 @@@ static inline int const_error(void
  extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
  extern void set_error_routine(void (*routine)(const char *err, va_list params));
  extern void set_die_is_recursing_routine(int (*routine)(void));
 +extern void set_error_handle(FILE *);
  
  extern int starts_with(const char *str, const char *prefix);
  
@@@ -717,12 -717,10 +717,12 @@@ extern void *xrealloc(void *ptr, size_
  extern void *xcalloc(size_t nmemb, size_t size);
  extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
 +extern int xopen(const char *path, int flags, ...);
  extern ssize_t xread(int fd, void *buf, size_t len);
  extern ssize_t xwrite(int fd, const void *buf, size_t len);
  extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset);
  extern int xdup(int fd);
 +extern FILE *xfopen(const char *path, const char *mode);
  extern FILE *xfdopen(int fd, const char *mode);
  extern int xmkstemp(char *template);
  extern int xmkstemp_mode(char *template, int mode);
@@@ -814,6 -812,9 +814,9 @@@ static inline int strtoul_ui(char cons
        char *p;
  
        errno = 0;
+       /* negative values would be accepted by strtoul */
+       if (strchr(s, '-'))
+               return -1;
        ul = strtoul(s, &p, base);
        if (errno || *p || p == s || (unsigned int) ul != ul)
                return -1;
diff --combined refs.c
index 24401f71f8a8dd9fc336b91bff974633727a155f,943ac5ef519538368ac5eb49316c34ce3e36ba07..91c88bad4a764e20c810276fc5b9c1689ba328c3
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -304,11 -304,6 +304,11 @@@ struct ref_entry 
  };
  
  static void read_loose_refs(const char *dirname, struct ref_dir *dir);
 +static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len);
 +static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache,
 +                                        const char *dirname, size_t len,
 +                                        int incomplete);
 +static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
  
  static struct ref_dir *get_ref_dir(struct ref_entry *entry)
  {
        dir = &entry->u.subdir;
        if (entry->flag & REF_INCOMPLETE) {
                read_loose_refs(entry->name, dir);
 +
 +              /*
 +               * Manually add refs/bisect, which, being
 +               * per-worktree, might not appear in the directory
 +               * listing for refs/ in the main repo.
 +               */
 +              if (!strcmp(entry->name, "refs/")) {
 +                      int pos = search_ref_dir(dir, "refs/bisect/", 12);
 +                      if (pos < 0) {
 +                              struct ref_entry *child_entry;
 +                              child_entry = create_dir_entry(dir->ref_cache,
 +                                                             "refs/bisect/",
 +                                                             12, 1);
 +                              add_entry_to_dir(dir, child_entry);
 +                              read_loose_refs("refs/bisect",
 +                                              &child_entry->u.subdir);
 +                      }
 +              }
                entry->flag &= ~REF_INCOMPLETE;
        }
        return dir;
@@@ -2131,6 -2108,15 +2131,15 @@@ int for_each_ref_in(const char *prefix
        return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
  }
  
+ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
+ {
+       unsigned int flag = 0;
+       if (broken)
+               flag = DO_FOR_EACH_INCLUDE_BROKEN;
+       return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);
+ }
  int for_each_ref_in_submodule(const char *submodule, const char *prefix,
                each_ref_fn fn, void *cb_data)
  {
@@@ -2672,8 -2658,6 +2681,8 @@@ struct pack_refs_cb_data 
        struct ref_to_prune *ref_to_prune;
  };
  
 +static int is_per_worktree_ref(const char *refname);
 +
  /*
   * An each_ref_entry_fn that is run over loose references only.  If
   * the loose reference can be packed, add an entry in the packed ref
@@@ -2687,10 -2671,6 +2696,10 @@@ static int pack_if_possible_fn(struct r
        struct ref_entry *packed_entry;
        int is_tag_ref = starts_with(entry->name, "refs/tags/");
  
 +      /* Do not pack per-worktree refs: */
 +      if (is_per_worktree_ref(entry->name))
 +              return 0;
 +
        /* ALWAYS pack tags */
        if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
                return 0;
@@@ -2883,118 -2863,12 +2892,118 @@@ static int delete_ref_loose(struct ref_
        return 0;
  }
  
 +static int is_per_worktree_ref(const char *refname)
 +{
 +      return !strcmp(refname, "HEAD") ||
 +              starts_with(refname, "refs/bisect/");
 +}
 +
 +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;
  
 +      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, old_sha1,
@@@ -3431,7 -3305,6 +3440,7 @@@ static int write_ref_to_lockfile(struc
  {
        static char term = '\n';
        struct object *o;
 +      int fd;
  
        o = parse_object(sha1);
        if (!o) {
                unlock_ref(lock);
                return -1;
        }
 -      if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
 -          write_in_full(lock->lk->fd, &term, 1) != 1 ||
 +      fd = get_lock_file_fd(lock->lk);
 +      if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 ||
 +          write_in_full(fd, &term, 1) != 1 ||
            close_ref(lock) < 0) {
                strbuf_addf(err,
 -                          "Couldn't write %s", lock->lk->filename.buf);
 +                          "Couldn't write %s", get_lock_file_path(lock->lk));
                unlock_ref(lock);
                return -1;
        }
@@@ -4098,25 -3970,17 +4107,25 @@@ int update_ref(const char *msg, const c
               const unsigned char *new_sha1, const unsigned char *old_sha1,
               unsigned int flags, enum action_on_err onerr)
  {
 -      struct ref_transaction *t;
 +      struct ref_transaction *t = NULL;
        struct strbuf err = STRBUF_INIT;
 +      int ret = 0;
  
 -      t = ref_transaction_begin(&err);
 -      if (!t ||
 -          ref_transaction_update(t, refname, new_sha1, old_sha1,
 -                                 flags, msg, &err) ||
 -          ref_transaction_commit(t, &err)) {
 +      if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 +              ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
 +      } else {
 +              t = ref_transaction_begin(&err);
 +              if (!t ||
 +                  ref_transaction_update(t, refname, new_sha1, old_sha1,
 +                                         flags, msg, &err) ||
 +                  ref_transaction_commit(t, &err)) {
 +                      ret = 1;
 +                      ref_transaction_free(t);
 +              }
 +      }
 +      if (ret) {
                const char *str = "update_ref failed for ref '%s': %s";
  
 -              ref_transaction_free(t);
                switch (onerr) {
                case UPDATE_REFS_MSG_ON_ERR:
                        error(str, refname, err.buf);
                return 1;
        }
        strbuf_release(&err);
 -      ref_transaction_free(t);
 +      if (t)
 +              ref_transaction_free(t);
        return 0;
  }
  
@@@ -4525,25 -4388,17 +4534,25 @@@ int parse_hide_refs_config(const char *
  
  int ref_is_hidden(const char *refname)
  {
 -      struct string_list_item *item;
 +      int i;
  
        if (!hide_refs)
                return 0;
 -      for_each_string_list_item(item, hide_refs) {
 +      for (i = hide_refs->nr - 1; i >= 0; i--) {
 +              const char *match = hide_refs->items[i].string;
 +              int neg = 0;
                int len;
 -              if (!starts_with(refname, item->string))
 +
 +              if (*match == '!') {
 +                      neg = 1;
 +                      match++;
 +              }
 +
 +              if (!starts_with(refname, match))
                        continue;
 -              len = strlen(item->string);
 +              len = strlen(match);
                if (!refname[len] || refname[len] == '/')
 -                      return 1;
 +                      return !neg;
        }
        return 0;
  }
@@@ -4640,7 -4495,7 +4649,7 @@@ int reflog_expire(const char *refname, 
                cb.newlog = fdopen_lock_file(&reflog_lock, "w");
                if (!cb.newlog) {
                        error("cannot fdopen %s (%s)",
 -                            reflog_lock.filename.buf, strerror(errno));
 +                            get_lock_file_path(&reflog_lock), strerror(errno));
                        goto failure;
                }
        }
                        status |= error("couldn't write %s: %s", log_file,
                                        strerror(errno));
                } else if (update &&
 -                         (write_in_full(lock->lk->fd,
 +                         (write_in_full(get_lock_file_fd(lock->lk),
                                sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
 -                       write_str_in_full(lock->lk->fd, "\n") != 1 ||
 -                       close_ref(lock) < 0)) {
 +                          write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 ||
 +                          close_ref(lock) < 0)) {
                        status |= error("couldn't write %s",
 -                                      lock->lk->filename.buf);
 +                                      get_lock_file_path(lock->lk));
                        rollback_lock_file(&reflog_lock);
                } else if (commit_lock_file(&reflog_lock)) {
                        status |= error("unable to commit reflog '%s' (%s)",
diff --combined refs.h
index e9a5f3230ab09b667e7ae28b2b0bcd26bd2f067e,02676498328d348bba0a28fe023eb34909264e79..6d30c980d182f27ab78d43342c0865df7906693c
--- 1/refs.h
--- 2/refs.h
+++ b/refs.h
@@@ -173,6 -173,7 +173,7 @@@ typedef int each_ref_fn(const char *ref
  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_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken);
  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);
@@@ -445,14 -446,6 +446,14 @@@ extern int parse_hide_refs_config(cons
  
  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,