Merge branch 'fc/at-head'
authorJunio C Hamano <gitster@pobox.com>
Fri, 20 Sep 2013 19:38:10 +0000 (12:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 20 Sep 2013 19:38:10 +0000 (12:38 -0700)
Instead of typing four capital letters "HEAD", you can say "@" now,
e.g. "git log @".

* fc/at-head:
Add new @ shortcut for HEAD
sha1-name: pass len argument to interpret_branch_name()

1  2 
Documentation/revisions.txt
cache.h
refs.c
revision.c
sha1_name.c
index 71dcd12ebd25811ed2c4c41717371f3a97321288,09896a37b1a7b735b0defc9d2c7eb2a14cc713ab..2c06ed34ad2ed72f9cf856fb617e54a1956da599
@@@ -58,6 -58,9 +58,9 @@@ the '$GIT_DIR/refs' directory or from t
  While the ref name encoding is unspecified, UTF-8 is preferred as
  some output processing may assume ref names in UTF-8.
  
+ '@'::
+   '@' alone is a shortcut for 'HEAD'.
  '<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}'::
    A ref followed by the suffix '@' with a date specification
    enclosed in a brace
  
  '<rev>{caret}\{<type>\}', e.g. 'v0.99.8{caret}\{commit\}'::
    A suffix '{caret}' followed by an object type name enclosed in
 -  brace pair means the object
 -  could be a tag, and dereference the tag recursively until an
 -  object of that type is found or the object cannot be
 -  dereferenced anymore (in which case, barf).  '<rev>{caret}0'
 +  brace pair means dereference the object at '<rev>' recursively until
 +  an object of type '<type>' is found or the object cannot be
 +  dereferenced anymore (in which case, barf).
 +  For example, if '<rev>' is a commit-ish, '<rev>{caret}\{commit\}'
 +  describes the corresponding commit object.
 +  Similarly, if '<rev>' is a tree-ish, '<rev>{caret}\{tree\}'
 +  describes the corresponding tree object.
 +  '<rev>{caret}0'
    is a short-hand for '<rev>{caret}\{commit\}'.
  +
  'rev{caret}\{object\}' can be used to make sure 'rev' names an
  object that exists, without requiring 'rev' to be a tag, and
  without dereferencing 'rev'; because a tag is already an object,
  it does not have to be dereferenced even once to get to an object.
 ++
 +'rev{caret}\{tag\}' can be used to ensure that 'rev' identifies an
 +existing tag object.
  
  '<rev>{caret}\{\}', e.g. 'v0.99.8{caret}\{\}'::
    A suffix '{caret}' followed by an empty brace pair
diff --combined cache.h
index a47b9c03036d41e8067a843dda305bc9b6161255,9fbc5fa1304f449f9528542f448ed7fb16704ffa..51d6602cd9da8f8da6b5176ea352574bcc7036f5
+++ b/cache.h
@@@ -101,9 -101,9 +101,9 @@@ unsigned long git_deflate_bound(git_zst
  
  #define CACHE_SIGNATURE 0x44495243    /* "DIRC" */
  struct cache_header {
 -      unsigned int hdr_signature;
 -      unsigned int hdr_version;
 -      unsigned int hdr_entries;
 +      uint32_t hdr_signature;
 +      uint32_t hdr_version;
 +      uint32_t hdr_entries;
  };
  
  #define INDEX_FORMAT_LB 2
   * check it for equality in the 32 bits we save.
   */
  struct cache_time {
 -      unsigned int sec;
 -      unsigned int nsec;
 +      uint32_t sec;
 +      uint32_t nsec;
  };
  
  struct stat_data {
@@@ -189,8 -189,6 +189,8 @@@ struct cache_entry 
  #error "CE_EXTENDED_FLAGS out of range"
  #endif
  
 +struct pathspec;
 +
  /*
   * Copy the sha1 and stat state of a cache entry from one to
   * another. But we never change the name, or the hash state!
@@@ -367,9 -365,6 +367,9 @@@ static inline enum object_type object_t
  #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
  #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
  #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
 +#define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
 +#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
 +#define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
  
  /*
   * This environment variable is expected to contain a boolean indicating
@@@ -417,7 -412,6 +417,7 @@@ extern void setup_work_tree(void)
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
  extern char *prefix_path(const char *prefix, int len, const char *path);
 +extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path);
  extern const char *prefix_filename(const char *prefix, int len, const char *path);
  extern int check_filename(const char *prefix, const char *name);
  extern void verify_filename(const char *prefix,
@@@ -455,7 -449,7 +455,7 @@@ extern void sanitize_stdfds(void)
  
  /* Initialize and use the cache information */
  extern int read_index(struct index_state *);
 -extern int read_index_preload(struct index_state *, const char **pathspec);
 +extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
  extern int read_index_from(struct index_state *, const char *path);
  extern int is_index_unborn(struct index_state *);
  extern int read_index_unmerged(struct index_state *);
@@@ -497,8 -491,28 +497,8 @@@ extern void *read_blob_data_from_index(
  extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  
 -#define PATHSPEC_ONESTAR 1    /* the pathspec pattern satisfies GFNM_ONESTAR */
 -
 -struct pathspec {
 -      const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
 -      int nr;
 -      unsigned int has_wildcard:1;
 -      unsigned int recursive:1;
 -      int max_depth;
 -      struct pathspec_item {
 -              const char *match;
 -              int len;
 -              int nowildcard_len;
 -              int flags;
 -      } *items;
 -};
 -
 -extern int init_pathspec(struct pathspec *, const char **);
 -extern void free_pathspec(struct pathspec *);
  extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
  
 -extern int limit_pathspec_to_literal(void);
 -
  #define HASH_WRITE_OBJECT 1
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
@@@ -526,7 -540,7 +526,7 @@@ extern void fill_stat_cache_info(struc
  #define REFRESH_IGNORE_MISSING        0x0008  /* ignore non-existent */
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
 -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
 +extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
  
  struct lock_file {
        struct lock_file *next;
@@@ -748,7 -762,6 +748,7 @@@ const char *real_path(const char *path)
  const char *real_path_if_valid(const char *path);
  const char *absolute_path(const char *path);
  const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
 +int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
  int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
@@@ -880,7 -893,7 +880,7 @@@ extern char *resolve_refdup(const char 
  
  extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
  extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
- extern int interpret_branch_name(const char *str, struct strbuf *);
+ extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
  extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
@@@ -1025,6 -1038,68 +1025,6 @@@ struct pack_entry 
        struct packed_git *p;
  };
  
 -struct ref {
 -      struct ref *next;
 -      unsigned char old_sha1[20];
 -      unsigned char new_sha1[20];
 -      char *symref;
 -      unsigned int
 -              force:1,
 -              forced_update:1,
 -              deletion:1,
 -              matched:1;
 -
 -      /*
 -       * Order is important here, as we write to FETCH_HEAD
 -       * in numeric order. And the default NOT_FOR_MERGE
 -       * should be 0, so that xcalloc'd structures get it
 -       * by default.
 -       */
 -      enum {
 -              FETCH_HEAD_MERGE = -1,
 -              FETCH_HEAD_NOT_FOR_MERGE = 0,
 -              FETCH_HEAD_IGNORE = 1
 -      } fetch_head_status;
 -
 -      enum {
 -              REF_STATUS_NONE = 0,
 -              REF_STATUS_OK,
 -              REF_STATUS_REJECT_NONFASTFORWARD,
 -              REF_STATUS_REJECT_ALREADY_EXISTS,
 -              REF_STATUS_REJECT_NODELETE,
 -              REF_STATUS_REJECT_FETCH_FIRST,
 -              REF_STATUS_REJECT_NEEDS_FORCE,
 -              REF_STATUS_UPTODATE,
 -              REF_STATUS_REMOTE_REJECT,
 -              REF_STATUS_EXPECTING_REPORT
 -      } status;
 -      char *remote_status;
 -      struct ref *peer_ref; /* when renaming */
 -      char name[FLEX_ARRAY]; /* more */
 -};
 -
 -#define REF_NORMAL    (1u << 0)
 -#define REF_HEADS     (1u << 1)
 -#define REF_TAGS      (1u << 2)
 -
 -extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 -
 -#define CONNECT_VERBOSE       (1u << 0)
 -extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
 -extern int finish_connect(struct child_process *conn);
 -extern int git_connection_is_socket(struct child_process *conn);
 -struct extra_have_objects {
 -      int nr, alloc;
 -      unsigned char (*array)[20];
 -};
 -extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
 -                                   struct ref **list, unsigned int flags,
 -                                   struct extra_have_objects *);
 -extern int server_supports(const char *feature);
 -extern int parse_feature_request(const char *features, const char *feature);
 -extern const char *server_feature_value(const char *feature, int *len_ret);
 -extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
 -
  extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
  
  /* A hook for count-objects to report invalid files in pack directory */
@@@ -1115,7 -1190,6 +1115,7 @@@ extern int git_config_with_options(conf
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
 +extern int64_t git_config_int64(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
  extern int git_config_bool_or_int(const char *, const char *, int *);
  extern int git_config_bool(const char *, const char *);
@@@ -1231,7 -1305,7 +1231,7 @@@ void packet_trace_identity(const char *
   * return 0 if success, 1 - if addition of a file failed and
   * ADD_FILES_IGNORE_ERRORS was specified in flags
   */
 -int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
 +int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
  
  /* diff.c */
  extern int diff_auto_refresh_index;
@@@ -1265,7 -1339,7 +1265,7 @@@ extern int ws_blank_line(const char *li
  #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
 -int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix);
 +int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
  char *alias_lookup(const char *alias);
diff --combined refs.c
index 6383813627ece441aa780c49f71feeaa41145c5c,bfe10e2d17ab915bdd336e07b84dd6344ba22535..ad5d66c8c95ca7e10b020da05b1d11157cee7af2
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -72,6 -72,10 +72,10 @@@ int check_refname_format(const char *re
  {
        int component_len, component_count = 0;
  
+       if (!strcmp(refname, "@"))
+               /* Refname is a single character '@'. */
+               return -1;
        while (1) {
                /* We are at the start of a path component. */
                component_len = check_refname_component(refname, flags);
@@@ -1951,7 -1955,7 +1955,7 @@@ static int remove_empty_directories(con
  static char *substitute_branch_name(const char **string, int *len)
  {
        struct strbuf buf = STRBUF_INIT;
-       int ret = interpret_branch_name(*string, &buf);
+       int ret = interpret_branch_name(*string, *len, &buf);
  
        if (ret == *len) {
                size_t size;
@@@ -2121,12 -2125,11 +2125,12 @@@ struct ref_lock *lock_ref_sha1(const ch
  }
  
  struct ref_lock *lock_any_ref_for_update(const char *refname,
 -                                       const unsigned char *old_sha1, int flags)
 +                                       const unsigned char *old_sha1,
 +                                       int flags, int *type_p)
  {
        if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                return NULL;
 -      return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
 +      return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
  }
  
  /*
@@@ -2414,82 -2417,60 +2418,82 @@@ static int curate_packed_ref_fn(struct 
        return 0;
  }
  
 -static int repack_without_ref(const char *refname)
 +static int repack_without_refs(const char **refnames, int n)
  {
        struct ref_dir *packed;
        struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
        struct string_list_item *ref_to_delete;
 +      int i, removed = 0;
 +
 +      /* Look for a packed ref */
 +      for (i = 0; i < n; i++)
 +              if (get_packed_ref(refnames[i]))
 +                      break;
  
 -      if (!get_packed_ref(refname))
 -              return 0; /* refname does not exist in packed refs */
 +      /* Avoid locking if we have nothing to do */
 +      if (i == n)
 +              return 0; /* no refname exists in packed refs */
  
        if (lock_packed_refs(0)) {
                unable_to_lock_error(git_path("packed-refs"), errno);
 -              return error("cannot delete '%s' from packed refs", refname);
 +              return error("cannot delete '%s' from packed refs", refnames[i]);
        }
        packed = get_packed_refs(&ref_cache);
  
 -      /* Remove refname from the cache: */
 -      if (remove_entry(packed, refname) == -1) {
 +      /* Remove refnames from the cache */
 +      for (i = 0; i < n; i++)
 +              if (remove_entry(packed, refnames[i]) != -1)
 +                      removed = 1;
 +      if (!removed) {
                /*
 -               * The packed entry disappeared while we were
 +               * All packed entries disappeared while we were
                 * acquiring the lock.
                 */
                rollback_packed_refs();
                return 0;
        }
  
 -      /* Remove any other accumulated cruft: */
 +      /* Remove any other accumulated cruft */
        do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
        for_each_string_list_item(ref_to_delete, &refs_to_delete) {
                if (remove_entry(packed, ref_to_delete->string) == -1)
                        die("internal error");
        }
  
 -      /* Write what remains: */
 +      /* Write what remains */
        return commit_packed_refs();
  }
  
 -int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
 +static int repack_without_ref(const char *refname)
  {
 -      struct ref_lock *lock;
 -      int err, i = 0, ret = 0, flag = 0;
 +      return repack_without_refs(&refname, 1);
 +}
  
 -      lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
 -      if (!lock)
 -              return 1;
 +static int delete_ref_loose(struct ref_lock *lock, int flag)
 +{
        if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
                /* loose */
 -              i = strlen(lock->lk->filename) - 5; /* .lock */
 +              int err, i = strlen(lock->lk->filename) - 5; /* .lock */
 +
                lock->lk->filename[i] = 0;
                err = unlink_or_warn(lock->lk->filename);
 -              if (err && errno != ENOENT)
 -                      ret = 1;
 -
                lock->lk->filename[i] = '.';
 +              if (err && errno != ENOENT)
 +                      return 1;
        }
 +      return 0;
 +}
 +
 +int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
 +{
 +      struct ref_lock *lock;
 +      int ret = 0, flag = 0;
 +
 +      lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
 +      if (!lock)
 +              return 1;
 +      ret |= delete_ref_loose(lock, flag);
 +
        /* removing the loose one could have resurrected an earlier
         * packed one.  Also, if it was not loose we need to repack
         * without it.
@@@ -3192,13 -3173,12 +3196,13 @@@ int for_each_reflog(each_ref_fn fn, voi
        return retval;
  }
  
 -int update_ref(const char *action, const char *refname,
 -              const unsigned char *sha1, const unsigned char *oldval,
 -              int flags, enum action_on_err onerr)
 +static struct ref_lock *update_ref_lock(const char *refname,
 +                                      const unsigned char *oldval,
 +                                      int flags, int *type_p,
 +                                      enum action_on_err onerr)
  {
 -      static struct ref_lock *lock;
 -      lock = lock_any_ref_for_update(refname, oldval, flags);
 +      struct ref_lock *lock;
 +      lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
        if (!lock) {
                const char *str = "Cannot lock the ref '%s'.";
                switch (onerr) {
                case DIE_ON_ERR: die(str, refname); break;
                case QUIET_ON_ERR: break;
                }
 -              return 1;
        }
 +      return lock;
 +}
 +
 +static int update_ref_write(const char *action, const char *refname,
 +                          const unsigned char *sha1, struct ref_lock *lock,
 +                          enum action_on_err onerr)
 +{
        if (write_ref_sha1(lock, sha1, action) < 0) {
                const char *str = "Cannot update the ref '%s'.";
                switch (onerr) {
        return 0;
  }
  
 -struct ref *find_ref_by_name(const struct ref *list, const char *name)
 +int update_ref(const char *action, const char *refname,
 +             const unsigned char *sha1, const unsigned char *oldval,
 +             int flags, enum action_on_err onerr)
  {
 -      for ( ; list; list = list->next)
 -              if (!strcmp(list->name, name))
 -                      return (struct ref *)list;
 -      return NULL;
 +      struct ref_lock *lock;
 +      lock = update_ref_lock(refname, oldval, flags, 0, onerr);
 +      if (!lock)
 +              return 1;
 +      return update_ref_write(action, refname, sha1, lock, onerr);
 +}
 +
 +static int ref_update_compare(const void *r1, const void *r2)
 +{
 +      const struct ref_update * const *u1 = r1;
 +      const struct ref_update * const *u2 = r2;
 +      return strcmp((*u1)->ref_name, (*u2)->ref_name);
 +}
 +
 +static int ref_update_reject_duplicates(struct ref_update **updates, int n,
 +                                      enum action_on_err onerr)
 +{
 +      int i;
 +      for (i = 1; i < n; i++)
 +              if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
 +                      const char *str =
 +                              "Multiple updates for ref '%s' not allowed.";
 +                      switch (onerr) {
 +                      case MSG_ON_ERR:
 +                              error(str, updates[i]->ref_name); break;
 +                      case DIE_ON_ERR:
 +                              die(str, updates[i]->ref_name); break;
 +                      case QUIET_ON_ERR:
 +                              break;
 +                      }
 +                      return 1;
 +              }
 +      return 0;
 +}
 +
 +int update_refs(const char *action, const struct ref_update **updates_orig,
 +              int n, enum action_on_err onerr)
 +{
 +      int ret = 0, delnum = 0, i;
 +      struct ref_update **updates;
 +      int *types;
 +      struct ref_lock **locks;
 +      const char **delnames;
 +
 +      if (!updates_orig || !n)
 +              return 0;
 +
 +      /* Allocate work space */
 +      updates = xmalloc(sizeof(*updates) * n);
 +      types = xmalloc(sizeof(*types) * n);
 +      locks = xcalloc(n, sizeof(*locks));
 +      delnames = xmalloc(sizeof(*delnames) * n);
 +
 +      /* Copy, sort, and reject duplicate refs */
 +      memcpy(updates, updates_orig, sizeof(*updates) * n);
 +      qsort(updates, n, sizeof(*updates), ref_update_compare);
 +      ret = ref_update_reject_duplicates(updates, n, onerr);
 +      if (ret)
 +              goto cleanup;
 +
 +      /* Acquire all locks while verifying old values */
 +      for (i = 0; i < n; i++) {
 +              locks[i] = update_ref_lock(updates[i]->ref_name,
 +                                         (updates[i]->have_old ?
 +                                          updates[i]->old_sha1 : NULL),
 +                                         updates[i]->flags,
 +                                         &types[i], onerr);
 +              if (!locks[i]) {
 +                      ret = 1;
 +                      goto cleanup;
 +              }
 +      }
 +
 +      /* Perform updates first so live commits remain referenced */
 +      for (i = 0; i < n; i++)
 +              if (!is_null_sha1(updates[i]->new_sha1)) {
 +                      ret = update_ref_write(action,
 +                                             updates[i]->ref_name,
 +                                             updates[i]->new_sha1,
 +                                             locks[i], onerr);
 +                      locks[i] = NULL; /* freed by update_ref_write */
 +                      if (ret)
 +                              goto cleanup;
 +              }
 +
 +      /* Perform deletes now that updates are safely completed */
 +      for (i = 0; i < n; i++)
 +              if (locks[i]) {
 +                      delnames[delnum++] = locks[i]->ref_name;
 +                      ret |= delete_ref_loose(locks[i], types[i]);
 +              }
 +      ret |= repack_without_refs(delnames, delnum);
 +      for (i = 0; i < delnum; i++)
 +              unlink_or_warn(git_path("logs/%s", delnames[i]));
 +      clear_loose_ref_cache(&ref_cache);
 +
 +cleanup:
 +      for (i = 0; i < n; i++)
 +              if (locks[i])
 +                      unlock_ref(locks[i]);
 +      free(updates);
 +      free(types);
 +      free(locks);
 +      free(delnames);
 +      return ret;
  }
  
  /*
diff --combined revision.c
index 172b0d3b2c090c4b0bcce40b5cb19d5e544a0aed,3ef13841f4ff39cf315b830b0dc4669cdf10ea3e..0173e0148b850bd1a3e2e7e5c652050ade6d5ba4
@@@ -15,7 -15,6 +15,7 @@@
  #include "string-list.h"
  #include "line-log.h"
  #include "mailmap.h"
 +#include "commit-slab.h"
  
  volatile show_early_output_fn_t show_early_output;
  
@@@ -139,7 -138,8 +139,7 @@@ void mark_tree_uninteresting(struct tre
         * We don't care about the tree any more
         * after it has been marked uninteresting.
         */
 -      free(tree->buffer);
 -      tree->buffer = NULL;
 +      free_tree_buffer(tree);
  }
  
  void mark_parents_uninteresting(struct commit *commit)
@@@ -200,7 -200,7 +200,7 @@@ static void add_pending_object_with_mod
                revs->no_walk = 0;
        if (revs->reflog_info && obj->type == OBJ_COMMIT) {
                struct strbuf buf = STRBUF_INIT;
-               int len = interpret_branch_name(name, &buf);
+               int len = interpret_branch_name(name, 0, &buf);
                int st;
  
                if (0 < len && name[len] && buf.len)
@@@ -1372,7 -1372,7 +1372,7 @@@ static void prepare_show_merge(struct r
                        i++;
        }
        free_pathspec(&revs->prune_data);
 -      init_pathspec(&revs->prune_data, prune);
 +      parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC, 0, "", prune);
        revs->limited = 1;
  }
  
@@@ -2120,8 -2120,8 +2120,8 @@@ int setup_revisions(int argc, const cha
                 */
                ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
                prune_data.path[prune_data.nr++] = NULL;
 -              init_pathspec(&revs->prune_data,
 -                            get_pathspec(revs->prefix, prune_data.path));
 +              parse_pathspec(&revs->prune_data, 0, 0,
 +                             revs->prefix, prune_data.path);
        }
  
        if (revs->def == NULL)
                revs->limited = 1;
  
        if (revs->prune_data.nr) {
 -              diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);
 +              copy_pathspec(&revs->pruning.pathspec, &revs->prune_data);
                /* Can't prune commits with rename following: the paths change.. */
                if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
                        revs->prune = 1;
                if (!revs->full_diff)
 -                      diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);
 +                      copy_pathspec(&revs->diffopt.pathspec,
 +                                    &revs->prune_data);
        }
        if (revs->combine_merges)
                revs->ignore_merges = 0;
@@@ -2764,7 -2763,7 +2764,7 @@@ static int commit_match(struct commit *
        return retval;
  }
  
 -static inline int want_ancestry(struct rev_info *revs)
 +static inline int want_ancestry(const struct rev_info *revs)
  {
        return (revs->rewrite_parents || revs->children.name);
  }
@@@ -2821,14 -2820,6 +2821,14 @@@ enum commit_action simplify_commit(stru
        if (action == commit_show &&
            !revs->show_all &&
            revs->prune && revs->dense && want_ancestry(revs)) {
 +              /*
 +               * --full-diff on simplified parents is no good: it
 +               * will show spurious changes from the commits that
 +               * were elided.  So we save the parents on the side
 +               * when --full-diff is in effect.
 +               */
 +              if (revs->full_diff)
 +                      save_parents(revs, commit);
                if (rewrite_parents(revs, commit, rewrite_one) < 0)
                        return commit_error;
        }
@@@ -2848,7 -2839,6 +2848,7 @@@ static struct commit *get_revision_1(st
                free(entry);
  
                if (revs->reflog_info) {
 +                      save_parents(revs, commit);
                        fake_reflog_parent(revs->reflog_info, commit);
                        commit->object.flags &= ~(ADDED | SEEN | SHOWN);
                }
@@@ -3048,8 -3038,6 +3048,8 @@@ struct commit *get_revision(struct rev_
        c = get_revision_internal(revs);
        if (c && revs->graph)
                graph_update(revs->graph, c);
 +      if (!c)
 +              free_saved_parents(revs);
        return c;
  }
  
@@@ -3081,54 -3069,3 +3081,54 @@@ void put_revision_mark(const struct rev
        fputs(mark, stdout);
        putchar(' ');
  }
 +
 +define_commit_slab(saved_parents, struct commit_list *);
 +
 +#define EMPTY_PARENT_LIST ((struct commit_list *)-1)
 +
 +void save_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list **pp;
 +
 +      if (!revs->saved_parents_slab) {
 +              revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents));
 +              init_saved_parents(revs->saved_parents_slab);
 +      }
 +
 +      pp = saved_parents_at(revs->saved_parents_slab, commit);
 +
 +      /*
 +       * When walking with reflogs, we may visit the same commit
 +       * several times: once for each appearance in the reflog.
 +       *
 +       * In this case, save_parents() will be called multiple times.
 +       * We want to keep only the first set of parents.  We need to
 +       * store a sentinel value for an empty (i.e., NULL) parent
 +       * list to distinguish it from a not-yet-saved list, however.
 +       */
 +      if (*pp)
 +              return;
 +      if (commit->parents)
 +              *pp = copy_commit_list(commit->parents);
 +      else
 +              *pp = EMPTY_PARENT_LIST;
 +}
 +
 +struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit)
 +{
 +      struct commit_list *parents;
 +
 +      if (!revs->saved_parents_slab)
 +              return commit->parents;
 +
 +      parents = *saved_parents_at(revs->saved_parents_slab, commit);
 +      if (parents == EMPTY_PARENT_LIST)
 +              return NULL;
 +      return parents;
 +}
 +
 +void free_saved_parents(struct rev_info *revs)
 +{
 +      if (revs->saved_parents_slab)
 +              clear_saved_parents(revs->saved_parents_slab);
 +}
diff --combined sha1_name.c
index 78c093f79e40db5eada30f3bbf6cfe314fb22834,a0b59235a96e05778c3334eeae0d4d1cdb24e11b..0e5fe7f9371d2e6ae5aa9ce99c4b6c2dfb7a456f
@@@ -677,13 -677,11 +677,13 @@@ static int peel_onion(const char *name
                return -1;
  
        sp++; /* beginning of type name, or closing brace for empty */
 -      if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
 +      if (!prefixcmp(sp, "commit}"))
                expected_type = OBJ_COMMIT;
 -      else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
 +      else if (!prefixcmp(sp, "tag}"))
 +              expected_type = OBJ_TAG;
 +      else if (!prefixcmp(sp, "tree}"))
                expected_type = OBJ_TREE;
 -      else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
 +      else if (!prefixcmp(sp, "blob}"))
                expected_type = OBJ_BLOB;
        else if (!prefixcmp(sp, "object}"))
                expected_type = OBJ_ANY;
@@@ -1006,6 -1004,28 +1006,28 @@@ int get_sha1_mb(const char *name, unsig
        return st;
  }
  
+ /* parse @something syntax, when 'something' is not {.*} */
+ static int interpret_empty_at(const char *name, int namelen, int len, struct strbuf *buf)
+ {
+       const char *next;
+       if (len || name[1] == '{')
+               return -1;
+       /* make sure it's a single @, or @@{.*}, not @foo */
+       next = strchr(name + len + 1, '@');
+       if (next && next[1] != '{')
+               return -1;
+       if (!next)
+               next = name + namelen;
+       if (next != name + 1)
+               return -1;
+       strbuf_reset(buf);
+       strbuf_add(buf, "HEAD", 4);
+       return 1;
+ }
  static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
  {
        /* we have extra data, which might need further processing */
        int ret;
  
        strbuf_add(buf, name + len, namelen - len);
-       ret = interpret_branch_name(buf->buf, &tmp);
+       ret = interpret_branch_name(buf->buf, buf->len, &tmp);
        /* that data was not interpreted, remove our cruft */
        if (ret < 0) {
                strbuf_setlen(buf, used);
   * If the input was ok but there are not N branch switches in the
   * reflog, it returns 0.
   */
- int interpret_branch_name(const char *name, struct strbuf *buf)
+ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
  {
        char *cp;
        struct branch *upstream;
-       int namelen = strlen(name);
        int len = interpret_nth_prior_checkout(name, buf);
        int tmp_len;
  
+       if (!namelen)
+               namelen = strlen(name);
        if (!len) {
                return len; /* syntax Ok, not enough switches */
        } else if (len > 0) {
        cp = strchr(name, '@');
        if (!cp)
                return -1;
+       len = interpret_empty_at(name, namelen, cp - name, buf);
+       if (len > 0)
+               return reinterpret(name, namelen, len, buf);
        tmp_len = upstream_mark(cp, namelen - (cp - name));
        if (!tmp_len)
                return -1;
        len = cp + tmp_len - name;
        cp = xstrndup(name, cp - name);
        upstream = branch_get(*cp ? cp : NULL);
  int strbuf_branchname(struct strbuf *sb, const char *name)
  {
        int len = strlen(name);
-       int used = interpret_branch_name(name, sb);
+       int used = interpret_branch_name(name, len, sb);
  
        if (used == len)
                return 0;
@@@ -1132,13 -1160,13 +1162,13 @@@ int get_sha1(const char *name, unsigne
  }
  
  /*
 - * Many callers know that the user meant to name a committish by
 + * Many callers know that the user meant to name a commit-ish by
   * syntactical positions where the object name appears.  Calling this
   * function allows the machinery to disambiguate shorter-than-unique
 - * abbreviated object names between committish and others.
 + * abbreviated object names between commit-ish and others.
   *
   * Note that this does NOT error out when the named object is not a
 - * committish. It is merely to give a hint to the disambiguation
 + * commit-ish. It is merely to give a hint to the disambiguation
   * machinery.
   */
  int get_sha1_committish(const char *name, unsigned char *sha1)