Merge branch 'master' into lj/refs
authorJunio C Hamano <junkio@cox.net>
Mon, 2 Oct 2006 18:49:59 +0000 (11:49 -0700)
committerJunio C Hamano <junkio@cox.net>
Mon, 2 Oct 2006 18:49:59 +0000 (11:49 -0700)
* master: (99 commits)
lock_ref_sha1_basic does not remove empty directories on BSD
git-push: .git/remotes/ file does not require SP after colon
git-mv: invalidate the removed path properly in cache-tree
Makefile: install and clean merge-recur, still.
GIT 1.4.3-rc1
gitweb: tree view: hash_base and hash are now context sensitive
git-diff -B output fix.
fetch: Reset remote refs list each time fetch_main is called
Remove -fPIC which was only needed for Git.xs
Fix approxidate() to understand 12:34 AM/PM are 00:34 and 12:34
git-diff -B output fix.
Make cvsexportcommit remove files.
diff --stat: ensure at least one '-' for deletions, and one '+' for additions
diff --stat=width[,name-width]: allow custom diffstat output width.
gitweb: History: blob and tree are first, then commitdiff, etc
gitweb: Remove redundant "commit" from history
http/ftp: optionally ask curl to not use EPSV command
gitweb: Don't use quotemeta on internally generated strings
gitweb: Add snapshot to shortlog
gitweb: Factor out gitweb_have_snapshot()
...

1  2 
Makefile
builtin-push.c
refs.c
diff --combined Makefile
index 65fb8284f3c9ef210c08711b5ce4796593e72e75,401b893bfaa6a5909b1f8e044401cf4b40002b5b..e826247ccaa5ce2412f3cc10e2e46f7454ae8523
+++ b/Makefile
@@@ -1,11 -1,6 +1,6 @@@
  # The default target of this Makefile is...
  all:
  
- # Define MOZILLA_SHA1 environment variable when running make to make use of
- # a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
- # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
- # choice) has very fast version optimized for i586.
- #
  # Define NO_OPENSSL environment variable if you do not have OpenSSL.
  # This also implies MOZILLA_SHA1.
  #
  # Define ARM_SHA1 environment variable when running make to make use of
  # a bundled SHA1 routine optimized for ARM.
  #
+ # Define MOZILLA_SHA1 environment variable when running make to make use of
+ # a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+ # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+ # choice) has very fast version optimized for i586.
+ #
  # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
  #
  # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
  # Define COLLISION_CHECK below if you believe that SHA1's
  # 1461501637330902918203684832716283019655932542976 hashes do not give you
  # sufficient guarantee that no collisions between objects will ever happen.
+ #
  # Define USE_NSEC below if you want git to care about sub-second file mtimes
  # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
  # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
  # randomly break unless your underlying filesystem supports those sub-second
  # times (my ext3 doesn't).
+ #
  # Define USE_STDEV below if you want git to care about the underlying device
  # change being considered an inode change from the update-cache perspective.
  
@@@ -149,6 -149,12 +149,12 @@@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__pow
  
  ### --- END CONFIGURATION SECTION ---
  
+ # Those must not be GNU-specific; they are shared with perl/ which may
+ # be built by a different compiler. (Note that this is an artifact now
+ # but it still might be nice to keep that distinction.)
+ BASIC_CFLAGS =
+ BASIC_LDFLAGS =
  SCRIPT_SH = \
        git-bisect.sh git-branch.sh git-checkout.sh \
        git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
@@@ -209,7 -215,8 +215,8 @@@ BUILT_INS = 
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
  
  # what 'all' will build and 'install' will install, in gitexecdir
- ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
+ ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) \
+       git-merge-recur$X
  
  # Backward compatibility -- to be removed after 1.0
  PROGRAMS += git-ssh-pull$X git-ssh-push$X
@@@ -271,7 -278,6 +278,7 @@@ BUILTIN_OBJS = 
        builtin-diff-stages.o \
        builtin-diff-tree.o \
        builtin-fmt-merge-msg.o \
 +      builtin-for-each-ref.o \
        builtin-grep.o \
        builtin-init-db.o \
        builtin-log.o \
        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)
LIBS = $(GITLIBS) -lz
EXTLIBS = -lz
  
  #
  # Platform specific tweaks
@@@ -327,14 -331,14 +334,14 @@@ ifeq ($(uname_S),Darwin
        NO_STRLCPY = YesPlease
        ifndef NO_FINK
                ifeq ($(shell test -d /sw/lib && echo y),y)
-                       ALL_CFLAGS += -I/sw/include
-                       ALL_LDFLAGS += -L/sw/lib
+                       BASIC_CFLAGS += -I/sw/include
+                       BASIC_LDFLAGS += -L/sw/lib
                endif
        endif
        ifndef NO_DARWIN_PORTS
                ifeq ($(shell test -d /opt/local/lib && echo y),y)
-                       ALL_CFLAGS += -I/opt/local/include
-                       ALL_LDFLAGS += -L/opt/local/lib
+                       BASIC_CFLAGS += -I/opt/local/include
+                       BASIC_LDFLAGS += -L/opt/local/lib
                endif
        endif
  endif
@@@ -356,7 -360,7 +363,7 @@@ ifeq ($(uname_S),SunOS
        endif
        INSTALL = ginstall
        TAR = gtar
-       ALL_CFLAGS += -D__EXTENSIONS__
+       BASIC_CFLAGS += -D__EXTENSIONS__
  endif
  ifeq ($(uname_O),Cygwin)
        NO_D_TYPE_IN_DIRENT = YesPlease
  endif
  ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
-       ALL_CFLAGS += -I/usr/local/include
-       ALL_LDFLAGS += -L/usr/local/lib
+       BASIC_CFLAGS += -I/usr/local/include
+       BASIC_LDFLAGS += -L/usr/local/lib
  endif
  ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
        NEEDS_LIBICONV = YesPlease
-       ALL_CFLAGS += -I/usr/local/include
-       ALL_LDFLAGS += -L/usr/local/lib
+       BASIC_CFLAGS += -I/usr/local/include
+       BASIC_LDFLAGS += -L/usr/local/lib
  endif
  ifeq ($(uname_S),NetBSD)
        ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
                NEEDS_LIBICONV = YesPlease
        endif
-       ALL_CFLAGS += -I/usr/pkg/include
-       ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib
+       BASIC_CFLAGS += -I/usr/pkg/include
+       BASIC_LDFLAGS += -L/usr/pkg/lib
+       ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib
  endif
  ifeq ($(uname_S),AIX)
        NO_STRCASESTR=YesPlease
@@@ -402,9 -407,9 +410,9 @@@ ifeq ($(uname_S),IRIX64
        NO_STRLCPY = YesPlease
        NO_SOCKADDR_STORAGE=YesPlease
        SHELL_PATH=/usr/gnu/bin/bash
-       ALL_CFLAGS += -DPATH_MAX=1024
+       BASIC_CFLAGS += -DPATH_MAX=1024
        # for now, build 32-bit version
-       ALL_LDFLAGS += -L/usr/lib32
+       BASIC_LDFLAGS += -L/usr/lib32
  endif
  ifneq (,$(findstring arm,$(uname_M)))
        ARM_SHA1 = YesPlease
@@@ -426,7 -431,7 +434,7 @@@ endi
  ifndef NO_CURL
        ifdef CURLDIR
                # This is still problematic -- gcc does not always want -R.
-               ALL_CFLAGS += -I$(CURLDIR)/include
+               BASIC_CFLAGS += -I$(CURLDIR)/include
                CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl
        else
                CURL_LIBCURL = -lcurl
@@@ -447,13 -452,13 +455,13 @@@ ifndef NO_OPENSS
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
                # Again this may be problematic -- gcc does not always want -R.
-               ALL_CFLAGS += -I$(OPENSSLDIR)/include
+               BASIC_CFLAGS += -I$(OPENSSLDIR)/include
                OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
        else
                OPENSSL_LINK =
        endif
  else
-       ALL_CFLAGS += -DNO_OPENSSL
+       BASIC_CFLAGS += -DNO_OPENSSL
        MOZILLA_SHA1 = 1
        OPENSSL_LIBSSL =
  endif
@@@ -465,32 -470,32 +473,32 @@@ endi
  ifdef NEEDS_LIBICONV
        ifdef ICONVDIR
                # Again this may be problematic -- gcc does not always want -R.
-               ALL_CFLAGS += -I$(ICONVDIR)/include
+               BASIC_CFLAGS += -I$(ICONVDIR)/include
                ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
        else
                ICONV_LINK =
        endif
-       LIBS += $(ICONV_LINK) -liconv
+       EXTLIBS += $(ICONV_LINK) -liconv
  endif
  ifdef NEEDS_SOCKET
-       LIBS += -lsocket
+       EXTLIBS += -lsocket
        SIMPLE_LIB += -lsocket
  endif
  ifdef NEEDS_NSL
-       LIBS += -lnsl
+       EXTLIBS += -lnsl
        SIMPLE_LIB += -lnsl
  endif
  ifdef NO_D_TYPE_IN_DIRENT
-       ALL_CFLAGS += -DNO_D_TYPE_IN_DIRENT
+       BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
  endif
  ifdef NO_D_INO_IN_DIRENT
-       ALL_CFLAGS += -DNO_D_INO_IN_DIRENT
+       BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
  endif
  ifdef NO_C99_FORMAT
        ALL_CFLAGS += -DNO_C99_FORMAT
  endif
  ifdef NO_SYMLINK_HEAD
-       ALL_CFLAGS += -DNO_SYMLINK_HEAD
+       BASIC_CFLAGS += -DNO_SYMLINK_HEAD
  endif
  ifdef NO_STRCASESTR
        COMPAT_CFLAGS += -DNO_STRCASESTR
@@@ -513,21 -518,24 +521,24 @@@ ifdef NO_MMA
        COMPAT_OBJS += compat/mmap.o
  endif
  ifdef NO_IPV6
-       ALL_CFLAGS += -DNO_IPV6
+       BASIC_CFLAGS += -DNO_IPV6
  endif
  ifdef NO_SOCKADDR_STORAGE
  ifdef NO_IPV6
-       ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in
+       BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in
  else
-       ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in6
+       BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6
  endif
  endif
  ifdef NO_INET_NTOP
        LIB_OBJS += compat/inet_ntop.o
  endif
+ ifdef NO_INET_PTON
+       LIB_OBJS += compat/inet_pton.o
+ endif
  
  ifdef NO_ICONV
-       ALL_CFLAGS += -DNO_ICONV
+       BASIC_CFLAGS += -DNO_ICONV
  endif
  
  ifdef PPC_SHA1
@@@ -543,12 -551,12 +554,12 @@@ ifdef MOZILLA_SHA
        LIB_OBJS += mozilla-sha1/sha1.o
  else
        SHA1_HEADER = <openssl/sha.h>
-       LIBS += $(LIB_4_CRYPTO)
+       EXTLIBS += $(LIB_4_CRYPTO)
  endif
  endif
  endif
  ifdef NO_ACCURATE_DIFF
-       ALL_CFLAGS += -DNO_ACCURATE_DIFF
+       BASIC_CFLAGS += -DNO_ACCURATE_DIFF
  endif
  
  # Shell quote (do not use $(call) to accommodate ancient setups);
@@@ -566,15 -574,23 +577,23 @@@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PA
  PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
  GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
  
- ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
+ LIBS = $(GITLIBS) $(EXTLIBS)
+ BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
  LIB_OBJS += $(COMPAT_OBJS)
+ ALL_CFLAGS += $(BASIC_CFLAGS)
+ ALL_LDFLAGS += $(BASIC_LDFLAGS)
  export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
  ### Build rules
  
- all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \
-       git-merge-recur$X
+ all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
  
- all:
+ all: perl/Makefile
+       $(MAKE) -C perl
        $(MAKE) -C templates
  
  strip: $(PROGRAMS) git$X
@@@ -608,9 -624,18 +627,18 @@@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %
        chmod +x $@+
        mv $@+ $@
  
- $(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
+ $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile
+ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        rm -f $@ $@+
-       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+       INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
+       sed -e '1{' \
+           -e '        s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+           -e '        h' \
+           -e '        s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
+           -e '        H' \
+           -e '        x' \
+           -e '}' \
+           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $@.perl >$@+
        chmod +x $@+
@@@ -740,6 -765,10 +768,10 @@@ $(XDIFF_LIB): $(XDIFF_OBJS
        rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
  
  
+ perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
+       (cd perl && $(PERL_PATH) Makefile.PL \
+               PREFIX='$(prefix_SQ)')
  doc:
        $(MAKE) -C Documentation all
  
@@@ -802,6 -831,7 +834,7 @@@ install: al
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+       $(MAKE) -C perl install
        $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
        $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
        if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
@@@ -872,7 -902,9 +905,9 @@@ clean
        rm -f $(htmldocs).tar.gz $(manpages).tar.gz
        rm -f gitweb/gitweb.cgi
        $(MAKE) -C Documentation/ clean
-       $(MAKE) -C templates clean
+       [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean
+       rm -f perl/ppport.h perl/Makefile.old
+       $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
        rm -f GIT-VERSION-FILE GIT-CFLAGS
  
diff --combined builtin-push.c
index 581c44ba8e404b80e6a1c2010b501d9bb0642f0b,f5150ed82d269dd825a45cc9c369c71e777bac05..5f7eccf14b272fc108bcd49aece911ebde460b4f
@@@ -27,7 -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 +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)
@@@ -78,12 -78,12 +78,12 @@@ static int get_remotes_uri(const char *
                int is_refspec;
                char *s, *p;
  
-               if (!strncmp("URL: ", buffer, 5)) {
+               if (!strncmp("URL:", buffer, 4)) {
                        is_refspec = 0;
-                       s = buffer + 5;
-               } else if (!strncmp("Push: ", buffer, 6)) {
+                       s = buffer + 4;
+               } else if (!strncmp("Push:", buffer, 5)) {
                        is_refspec = 1;
-                       s = buffer + 6;
+                       s = buffer + 5;
                } else
                        continue;
  
diff --combined refs.c
index aa4c4e0b94585835298ad1785eee77462760e2d6,98327d798375d2bc966f877af227a03070ce5d1b..305c1a92cca8e546a91b578df227212acc96279c
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
  
  #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;
                 * 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;
                        }
                }
  
+               /* Is it a directory? */
+               if (S_ISDIR(st.st_mode)) {
+                       errno = EISDIR;
+                       return NULL;
+               }
                /*
                 * Anything else, just open it and try to use it as
                 * a ref
                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) {
        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);
  }
  
  /*
@@@ -461,13 -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;
        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)
 +{
 +      /* 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)
  {
 -      const char *orig_path = path;
 +      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(git_path("%s", ref),
 -              strlen(ref), old_sha1, mustexist);
 +      return lock_ref_sha1_basic(ref, old_sha1, NULL);
 +}
 +
 +static struct lock_file packlock;
 +
 +static int repack_without_ref(const char *refname)
 +{
 +      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)
                if (lock->lk)
                        rollback_lock_file(lock->lk);
        }
 -      free(lock->ref_file);
 +      free(lock->ref_name);
        free(lock->log_file);
        free(lock);
  }
@@@ -774,13 -426,12 +780,13 @@@ int write_ref_sha1(struct ref_lock *loc
                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;
        }