# 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.
### --- 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 \
$(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
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
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
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
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
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
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
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
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
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);
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
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 $@+
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
$(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)'; \
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
#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);
}
/*
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);
}
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;
}