From d5fdae67379778502ca8b3b3186ce4692d912e30 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 25 Apr 2012 00:45:07 +0200 Subject: [PATCH 01/54] get_ref_dir(): return early if directory cannot be read Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 85 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/refs.c b/refs.c index 09322fede0..d539241d98 100644 --- a/refs.c +++ b/refs.c @@ -754,6 +754,9 @@ static void get_ref_dir(struct ref_cache *refs, const char *base, { DIR *d; const char *path; + struct dirent *de; + int baselen; + char *refname; if (*refs->name) path = git_path_submodule(refs->name, "%s", base); @@ -761,55 +764,55 @@ static void get_ref_dir(struct ref_cache *refs, const char *base, path = git_path("%s", base); d = opendir(path); - if (d) { - struct dirent *de; - int baselen = strlen(base); - char *refname = xmalloc(baselen + 257); + if (!d) + return; - memcpy(refname, base, baselen); - if (baselen && base[baselen-1] != '/') - refname[baselen++] = '/'; + baselen = strlen(base); + refname = xmalloc(baselen + 257); - while ((de = readdir(d)) != NULL) { - unsigned char sha1[20]; - struct stat st; - int flag; - int namelen; - const char *refdir; + memcpy(refname, base, baselen); + if (baselen && base[baselen-1] != '/') + refname[baselen++] = '/'; - if (de->d_name[0] == '.') - continue; - namelen = strlen(de->d_name); - if (namelen > 255) - continue; - if (has_extension(de->d_name, ".lock")) - continue; - memcpy(refname + baselen, de->d_name, namelen+1); - refdir = *refs->name - ? git_path_submodule(refs->name, "%s", refname) - : git_path("%s", refname); - if (stat(refdir, &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) { - get_ref_dir(refs, refname, dir); - continue; - } - if (*refs->name) { - hashclr(sha1); - flag = 0; - if (resolve_gitlink_ref(refs->name, refname, sha1) < 0) { - hashclr(sha1); - flag |= REF_ISBROKEN; - } - } else if (read_ref_full(refname, sha1, 1, &flag)) { + while ((de = readdir(d)) != NULL) { + unsigned char sha1[20]; + struct stat st; + int flag; + int namelen; + const char *refdir; + + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if (namelen > 255) + continue; + if (has_extension(de->d_name, ".lock")) + continue; + memcpy(refname + baselen, de->d_name, namelen+1); + refdir = *refs->name + ? git_path_submodule(refs->name, "%s", refname) + : git_path("%s", refname); + if (stat(refdir, &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) { + get_ref_dir(refs, refname, dir); + continue; + } + if (*refs->name) { + hashclr(sha1); + flag = 0; + if (resolve_gitlink_ref(refs->name, refname, sha1) < 0) { hashclr(sha1); flag |= REF_ISBROKEN; } - add_ref(dir, create_ref_entry(refname, sha1, flag, 1)); + } else if (read_ref_full(refname, sha1, 1, &flag)) { + hashclr(sha1); + flag |= REF_ISBROKEN; } - free(refname); - closedir(d); + add_ref(dir, create_ref_entry(refname, sha1, flag, 1)); } + free(refname); + closedir(d); } static struct ref_dir *get_loose_refs(struct ref_cache *refs) From 6163cd8a2a8703bdbd30920731aec32c68c39453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Thu, 3 May 2012 08:51:00 +0700 Subject: [PATCH 02/54] streaming: void pointer instead of char pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow any kind of buffer to be fed to read_istream() without an explicit cast by making it's buf argument a void pointer. It's about arbitrary data, not only characters. Signed-off-by: Rene Scharfe Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- streaming.c | 2 +- streaming.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/streaming.c b/streaming.c index 7e7ee2be6f..3a3cd1206a 100644 --- a/streaming.c +++ b/streaming.c @@ -99,7 +99,7 @@ int close_istream(struct git_istream *st) return r; } -ssize_t read_istream(struct git_istream *st, char *buf, size_t sz) +ssize_t read_istream(struct git_istream *st, void *buf, size_t sz) { return st->vtbl->read(st, buf, sz); } diff --git a/streaming.h b/streaming.h index 3e827709c8..1d05c2a465 100644 --- a/streaming.h +++ b/streaming.h @@ -10,7 +10,7 @@ struct git_istream; extern struct git_istream *open_istream(const unsigned char *, enum object_type *, unsigned long *, struct stream_filter *); extern int close_istream(struct git_istream *); -extern ssize_t read_istream(struct git_istream *, char *, size_t); +extern ssize_t read_istream(struct git_istream *, void *, size_t); extern int stream_blob_to_fd(int fd, const unsigned char *, struct stream_filter *, int can_seek); From d240d4102103215e9fe38431b1702a4024d2f1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 3 May 2012 08:51:01 +0700 Subject: [PATCH 03/54] archive-tar: turn write_tar_entry into blob-writing only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this patch write_tar_entry() can: - write global header by write_global_extended_header() calling write_tar_entry with with both sha1 and path == NULL - write extended header for symlinks, by write_tar_entry() calling itself with sha1 != NULL and path == NULL - write a normal blob. In this case both sha1 and path are valid. After this patch, the first two call sites are modified to write the header without calling write_tar_entry(). The function is now for writing blobs only. This simplifies handling when write_tar_entry() learns about large blobs. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive-tar.c | 78 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/archive-tar.c b/archive-tar.c index 20af0051a3..1727ab90ae 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -123,6 +123,43 @@ static size_t get_path_prefix(const char *path, size_t pathlen, size_t maxlen) return i; } +static void prepare_header(struct archiver_args *args, + struct ustar_header *header, + unsigned int mode, unsigned long size) +{ + sprintf(header->mode, "%07o", mode & 07777); + sprintf(header->size, "%011lo", S_ISREG(mode) ? size : 0); + sprintf(header->mtime, "%011lo", (unsigned long) args->time); + + sprintf(header->uid, "%07o", 0); + sprintf(header->gid, "%07o", 0); + strlcpy(header->uname, "root", sizeof(header->uname)); + strlcpy(header->gname, "root", sizeof(header->gname)); + sprintf(header->devmajor, "%07o", 0); + sprintf(header->devminor, "%07o", 0); + + memcpy(header->magic, "ustar", 6); + memcpy(header->version, "00", 2); + + sprintf(header->chksum, "%07o", ustar_header_chksum(header)); +} + +static int write_extended_header(struct archiver_args *args, + const unsigned char *sha1, + const void *buffer, unsigned long size) +{ + struct ustar_header header; + unsigned int mode; + memset(&header, 0, sizeof(header)); + *header.typeflag = TYPEFLAG_EXT_HEADER; + mode = 0100666; + sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + prepare_header(args, &header, mode, size); + write_blocked(&header, sizeof(header)); + write_blocked(buffer, size); + return 0; +} + static int write_tar_entry(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size) @@ -134,13 +171,9 @@ static int write_tar_entry(struct archiver_args *args, memset(&header, 0, sizeof(header)); if (!sha1) { - *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666; - strcpy(header.name, "pax_global_header"); + die("BUG: sha1 == NULL is not supported"); } else if (!path) { - *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666; - sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + die("BUG: path == NULL is not supported"); } else { if (S_ISDIR(mode) || S_ISGITLINK(mode)) { *header.typeflag = TYPEFLAG_DIR; @@ -182,25 +215,11 @@ static int write_tar_entry(struct archiver_args *args, memcpy(header.linkname, buffer, size); } - sprintf(header.mode, "%07o", mode & 07777); - sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header.mtime, "%011lo", (unsigned long) args->time); - - sprintf(header.uid, "%07o", 0); - sprintf(header.gid, "%07o", 0); - strlcpy(header.uname, "root", sizeof(header.uname)); - strlcpy(header.gname, "root", sizeof(header.gname)); - sprintf(header.devmajor, "%07o", 0); - sprintf(header.devminor, "%07o", 0); - - memcpy(header.magic, "ustar", 6); - memcpy(header.version, "00", 2); - - sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); + prepare_header(args, &header, mode, size); if (ext_header.len > 0) { - err = write_tar_entry(args, sha1, NULL, 0, 0, ext_header.buf, - ext_header.len); + err = write_extended_header(args, sha1, ext_header.buf, + ext_header.len); if (err) return err; } @@ -215,11 +234,18 @@ static int write_global_extended_header(struct archiver_args *args) { const unsigned char *sha1 = args->commit_sha1; struct strbuf ext_header = STRBUF_INIT; - int err; + struct ustar_header header; + unsigned int mode; + int err = 0; strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); - err = write_tar_entry(args, NULL, NULL, 0, 0, ext_header.buf, - ext_header.len); + memset(&header, 0, sizeof(header)); + *header.typeflag = TYPEFLAG_GLOBAL_HEADER; + mode = 0100666; + strcpy(header.name, "pax_global_header"); + prepare_header(args, &header, mode, ext_header.len); + write_blocked(&header, sizeof(header)); + write_blocked(ext_header.buf, ext_header.len); strbuf_release(&ext_header); return err; } From 853907097af803c595f0c12fc355c907702eb7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 3 May 2012 08:51:02 +0700 Subject: [PATCH 04/54] archive-tar: unindent write_tar_entry by one level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's used to be if (!sha1) { ... } else if (!path) { ... } else { ... } Now that the first two blocks are no-op. We can remove the if/else skeleton and put the else block back by one indent level. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive-tar.c | 58 +++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/archive-tar.c b/archive-tar.c index 1727ab90ae..6c8a0bd3bf 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -170,40 +170,34 @@ static int write_tar_entry(struct archiver_args *args, memset(&header, 0, sizeof(header)); - if (!sha1) { - die("BUG: sha1 == NULL is not supported"); - } else if (!path) { - die("BUG: path == NULL is not supported"); + if (S_ISDIR(mode) || S_ISGITLINK(mode)) { + *header.typeflag = TYPEFLAG_DIR; + mode = (mode | 0777) & ~tar_umask; + } else if (S_ISLNK(mode)) { + *header.typeflag = TYPEFLAG_LNK; + mode |= 0777; + } else if (S_ISREG(mode)) { + *header.typeflag = TYPEFLAG_REG; + mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; } else { - if (S_ISDIR(mode) || S_ISGITLINK(mode)) { - *header.typeflag = TYPEFLAG_DIR; - mode = (mode | 0777) & ~tar_umask; - } else if (S_ISLNK(mode)) { - *header.typeflag = TYPEFLAG_LNK; - mode |= 0777; - } else if (S_ISREG(mode)) { - *header.typeflag = TYPEFLAG_REG; - mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; - } else { - return error("unsupported file mode: 0%o (SHA1: %s)", - mode, sha1_to_hex(sha1)); - } - if (pathlen > sizeof(header.name)) { - size_t plen = get_path_prefix(path, pathlen, - sizeof(header.prefix)); - size_t rest = pathlen - plen - 1; - if (plen > 0 && rest <= sizeof(header.name)) { - memcpy(header.prefix, path, plen); - memcpy(header.name, path + plen + 1, rest); - } else { - sprintf(header.name, "%s.data", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "path", - path, pathlen); - } - } else - memcpy(header.name, path, pathlen); + return error("unsupported file mode: 0%o (SHA1: %s)", + mode, sha1_to_hex(sha1)); } + if (pathlen > sizeof(header.name)) { + size_t plen = get_path_prefix(path, pathlen, + sizeof(header.prefix)); + size_t rest = pathlen - plen - 1; + if (plen > 0 && rest <= sizeof(header.name)) { + memcpy(header.prefix, path, plen); + memcpy(header.name, path + plen + 1, rest); + } else { + sprintf(header.name, "%s.data", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "path", + path, pathlen); + } + } else + memcpy(header.name, path, pathlen); if (S_ISLNK(mode) && buffer) { if (size > sizeof(header.linkname)) { From 9cb513b7988c2fe443c47186e42dd827b76ddb14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 3 May 2012 08:51:03 +0700 Subject: [PATCH 05/54] archive: delegate blob reading to backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit archive-tar.c and archive-zip.c now perform conversion check, with help of sha1_file_to_archive() from archive.c This gives backends more freedom in dealing with (streaming) large blobs. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive-tar.c | 25 +++++++++++++++++++++---- archive-zip.c | 15 +++++++++++++-- archive.c | 28 +++++++++++----------------- archive.h | 10 +++++++++- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/archive-tar.c b/archive-tar.c index 6c8a0bd3bf..3be0cdf350 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -161,11 +161,15 @@ static int write_extended_header(struct archiver_args *args, } static int write_tar_entry(struct archiver_args *args, - const unsigned char *sha1, const char *path, size_t pathlen, - unsigned int mode, void *buffer, unsigned long size) + const unsigned char *sha1, + const char *path, size_t pathlen, + unsigned int mode) { struct ustar_header header; struct strbuf ext_header = STRBUF_INIT; + unsigned int old_mode = mode; + unsigned long size; + void *buffer; int err = 0; memset(&header, 0, sizeof(header)); @@ -199,7 +203,17 @@ static int write_tar_entry(struct archiver_args *args, } else memcpy(header.name, path, pathlen); - if (S_ISLNK(mode) && buffer) { + if (S_ISLNK(mode) || S_ISREG(mode)) { + enum object_type type; + buffer = sha1_file_to_archive(args, path, sha1, old_mode, &type, &size); + if (!buffer) + return error("cannot read %s", sha1_to_hex(sha1)); + } else { + buffer = NULL; + size = 0; + } + + if (S_ISLNK(mode)) { if (size > sizeof(header.linkname)) { sprintf(header.linkname, "see %s.paxheader", sha1_to_hex(sha1)); @@ -214,13 +228,16 @@ static int write_tar_entry(struct archiver_args *args, if (ext_header.len > 0) { err = write_extended_header(args, sha1, ext_header.buf, ext_header.len); - if (err) + if (err) { + free(buffer); return err; + } } strbuf_release(&ext_header); write_blocked(&header, sizeof(header)); if (S_ISREG(mode) && buffer && size > 0) write_blocked(buffer, size); + free(buffer); return err; } diff --git a/archive-zip.c b/archive-zip.c index 02d1f3787a..716cc42710 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -121,8 +121,9 @@ static void *zlib_deflate(void *data, unsigned long size, } static int write_zip_entry(struct archiver_args *args, - const unsigned char *sha1, const char *path, size_t pathlen, - unsigned int mode, void *buffer, unsigned long size) + const unsigned char *sha1, + const char *path, size_t pathlen, + unsigned int mode) { struct zip_local_header header; struct zip_dir_header dirent; @@ -134,6 +135,8 @@ static int write_zip_entry(struct archiver_args *args, int method; unsigned char *out; void *deflated = NULL; + void *buffer; + unsigned long size; crc = crc32(0, NULL, 0); @@ -148,7 +151,14 @@ static int write_zip_entry(struct archiver_args *args, out = NULL; uncompressed_size = 0; compressed_size = 0; + buffer = NULL; + size = 0; } else if (S_ISREG(mode) || S_ISLNK(mode)) { + enum object_type type; + buffer = sha1_file_to_archive(args, path, sha1, mode, &type, &size); + if (!buffer) + return error("cannot read %s", sha1_to_hex(sha1)); + method = 0; attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : (mode & 0111) ? ((mode) << 16) : 0; @@ -229,6 +239,7 @@ static int write_zip_entry(struct archiver_args *args, } free(deflated); + free(buffer); return 0; } diff --git a/archive.c b/archive.c index 1ee837d717..cd083eaf9a 100644 --- a/archive.c +++ b/archive.c @@ -59,12 +59,15 @@ static void format_subst(const struct commit *commit, free(to_free); } -static void *sha1_file_to_archive(const char *path, const unsigned char *sha1, - unsigned int mode, enum object_type *type, - unsigned long *sizep, const struct commit *commit) +void *sha1_file_to_archive(const struct archiver_args *args, + const char *path, const unsigned char *sha1, + unsigned int mode, enum object_type *type, + unsigned long *sizep) { void *buffer; + const struct commit *commit = args->convert ? args->commit : NULL; + path += args->baselen; buffer = read_sha1_file(sha1, type, sizep); if (buffer && S_ISREG(mode)) { struct strbuf buf = STRBUF_INIT; @@ -109,12 +112,9 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, write_archive_entry_fn_t write_entry = c->write_entry; struct git_attr_check check[2]; const char *path_without_prefix; - int convert = 0; int err; - enum object_type type; - unsigned long size; - void *buffer; + args->convert = 0; strbuf_reset(&path); strbuf_grow(&path, PATH_MAX); strbuf_add(&path, args->base, args->baselen); @@ -126,28 +126,22 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) { if (ATTR_TRUE(check[0].value)) return 0; - convert = ATTR_TRUE(check[1].value); + args->convert = ATTR_TRUE(check[1].value); } if (S_ISDIR(mode) || S_ISGITLINK(mode)) { strbuf_addch(&path, '/'); if (args->verbose) fprintf(stderr, "%.*s\n", (int)path.len, path.buf); - err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0); + err = write_entry(args, sha1, path.buf, path.len, mode); if (err) return err; return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); } - buffer = sha1_file_to_archive(path_without_prefix, sha1, mode, - &type, &size, convert ? args->commit : NULL); - if (!buffer) - return error("cannot read %s", sha1_to_hex(sha1)); if (args->verbose) fprintf(stderr, "%.*s\n", (int)path.len, path.buf); - err = write_entry(args, sha1, path.buf, path.len, mode, buffer, size); - free(buffer); - return err; + return write_entry(args, sha1, path.buf, path.len, mode); } int write_archive_entries(struct archiver_args *args, @@ -167,7 +161,7 @@ int write_archive_entries(struct archiver_args *args, if (args->verbose) fprintf(stderr, "%.*s\n", (int)len, args->base); err = write_entry(args, args->tree->object.sha1, args->base, - len, 040777, NULL, 0); + len, 040777); if (err) return err; } diff --git a/archive.h b/archive.h index 2b0884f1ef..895afcdc7a 100644 --- a/archive.h +++ b/archive.h @@ -11,6 +11,7 @@ struct archiver_args { const char **pathspec; unsigned int verbose : 1; unsigned int worktree_attributes : 1; + unsigned int convert : 1; int compression_level; }; @@ -27,11 +28,18 @@ extern void register_archiver(struct archiver *); extern void init_tar_archiver(void); extern void init_zip_archiver(void); -typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size); +typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, + const unsigned char *sha1, + const char *path, size_t pathlen, + unsigned int mode); extern int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry); extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix, const char *name_hint, int remote); const char *archive_format_from_filename(const char *filename); +extern void *sha1_file_to_archive(const struct archiver_args *args, + const char *path, const unsigned char *sha1, + unsigned int mode, enum object_type *type, + unsigned long *sizep); #endif /* ARCHIVE_H */ From 5544049def9a80bc5ea09a5649e13c1b56160518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 3 May 2012 08:51:04 +0700 Subject: [PATCH 06/54] archive-tar: stream large blobs to tar file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit t5000 verifies output while t1050 makes sure the command always respects core.bigfilethreshold Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive-tar.c | 56 +++++++++++++++++++++++++++++++++++++++++---- t/t1050-large.sh | 4 ++++ t/t5000-tar-tree.sh | 6 +++++ 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/archive-tar.c b/archive-tar.c index 3be0cdf350..93387ea336 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -4,6 +4,7 @@ #include "cache.h" #include "tar.h" #include "archive.h" +#include "streaming.h" #include "run-command.h" #define RECORDSIZE (512) @@ -30,10 +31,9 @@ static void write_if_needed(void) * queues up writes, so that all our write(2) calls write exactly one * full block; pads writes to RECORDSIZE */ -static void write_blocked(const void *data, unsigned long size) +static void do_write_blocked(const void *data, unsigned long size) { const char *buf = data; - unsigned long tail; if (offset) { unsigned long chunk = BLOCKSIZE - offset; @@ -54,6 +54,11 @@ static void write_blocked(const void *data, unsigned long size) memcpy(block + offset, buf, size); offset += size; } +} + +static void finish_record(void) +{ + unsigned long tail; tail = offset % RECORDSIZE; if (tail) { memset(block + offset, 0, RECORDSIZE - tail); @@ -62,6 +67,12 @@ static void write_blocked(const void *data, unsigned long size) write_if_needed(); } +static void write_blocked(const void *data, unsigned long size) +{ + do_write_blocked(data, size); + finish_record(); +} + /* * The end of tar archives is marked by 2*512 nul bytes and after that * follows the rest of the block (if any). @@ -77,6 +88,33 @@ static void write_trailer(void) } } +/* + * queues up writes, so that all our write(2) calls write exactly one + * full block; pads writes to RECORDSIZE + */ +static int stream_blocked(const unsigned char *sha1) +{ + struct git_istream *st; + enum object_type type; + unsigned long sz; + char buf[BLOCKSIZE]; + ssize_t readlen; + + st = open_istream(sha1, &type, &sz, NULL); + if (!st) + return error("cannot stream blob %s", sha1_to_hex(sha1)); + for (;;) { + readlen = read_istream(st, buf, sizeof(buf)); + if (readlen <= 0) + break; + do_write_blocked(buf, readlen); + } + close_istream(st); + if (!readlen) + finish_record(); + return readlen; +} + /* * pax extended header records have the format "%u %s=%s\n". %u contains * the size of the whole string (including the %u), the first %s is the @@ -203,7 +241,11 @@ static int write_tar_entry(struct archiver_args *args, } else memcpy(header.name, path, pathlen); - if (S_ISLNK(mode) || S_ISREG(mode)) { + if (S_ISREG(mode) && !args->convert && + sha1_object_info(sha1, &size) == OBJ_BLOB && + size > big_file_threshold) + buffer = NULL; + else if (S_ISLNK(mode) || S_ISREG(mode)) { enum object_type type; buffer = sha1_file_to_archive(args, path, sha1, old_mode, &type, &size); if (!buffer) @@ -235,8 +277,12 @@ static int write_tar_entry(struct archiver_args *args, } strbuf_release(&ext_header); write_blocked(&header, sizeof(header)); - if (S_ISREG(mode) && buffer && size > 0) - write_blocked(buffer, size); + if (S_ISREG(mode) && size > 0) { + if (buffer) + write_blocked(buffer, size); + else + err = stream_blocked(sha1); + } free(buffer); return err; } diff --git a/t/t1050-large.sh b/t/t1050-large.sh index 4d127f19b7..fe475542f5 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -134,4 +134,8 @@ test_expect_success 'repack' ' git repack -ad ' +test_expect_success 'tar achiving' ' + git archive --format=tar HEAD >/dev/null +' + test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 527c9e7548..d9b997f5d3 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -84,6 +84,12 @@ test_expect_success \ 'git archive vs. git tar-tree' \ 'test_cmp b.tar b2.tar' +test_expect_success 'git archive on large files' ' + test_config core.bigfilethreshold 1 && + git archive HEAD >b3.tar && + test_cmp b.tar b3.tar +' + test_expect_success \ 'git archive in a bare repo' \ '(cd bare.git && git archive HEAD) >b3.tar' From 60df6bd19ad40e3eae2926f3785a63e670c150ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Thu, 3 May 2012 08:51:05 +0700 Subject: [PATCH 07/54] archive-zip: remove uncompressed_size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We only need size and compressed_size. Signed-off-by: Rene Scharfe Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive-zip.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/archive-zip.c b/archive-zip.c index 716cc42710..400ba38c7d 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -129,7 +129,6 @@ static int write_zip_entry(struct archiver_args *args, struct zip_dir_header dirent; unsigned long attr2; unsigned long compressed_size; - unsigned long uncompressed_size; unsigned long crc; unsigned long direntsize; int method; @@ -149,7 +148,7 @@ static int write_zip_entry(struct archiver_args *args, method = 0; attr2 = 16; out = NULL; - uncompressed_size = 0; + size = 0; compressed_size = 0; buffer = NULL; size = 0; @@ -166,7 +165,6 @@ static int write_zip_entry(struct archiver_args *args, method = 8; crc = crc32(crc, buffer, size); out = buffer; - uncompressed_size = size; compressed_size = size; } else { return error("unsupported file mode: 0%o (SHA1: %s)", mode, @@ -204,7 +202,7 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(dirent.mdate, zip_date); copy_le32(dirent.crc32, crc); copy_le32(dirent.compressed_size, compressed_size); - copy_le32(dirent.size, uncompressed_size); + copy_le32(dirent.size, size); copy_le16(dirent.filename_length, pathlen); copy_le16(dirent.extra_length, 0); copy_le16(dirent.comment_length, 0); @@ -226,7 +224,7 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(header.mdate, zip_date); copy_le32(header.crc32, crc); copy_le32(header.compressed_size, compressed_size); - copy_le32(header.size, uncompressed_size); + copy_le32(header.size, size); copy_le16(header.filename_length, pathlen); copy_le16(header.extra_length, 0); write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE); From ebf5374afa87afa334b040faec35144c2a3d03d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Thu, 3 May 2012 08:51:06 +0700 Subject: [PATCH 08/54] archive-zip: factor out helpers for writing sizes and CRC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We're going to reuse them soon for streaming. Also, update the ZIP directory only at the very end, which will also make streaming easier. Signed-off-by: Rene Scharfe Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive-zip.c | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/archive-zip.c b/archive-zip.c index 400ba38c7d..678569ab2c 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -120,6 +120,26 @@ static void *zlib_deflate(void *data, unsigned long size, return buffer; } +static void set_zip_dir_data_desc(struct zip_dir_header *header, + unsigned long size, + unsigned long compressed_size, + unsigned long crc) +{ + copy_le32(header->crc32, crc); + copy_le32(header->compressed_size, compressed_size); + copy_le32(header->size, size); +} + +static void set_zip_header_data_desc(struct zip_local_header *header, + unsigned long size, + unsigned long compressed_size, + unsigned long crc) +{ + copy_le32(header->crc32, crc); + copy_le32(header->compressed_size, compressed_size); + copy_le32(header->size, size); +} + static int write_zip_entry(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, @@ -200,9 +220,7 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(dirent.compression_method, method); copy_le16(dirent.mtime, zip_time); copy_le16(dirent.mdate, zip_date); - copy_le32(dirent.crc32, crc); - copy_le32(dirent.compressed_size, compressed_size); - copy_le32(dirent.size, size); + set_zip_dir_data_desc(&dirent, size, compressed_size, crc); copy_le16(dirent.filename_length, pathlen); copy_le16(dirent.extra_length, 0); copy_le16(dirent.comment_length, 0); @@ -210,11 +228,6 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(dirent.attr1, 0); copy_le32(dirent.attr2, attr2); copy_le32(dirent.offset, zip_offset); - memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE); - zip_dir_offset += ZIP_DIR_HEADER_SIZE; - memcpy(zip_dir + zip_dir_offset, path, pathlen); - zip_dir_offset += pathlen; - zip_dir_entries++; copy_le32(header.magic, 0x04034b50); copy_le16(header.version, 10); @@ -222,9 +235,7 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(header.compression_method, method); copy_le16(header.mtime, zip_time); copy_le16(header.mdate, zip_date); - copy_le32(header.crc32, crc); - copy_le32(header.compressed_size, compressed_size); - copy_le32(header.size, size); + set_zip_header_data_desc(&header, size, compressed_size, crc); copy_le16(header.filename_length, pathlen); copy_le16(header.extra_length, 0); write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE); @@ -239,6 +250,12 @@ static int write_zip_entry(struct archiver_args *args, free(deflated); free(buffer); + memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE); + zip_dir_offset += ZIP_DIR_HEADER_SIZE; + memcpy(zip_dir + zip_dir_offset, path, pathlen); + zip_dir_offset += pathlen; + zip_dir_entries++; + return 0; } From 2158f883d99a92f801534c91294305ccbe171f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Thu, 3 May 2012 08:51:07 +0700 Subject: [PATCH 09/54] archive-zip: streaming for stored files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Write a data descriptor containing the CRC of the entry and its sizes after streaming it out. For simplicity, do that only if we're storing files (option -0) for now. t5000 verifies output. t1050 makes sure the command always respects core.bigfilethreshold Signed-off-by: Rene Scharfe Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive-zip.c | 90 +++++++++++++++++++++++++++++++++++++++------ t/t1050-large.sh | 4 ++ t/t5000-tar-tree.sh | 6 +++ 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/archive-zip.c b/archive-zip.c index 678569ab2c..1c6c39d42c 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -3,6 +3,7 @@ */ #include "cache.h" #include "archive.h" +#include "streaming.h" static int zip_date; static int zip_time; @@ -15,6 +16,7 @@ static unsigned int zip_dir_offset; static unsigned int zip_dir_entries; #define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) +#define ZIP_STREAM (8) struct zip_local_header { unsigned char magic[4]; @@ -31,6 +33,14 @@ struct zip_local_header { unsigned char _end[1]; }; +struct zip_data_desc { + unsigned char magic[4]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char _end[1]; +}; + struct zip_dir_header { unsigned char magic[4]; unsigned char creator_version[2]; @@ -70,6 +80,7 @@ struct zip_dir_trailer { * we're interested in. */ #define ZIP_LOCAL_HEADER_SIZE offsetof(struct zip_local_header, _end) +#define ZIP_DATA_DESC_SIZE offsetof(struct zip_data_desc, _end) #define ZIP_DIR_HEADER_SIZE offsetof(struct zip_dir_header, _end) #define ZIP_DIR_TRAILER_SIZE offsetof(struct zip_dir_trailer, _end) @@ -120,6 +131,19 @@ static void *zlib_deflate(void *data, unsigned long size, return buffer; } +static void write_zip_data_desc(unsigned long size, + unsigned long compressed_size, + unsigned long crc) +{ + struct zip_data_desc trailer; + + copy_le32(trailer.magic, 0x08074b50); + copy_le32(trailer.crc32, crc); + copy_le32(trailer.compressed_size, compressed_size); + copy_le32(trailer.size, size); + write_or_die(1, &trailer, ZIP_DATA_DESC_SIZE); +} + static void set_zip_dir_data_desc(struct zip_dir_header *header, unsigned long size, unsigned long compressed_size, @@ -140,6 +164,8 @@ static void set_zip_header_data_desc(struct zip_local_header *header, copy_le32(header->size, size); } +#define STREAM_BUFFER_SIZE (1024 * 16) + static int write_zip_entry(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, @@ -155,6 +181,8 @@ static int write_zip_entry(struct archiver_args *args, unsigned char *out; void *deflated = NULL; void *buffer; + struct git_istream *stream = NULL; + unsigned long flags = 0; unsigned long size; crc = crc32(0, NULL, 0); @@ -173,25 +201,38 @@ static int write_zip_entry(struct archiver_args *args, buffer = NULL; size = 0; } else if (S_ISREG(mode) || S_ISLNK(mode)) { - enum object_type type; - buffer = sha1_file_to_archive(args, path, sha1, mode, &type, &size); - if (!buffer) - return error("cannot read %s", sha1_to_hex(sha1)); + enum object_type type = sha1_object_info(sha1, &size); method = 0; attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : (mode & 0111) ? ((mode) << 16) : 0; - if (S_ISREG(mode) && args->compression_level != 0) + if (S_ISREG(mode) && args->compression_level != 0 && size > 0) method = 8; - crc = crc32(crc, buffer, size); - out = buffer; compressed_size = size; + + if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert && + size > big_file_threshold && method == 0) { + stream = open_istream(sha1, &type, &size, NULL); + if (!stream) + return error("cannot stream blob %s", + sha1_to_hex(sha1)); + flags |= ZIP_STREAM; + out = buffer = NULL; + } else { + buffer = sha1_file_to_archive(args, path, sha1, mode, + &type, &size); + if (!buffer) + return error("cannot read %s", + sha1_to_hex(sha1)); + crc = crc32(crc, buffer, size); + out = buffer; + } } else { return error("unsupported file mode: 0%o (SHA1: %s)", mode, sha1_to_hex(sha1)); } - if (method == 8) { + if (buffer && method == 8) { deflated = zlib_deflate(buffer, size, args->compression_level, &compressed_size); if (deflated && compressed_size - 6 < size) { @@ -216,7 +257,7 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(dirent.creator_version, S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0); copy_le16(dirent.version, 10); - copy_le16(dirent.flags, 0); + copy_le16(dirent.flags, flags); copy_le16(dirent.compression_method, method); copy_le16(dirent.mtime, zip_time); copy_le16(dirent.mdate, zip_date); @@ -231,18 +272,43 @@ static int write_zip_entry(struct archiver_args *args, copy_le32(header.magic, 0x04034b50); copy_le16(header.version, 10); - copy_le16(header.flags, 0); + copy_le16(header.flags, flags); copy_le16(header.compression_method, method); copy_le16(header.mtime, zip_time); copy_le16(header.mdate, zip_date); - set_zip_header_data_desc(&header, size, compressed_size, crc); + if (flags & ZIP_STREAM) + set_zip_header_data_desc(&header, 0, 0, 0); + else + set_zip_header_data_desc(&header, size, compressed_size, crc); copy_le16(header.filename_length, pathlen); copy_le16(header.extra_length, 0); write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE); zip_offset += ZIP_LOCAL_HEADER_SIZE; write_or_die(1, path, pathlen); zip_offset += pathlen; - if (compressed_size > 0) { + if (stream && method == 0) { + unsigned char buf[STREAM_BUFFER_SIZE]; + ssize_t readlen; + + for (;;) { + readlen = read_istream(stream, buf, sizeof(buf)); + if (readlen <= 0) + break; + crc = crc32(crc, buf, readlen); + write_or_die(1, buf, readlen); + } + close_istream(stream); + if (readlen) + return readlen; + + compressed_size = size; + zip_offset += compressed_size; + + write_zip_data_desc(size, compressed_size, crc); + zip_offset += ZIP_DATA_DESC_SIZE; + + set_zip_dir_data_desc(&dirent, size, compressed_size, crc); + } else if (compressed_size > 0) { write_or_die(1, out, compressed_size); zip_offset += compressed_size; } diff --git a/t/t1050-large.sh b/t/t1050-large.sh index fe475542f5..9db54b56bc 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -138,4 +138,8 @@ test_expect_success 'tar achiving' ' git archive --format=tar HEAD >/dev/null ' +test_expect_success 'zip achiving, store only' ' + git archive --format=zip -0 HEAD >/dev/null +' + test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index d9b997f5d3..3b54c38621 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -244,6 +244,12 @@ test_expect_success UNZIP \ 'validate file contents with prefix' \ 'diff -r a e/prefix/a' +test_expect_success UNZIP 'git archive -0 --format=zip on large files' ' + test_config core.bigfilethreshold 1 && + git archive -0 --format=zip HEAD >large.zip && + (mkdir large && cd large && $UNZIP ../large.zip) +' + test_expect_success \ 'git archive --list outside of a git repo' \ 'GIT_DIR=some/non-existing/directory git archive --list' From c743c21591f9433fe784ac38902872701ce2e850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Thu, 3 May 2012 08:51:08 +0700 Subject: [PATCH 10/54] archive-zip: streaming for deflated files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After an entry has been streamed out, its CRC and sizes are written as part of a data descriptor. For simplicity, we make the buffer for the compressed chunks twice as big as for the uncompressed ones, to be sure the result fit in even if deflate makes them bigger. t5000 verifies output. t1050 makes sure the command always respects core.bigfilethreshold Signed-off-by: Rene Scharfe Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive-zip.c | 64 ++++++++++++++++++++++++++++++++++++++++++++- t/t1050-large.sh | 4 +++ t/t5000-tar-tree.sh | 7 +++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/archive-zip.c b/archive-zip.c index 1c6c39d42c..f5af81f904 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -211,7 +211,7 @@ static int write_zip_entry(struct archiver_args *args, compressed_size = size; if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert && - size > big_file_threshold && method == 0) { + size > big_file_threshold) { stream = open_istream(sha1, &type, &size, NULL); if (!stream) return error("cannot stream blob %s", @@ -307,6 +307,68 @@ static int write_zip_entry(struct archiver_args *args, write_zip_data_desc(size, compressed_size, crc); zip_offset += ZIP_DATA_DESC_SIZE; + set_zip_dir_data_desc(&dirent, size, compressed_size, crc); + } else if (stream && method == 8) { + unsigned char buf[STREAM_BUFFER_SIZE]; + ssize_t readlen; + git_zstream zstream; + int result; + size_t out_len; + unsigned char compressed[STREAM_BUFFER_SIZE * 2]; + + memset(&zstream, 0, sizeof(zstream)); + git_deflate_init(&zstream, args->compression_level); + + compressed_size = 0; + zstream.next_out = compressed; + zstream.avail_out = sizeof(compressed); + + for (;;) { + readlen = read_istream(stream, buf, sizeof(buf)); + if (readlen <= 0) + break; + crc = crc32(crc, buf, readlen); + + zstream.next_in = buf; + zstream.avail_in = readlen; + result = git_deflate(&zstream, 0); + if (result != Z_OK) + die("deflate error (%d)", result); + out = compressed; + if (!compressed_size) + out += 2; + out_len = zstream.next_out - out; + + if (out_len > 0) { + write_or_die(1, out, out_len); + compressed_size += out_len; + zstream.next_out = compressed; + zstream.avail_out = sizeof(compressed); + } + + } + close_istream(stream); + if (readlen) + return readlen; + + zstream.next_in = buf; + zstream.avail_in = 0; + result = git_deflate(&zstream, Z_FINISH); + if (result != Z_STREAM_END) + die("deflate error (%d)", result); + + git_deflate_end(&zstream); + out = compressed; + if (!compressed_size) + out += 2; + out_len = zstream.next_out - out - 4; + write_or_die(1, out, out_len); + compressed_size += out_len; + zip_offset += compressed_size; + + write_zip_data_desc(size, compressed_size, crc); + zip_offset += ZIP_DATA_DESC_SIZE; + set_zip_dir_data_desc(&dirent, size, compressed_size, crc); } else if (compressed_size > 0) { write_or_die(1, out, compressed_size); diff --git a/t/t1050-large.sh b/t/t1050-large.sh index 9db54b56bc..55ed955cef 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -142,4 +142,8 @@ test_expect_success 'zip achiving, store only' ' git archive --format=zip -0 HEAD >/dev/null ' +test_expect_success 'zip achiving, deflate' ' + git archive --format=zip HEAD >/dev/null +' + test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 3b54c38621..94f2ebac5f 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -250,6 +250,13 @@ test_expect_success UNZIP 'git archive -0 --format=zip on large files' ' (mkdir large && cd large && $UNZIP ../large.zip) ' +test_expect_success UNZIP 'git archive --format=zip on large files' ' + test_config core.bigfilethreshold 1 && + git archive --format=zip HEAD >large-compressed.zip && + (mkdir large-compressed && cd large-compressed && $UNZIP ../large-compressed.zip) && + test_cmp large-compressed/a/bin/sh large/a/bin/sh +' + test_expect_success \ 'git archive --list outside of a git repo' \ 'GIT_DIR=some/non-existing/directory git archive --list' From 2dd42334dea6619c0774511beda9a02642088f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Thu, 3 May 2012 10:52:16 +0200 Subject: [PATCH 11/54] t5000: rationalize unzip tests Factor out a function for checking the contents of ZIP archives. It extracts their contents and compares them to the original files. This removes some duplicate code. Tests that just create archives can lose their UNZIP prerequisite. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- t/t5000-tar-tree.sh | 81 ++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 94f2ebac5f..ecf00edab2 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -31,6 +31,26 @@ GUNZIP=${GUNZIP:-gzip -d} SUBSTFORMAT=%H%n +check_zip() { + zipfile=$1.zip + listfile=$1.lst + dir=$1 + dir_with_prefix=$dir/$2 + + test_expect_success UNZIP " extract ZIP archive" " + (mkdir $dir && cd $dir && $UNZIP ../$zipfile) + " + + test_expect_success UNZIP " validate filenames" " + (cd ${dir_with_prefix}a && find .) | sort >$listfile && + test_cmp a.lst $listfile + " + + test_expect_success UNZIP " validate file contents" " + diff -r a ${dir_with_prefix}a + " +} + test_expect_success \ 'populate workdir' \ 'mkdir a b c && @@ -181,10 +201,19 @@ test_expect_success \ test_cmp a/substfile2 g/prefix/a/substfile2 ' +$UNZIP -v >/dev/null 2>&1 +if [ $? -eq 127 ]; then + say "Skipping ZIP tests, because unzip was not found" +else + test_set_prereq UNZIP +fi + test_expect_success \ 'git archive --format=zip' \ 'git archive --format=zip HEAD >d.zip' +check_zip d + test_expect_success \ 'git archive --format=zip in a bare repo' \ '(cd bare.git && git archive --format=zip HEAD) >d1.zip' @@ -207,56 +236,26 @@ test_expect_success 'git archive with --output, override inferred format' ' test_cmp b.tar d4.zip ' -$UNZIP -v >/dev/null 2>&1 -if [ $? -eq 127 ]; then - say "Skipping ZIP tests, because unzip was not found" -else - test_set_prereq UNZIP -fi - -test_expect_success UNZIP \ - 'extract ZIP archive' \ - '(mkdir d && cd d && $UNZIP ../d.zip)' - -test_expect_success UNZIP \ - 'validate filenames' \ - '(cd d/a && find .) | sort >d.lst && - test_cmp a.lst d.lst' - -test_expect_success UNZIP \ - 'validate file contents' \ - 'diff -r a d/a' - test_expect_success \ 'git archive --format=zip with prefix' \ 'git archive --format=zip --prefix=prefix/ HEAD >e.zip' -test_expect_success UNZIP \ - 'extract ZIP archive with prefix' \ - '(mkdir e && cd e && $UNZIP ../e.zip)' +check_zip e prefix/ -test_expect_success UNZIP \ - 'validate filenames with prefix' \ - '(cd e/prefix/a && find .) | sort >e.lst && - test_cmp a.lst e.lst' - -test_expect_success UNZIP \ - 'validate file contents with prefix' \ - 'diff -r a e/prefix/a' - -test_expect_success UNZIP 'git archive -0 --format=zip on large files' ' - test_config core.bigfilethreshold 1 && - git archive -0 --format=zip HEAD >large.zip && - (mkdir large && cd large && $UNZIP ../large.zip) +test_expect_success 'git archive -0 --format=zip on large files' ' + test_config core.bigfilethreshold 1 && + git archive -0 --format=zip HEAD >large.zip ' -test_expect_success UNZIP 'git archive --format=zip on large files' ' - test_config core.bigfilethreshold 1 && - git archive --format=zip HEAD >large-compressed.zip && - (mkdir large-compressed && cd large-compressed && $UNZIP ../large-compressed.zip) && - test_cmp large-compressed/a/bin/sh large/a/bin/sh +check_zip large + +test_expect_success 'git archive --format=zip on large files' ' + test_config core.bigfilethreshold 1 && + git archive --format=zip HEAD >large-compressed.zip ' +check_zip large-compressed + test_expect_success \ 'git archive --list outside of a git repo' \ 'GIT_DIR=some/non-existing/directory git archive --list' From 72b64b44e77112b93e7b046e54c62a01e69cef3d Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 25 Apr 2012 00:45:08 +0200 Subject: [PATCH 12/54] get_ref_dir(): use a strbuf to hold refname This simplifies the bookkeeping and allows an (artificial) restriction on refname component length to be removed. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 54 ++++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/refs.c b/refs.c index d539241d98..ed6d84230d 100644 --- a/refs.c +++ b/refs.c @@ -756,7 +756,7 @@ static void get_ref_dir(struct ref_cache *refs, const char *base, const char *path; struct dirent *de; int baselen; - char *refname; + struct strbuf refname; if (*refs->name) path = git_path_submodule(refs->name, "%s", base); @@ -768,50 +768,48 @@ static void get_ref_dir(struct ref_cache *refs, const char *base, return; baselen = strlen(base); - refname = xmalloc(baselen + 257); - - memcpy(refname, base, baselen); - if (baselen && base[baselen-1] != '/') - refname[baselen++] = '/'; + strbuf_init(&refname, baselen + 257); + strbuf_add(&refname, base, baselen); + if (baselen && base[baselen-1] != '/') { + strbuf_addch(&refname, '/'); + baselen++; + } while ((de = readdir(d)) != NULL) { unsigned char sha1[20]; struct stat st; int flag; - int namelen; const char *refdir; if (de->d_name[0] == '.') continue; - namelen = strlen(de->d_name); - if (namelen > 255) - continue; if (has_extension(de->d_name, ".lock")) continue; - memcpy(refname + baselen, de->d_name, namelen+1); + strbuf_addstr(&refname, de->d_name); refdir = *refs->name - ? git_path_submodule(refs->name, "%s", refname) - : git_path("%s", refname); - if (stat(refdir, &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) { - get_ref_dir(refs, refname, dir); - continue; - } - if (*refs->name) { - hashclr(sha1); - flag = 0; - if (resolve_gitlink_ref(refs->name, refname, sha1) < 0) { + ? git_path_submodule(refs->name, "%s", refname.buf) + : git_path("%s", refname.buf); + if (stat(refdir, &st) < 0) { + ; /* silently ignore */ + } else if (S_ISDIR(st.st_mode)) { + get_ref_dir(refs, refname.buf, dir); + } else { + if (*refs->name) { + hashclr(sha1); + flag = 0; + if (resolve_gitlink_ref(refs->name, refname.buf, sha1) < 0) { + hashclr(sha1); + flag |= REF_ISBROKEN; + } + } else if (read_ref_full(refname.buf, sha1, 1, &flag)) { hashclr(sha1); flag |= REF_ISBROKEN; } - } else if (read_ref_full(refname, sha1, 1, &flag)) { - hashclr(sha1); - flag |= REF_ISBROKEN; + add_ref(dir, create_ref_entry(refname.buf, sha1, flag, 1)); } - add_ref(dir, create_ref_entry(refname, sha1, flag, 1)); + strbuf_setlen(&refname, baselen); } - free(refname); + strbuf_release(&refname); closedir(d); } From 66a3d20b8f8566581e8aa46e35555f353074f232 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 25 Apr 2012 00:45:09 +0200 Subject: [PATCH 13/54] get_ref_dir(): rename "base" parameter to "dirname" Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/refs.c b/refs.c index ed6d84230d..37ea557f3e 100644 --- a/refs.c +++ b/refs.c @@ -749,30 +749,30 @@ void add_packed_ref(const char *refname, const unsigned char *sha1) create_ref_entry(refname, sha1, REF_ISPACKED, 1)); } -static void get_ref_dir(struct ref_cache *refs, const char *base, +static void get_ref_dir(struct ref_cache *refs, const char *dirname, struct ref_dir *dir) { DIR *d; const char *path; struct dirent *de; - int baselen; + int dirnamelen; struct strbuf refname; if (*refs->name) - path = git_path_submodule(refs->name, "%s", base); + path = git_path_submodule(refs->name, "%s", dirname); else - path = git_path("%s", base); + path = git_path("%s", dirname); d = opendir(path); if (!d) return; - baselen = strlen(base); - strbuf_init(&refname, baselen + 257); - strbuf_add(&refname, base, baselen); - if (baselen && base[baselen-1] != '/') { + dirnamelen = strlen(dirname); + strbuf_init(&refname, dirnamelen + 257); + strbuf_add(&refname, dirname, dirnamelen); + if (dirnamelen && dirname[dirnamelen-1] != '/') { strbuf_addch(&refname, '/'); - baselen++; + dirnamelen++; } while ((de = readdir(d)) != NULL) { @@ -807,7 +807,7 @@ static void get_ref_dir(struct ref_cache *refs, const char *base, } add_ref(dir, create_ref_entry(refname.buf, sha1, flag, 1)); } - strbuf_setlen(&refname, baselen); + strbuf_setlen(&refname, dirnamelen); } strbuf_release(&refname); closedir(d); From abc390989f1086aa3a8620a81f87622a78cf393b Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 25 Apr 2012 00:45:10 +0200 Subject: [PATCH 14/54] get_ref_dir(): require that the dirname argument ends in '/' This removes some conditional code and makes it consistent with the way that direntry names are stored. Please note that this function is never used on the top-level .git directory; it is always called for directories at level .git/refs or deeper. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/refs.c b/refs.c index 37ea557f3e..113739f14f 100644 --- a/refs.c +++ b/refs.c @@ -749,13 +749,17 @@ void add_packed_ref(const char *refname, const unsigned char *sha1) create_ref_entry(refname, sha1, REF_ISPACKED, 1)); } +/* + * Read the loose references for refs from the namespace dirname. + * dirname must end with '/'. + */ static void get_ref_dir(struct ref_cache *refs, const char *dirname, struct ref_dir *dir) { DIR *d; const char *path; struct dirent *de; - int dirnamelen; + int dirnamelen = strlen(dirname); struct strbuf refname; if (*refs->name) @@ -767,13 +771,8 @@ static void get_ref_dir(struct ref_cache *refs, const char *dirname, if (!d) return; - dirnamelen = strlen(dirname); strbuf_init(&refname, dirnamelen + 257); strbuf_add(&refname, dirname, dirnamelen); - if (dirnamelen && dirname[dirnamelen-1] != '/') { - strbuf_addch(&refname, '/'); - dirnamelen++; - } while ((de = readdir(d)) != NULL) { unsigned char sha1[20]; @@ -792,6 +791,7 @@ static void get_ref_dir(struct ref_cache *refs, const char *dirname, if (stat(refdir, &st) < 0) { ; /* silently ignore */ } else if (S_ISDIR(st.st_mode)) { + strbuf_addch(&refname, '/'); get_ref_dir(refs, refname.buf, dir); } else { if (*refs->name) { @@ -816,7 +816,7 @@ static void get_ref_dir(struct ref_cache *refs, const char *dirname, static struct ref_dir *get_loose_refs(struct ref_cache *refs) { if (!refs->did_loose) { - get_ref_dir(refs, "refs", &refs->loose); + get_ref_dir(refs, "refs/", &refs->loose); refs->did_loose = 1; } return &refs->loose; From f348ac923c9f834c3cdc434e6266872cf5710b71 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 25 Apr 2012 00:45:11 +0200 Subject: [PATCH 15/54] refs.c: extract function search_for_subdir() Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/refs.c b/refs.c index 113739f14f..9471b1d8a6 100644 --- a/refs.c +++ b/refs.c @@ -276,6 +276,27 @@ static struct ref_entry *search_ref_dir(struct ref_dir *dir, const char *refname return *r; } +/* + * Search for a directory entry directly within dir (without + * recursing). Sort dir if necessary. subdirname must be a directory + * name (i.e., end in '/'). If mkdir is set, then create the + * directory if it is missing; otherwise, return NULL if the desired + * directory cannot be found. + */ +static struct ref_entry *search_for_subdir(struct ref_dir *dir, + const char *subdirname, int mkdir) +{ + struct ref_entry *entry = search_ref_dir(dir, subdirname); + if (!entry) { + if (!mkdir) + return NULL; + entry = create_dir_entry(subdirname); + add_entry_to_dir(dir, entry); + } + assert(entry->flag & REF_DIR); + return entry; +} + /* * If refname is a reference name, find the ref_dir within the dir * tree that should hold refname. If refname is a directory name @@ -294,17 +315,10 @@ static struct ref_dir *find_containing_dir(struct ref_dir *dir, for (slash = strchr(refname_copy, '/'); slash; slash = strchr(slash + 1, '/')) { char tmp = slash[1]; slash[1] = '\0'; - entry = search_ref_dir(dir, refname_copy); - if (!entry) { - if (!mkdir) { - dir = NULL; - break; - } - entry = create_dir_entry(refname_copy); - add_entry_to_dir(dir, entry); - } + entry = search_for_subdir(dir, refname_copy, mkdir); slash[1] = tmp; - assert(entry->flag & REF_DIR); + if (!entry) + break; dir = &entry->u.subdir; } From 9f2fb4a3737c86e245ce365f6ad8f901ad397d6f Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 25 Apr 2012 00:45:12 +0200 Subject: [PATCH 16/54] get_ref_dir(): take the containing directory as argument Previously, the "dir" argument to get_ref_dir() was a pointer to the top-level ref_dir. Change the function to expect a pointer to the ref_dir corresponding to dirname. This allows entries to be added directly to dir, without having to recurse through the reference trie each time (i.e., we can use add_entry_to_dir() instead of add_ref()). Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/refs.c b/refs.c index 9471b1d8a6..afd0cf75bc 100644 --- a/refs.c +++ b/refs.c @@ -765,7 +765,8 @@ void add_packed_ref(const char *refname, const unsigned char *sha1) /* * Read the loose references for refs from the namespace dirname. - * dirname must end with '/'. + * dirname must end with '/'. dir must be the directory entry + * corresponding to dirname. */ static void get_ref_dir(struct ref_cache *refs, const char *dirname, struct ref_dir *dir) @@ -806,7 +807,8 @@ static void get_ref_dir(struct ref_cache *refs, const char *dirname, ; /* silently ignore */ } else if (S_ISDIR(st.st_mode)) { strbuf_addch(&refname, '/'); - get_ref_dir(refs, refname.buf, dir); + get_ref_dir(refs, refname.buf, + &search_for_subdir(dir, refname.buf, 1)->u.subdir); } else { if (*refs->name) { hashclr(sha1); @@ -819,7 +821,8 @@ static void get_ref_dir(struct ref_cache *refs, const char *dirname, hashclr(sha1); flag |= REF_ISBROKEN; } - add_ref(dir, create_ref_entry(refname.buf, sha1, flag, 1)); + add_entry_to_dir(dir, + create_ref_entry(refname.buf, sha1, flag, 1)); } strbuf_setlen(&refname, dirnamelen); } @@ -830,7 +833,8 @@ static void get_ref_dir(struct ref_cache *refs, const char *dirname, static struct ref_dir *get_loose_refs(struct ref_cache *refs) { if (!refs->did_loose) { - get_ref_dir(refs, "refs/", &refs->loose); + get_ref_dir(refs, "refs/", + &search_for_subdir(&refs->loose, "refs/", 1)->u.subdir); refs->did_loose = 1; } return &refs->loose; From 93c603fcb7bfbe3d4bfb108463166b850de638f3 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 25 Apr 2012 00:45:13 +0200 Subject: [PATCH 17/54] do_for_each_reflog(): return early on error Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 70 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/refs.c b/refs.c index afd0cf75bc..8c39bd76e5 100644 --- a/refs.c +++ b/refs.c @@ -2246,47 +2246,47 @@ static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data) { DIR *d = opendir(git_path("logs/%s", base)); int retval = 0; + struct dirent *de; + int baselen; + char *log; - if (d) { - struct dirent *de; - int baselen = strlen(base); - char *log = xmalloc(baselen + 257); + if (!d) + return *base ? errno : 0; - memcpy(log, base, baselen); - if (baselen && base[baselen-1] != '/') - log[baselen++] = '/'; + baselen = strlen(base); + log = xmalloc(baselen + 257); + memcpy(log, base, baselen); + if (baselen && base[baselen-1] != '/') + log[baselen++] = '/'; - while ((de = readdir(d)) != NULL) { - struct stat st; - int namelen; + while ((de = readdir(d)) != NULL) { + struct stat st; + 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(log + baselen, de->d_name, namelen+1); - if (stat(git_path("logs/%s", log), &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) { - retval = do_for_each_reflog(log, fn, cb_data); - } else { - unsigned char sha1[20]; - if (read_ref_full(log, sha1, 0, NULL)) - retval = error("bad ref for %s", log); - else - retval = fn(log, sha1, 0, cb_data); - } - if (retval) - break; + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if (namelen > 255) + continue; + if (has_extension(de->d_name, ".lock")) + continue; + memcpy(log + baselen, de->d_name, namelen+1); + if (stat(git_path("logs/%s", log), &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) { + retval = do_for_each_reflog(log, fn, cb_data); + } else { + unsigned char sha1[20]; + if (read_ref_full(log, sha1, 0, NULL)) + retval = error("bad ref for %s", log); + else + retval = fn(log, sha1, 0, cb_data); } - free(log); - closedir(d); + if (retval) + break; } - else if (*base) - return errno; + free(log); + closedir(d); return retval; } From 989c0e5d02b1844b44e5ea2ff61a2cbd2f054a25 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Wed, 25 Apr 2012 00:45:14 +0200 Subject: [PATCH 18/54] do_for_each_reflog(): use a strbuf to hold logfile name This simplifies the bookkeeping and allows an (artificial) restriction on refname component length to be removed. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 60 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/refs.c b/refs.c index 8c39bd76e5..0d81b9e267 100644 --- a/refs.c +++ b/refs.c @@ -2242,57 +2242,59 @@ int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_dat return for_each_recent_reflog_ent(refname, fn, 0, cb_data); } -static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data) +/* + * Call fn for each reflog in the namespace indicated by name. name + * must be empty or end with '/'. Name will be used as a scratch + * space, but its contents will be restored before return. + */ +static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data) { - DIR *d = opendir(git_path("logs/%s", base)); + DIR *d = opendir(git_path("logs/%s", name->buf)); int retval = 0; struct dirent *de; - int baselen; - char *log; + int oldlen = name->len; if (!d) - return *base ? errno : 0; - - baselen = strlen(base); - log = xmalloc(baselen + 257); - memcpy(log, base, baselen); - if (baselen && base[baselen-1] != '/') - log[baselen++] = '/'; + return name->len ? errno : 0; while ((de = readdir(d)) != NULL) { struct stat st; - 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(log + baselen, de->d_name, namelen+1); - if (stat(git_path("logs/%s", log), &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) { - retval = do_for_each_reflog(log, fn, cb_data); + strbuf_addstr(name, de->d_name); + if (stat(git_path("logs/%s", name->buf), &st) < 0) { + ; /* silently ignore */ } else { - unsigned char sha1[20]; - if (read_ref_full(log, sha1, 0, NULL)) - retval = error("bad ref for %s", log); - else - retval = fn(log, sha1, 0, cb_data); + if (S_ISDIR(st.st_mode)) { + strbuf_addch(name, '/'); + retval = do_for_each_reflog(name, fn, cb_data); + } else { + unsigned char sha1[20]; + if (read_ref_full(name->buf, sha1, 0, NULL)) + retval = error("bad ref for %s", name->buf); + else + retval = fn(name->buf, sha1, 0, cb_data); + } + if (retval) + break; } - if (retval) - break; + strbuf_setlen(name, oldlen); } - free(log); closedir(d); return retval; } int for_each_reflog(each_ref_fn fn, void *cb_data) { - return do_for_each_reflog("", fn, cb_data); + int retval; + struct strbuf name; + strbuf_init(&name, PATH_MAX); + retval = do_for_each_reflog(&name, fn, cb_data); + strbuf_release(&name); + return retval; } int update_ref(const char *action, const char *refname, From 144e7090045a703c8f5d140474f202ba4f38ac9a Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:26:59 +0200 Subject: [PATCH 19/54] bisect: copy filename string obtained from git_path() Prevent the string from being overwritten by other callers of git_path() and friends before we are done using it. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- bisect.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bisect.c b/bisect.c index 6e186e29cc..48acf73391 100644 --- a/bisect.c +++ b/bisect.c @@ -833,7 +833,7 @@ static int check_ancestors(const char *prefix) */ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout) { - const char *filename = git_path("BISECT_ANCESTORS_OK"); + char *filename = xstrdup(git_path("BISECT_ANCESTORS_OK")); struct stat st; int fd; @@ -842,11 +842,11 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout) /* Check if file BISECT_ANCESTORS_OK exists. */ if (!stat(filename, &st) && S_ISREG(st.st_mode)) - return; + goto done; /* Bisecting with no good rev is ok. */ if (good_revs.nr == 0) - return; + goto done; /* Check if all good revs are ancestor of the bad rev. */ if (check_ancestors(prefix)) @@ -859,6 +859,8 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout) filename, strerror(errno)); else close(fd); + done: + free(filename); } /* From 5fa0441844b1d23e6a2a3c369651dc8c13712ef6 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:27:00 +0200 Subject: [PATCH 20/54] find_containing_dir(): use strbuf in implementation of this function Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/refs.c b/refs.c index 0d81b9e267..623fb55da8 100644 --- a/refs.c +++ b/refs.c @@ -309,20 +309,21 @@ static struct ref_entry *search_for_subdir(struct ref_dir *dir, static struct ref_dir *find_containing_dir(struct ref_dir *dir, const char *refname, int mkdir) { - char *refname_copy = xstrdup(refname); - char *slash; - struct ref_entry *entry; - for (slash = strchr(refname_copy, '/'); slash; slash = strchr(slash + 1, '/')) { - char tmp = slash[1]; - slash[1] = '\0'; - entry = search_for_subdir(dir, refname_copy, mkdir); - slash[1] = tmp; + struct strbuf dirname; + const char *slash; + strbuf_init(&dirname, PATH_MAX); + for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { + struct ref_entry *entry; + strbuf_add(&dirname, + refname + dirname.len, + (slash + 1) - (refname + dirname.len)); + entry = search_for_subdir(dir, dirname.buf, mkdir); if (!entry) break; dir = &entry->u.subdir; } - free(refname_copy); + strbuf_release(&dirname); return dir; } From d12229f532dc88024c249fb3e2ca0ac921cc14a9 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:27:01 +0200 Subject: [PATCH 21/54] refs: wrap top-level ref_dirs in ref_entries Make it turtles all the way down. This affects the loose and packed fields of ref_cache instances. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/refs.c b/refs.c index 623fb55da8..56d37abb28 100644 --- a/refs.c +++ b/refs.c @@ -607,26 +607,26 @@ static int is_refname_available(const char *refname, const char *oldrefname, */ static struct ref_cache { struct ref_cache *next; - char did_loose; - char did_packed; - struct ref_dir loose; - struct ref_dir packed; + struct ref_entry *loose; + struct ref_entry *packed; /* The submodule name, or "" for the main repo. */ char name[FLEX_ARRAY]; } *ref_cache; static void clear_packed_ref_cache(struct ref_cache *refs) { - if (refs->did_packed) - clear_ref_dir(&refs->packed); - refs->did_packed = 0; + if (refs->packed) { + free_ref_entry(refs->packed); + refs->packed = NULL; + } } static void clear_loose_ref_cache(struct ref_cache *refs) { - if (refs->did_loose) - clear_ref_dir(&refs->loose); - refs->did_loose = 0; + if (refs->loose) { + free_ref_entry(refs->loose); + refs->loose = NULL; + } } static struct ref_cache *create_ref_cache(const char *submodule) @@ -740,22 +740,22 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir) static struct ref_dir *get_packed_refs(struct ref_cache *refs) { - if (!refs->did_packed) { + if (!refs->packed) { const char *packed_refs_file; FILE *f; + refs->packed = create_dir_entry(""); if (*refs->name) packed_refs_file = git_path_submodule(refs->name, "packed-refs"); else packed_refs_file = git_path("packed-refs"); f = fopen(packed_refs_file, "r"); if (f) { - read_packed_refs(f, &refs->packed); + read_packed_refs(f, &refs->packed->u.subdir); fclose(f); } - refs->did_packed = 1; } - return &refs->packed; + return &refs->packed->u.subdir; } void add_packed_ref(const char *refname, const unsigned char *sha1) @@ -833,12 +833,13 @@ static void get_ref_dir(struct ref_cache *refs, const char *dirname, static struct ref_dir *get_loose_refs(struct ref_cache *refs) { - if (!refs->did_loose) { + if (!refs->loose) { + refs->loose = create_dir_entry(""); get_ref_dir(refs, "refs/", - &search_for_subdir(&refs->loose, "refs/", 1)->u.subdir); - refs->did_loose = 1; + &search_for_subdir(&refs->loose->u.subdir, + "refs/", 1)->u.subdir); } - return &refs->loose; + return &refs->loose->u.subdir; } /* We allow "recursive" symbolic refs. Only within reason, though */ From 1900b976a4a3d06c22d57031efece81947539337 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:27:02 +0200 Subject: [PATCH 22/54] read_loose_refs(): rename function from get_ref_dir() The new name better describes the function's purpose, and also makes the old name available for a more suitable purpose. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/refs.c b/refs.c index 56d37abb28..beb186c7c9 100644 --- a/refs.c +++ b/refs.c @@ -769,8 +769,8 @@ void add_packed_ref(const char *refname, const unsigned char *sha1) * dirname must end with '/'. dir must be the directory entry * corresponding to dirname. */ -static void get_ref_dir(struct ref_cache *refs, const char *dirname, - struct ref_dir *dir) +static void read_loose_refs(struct ref_cache *refs, const char *dirname, + struct ref_dir *dir) { DIR *d; const char *path; @@ -808,8 +808,8 @@ static void get_ref_dir(struct ref_cache *refs, const char *dirname, ; /* silently ignore */ } else if (S_ISDIR(st.st_mode)) { strbuf_addch(&refname, '/'); - get_ref_dir(refs, refname.buf, - &search_for_subdir(dir, refname.buf, 1)->u.subdir); + read_loose_refs(refs, refname.buf, + &search_for_subdir(dir, refname.buf, 1)->u.subdir); } else { if (*refs->name) { hashclr(sha1); @@ -835,9 +835,9 @@ static struct ref_dir *get_loose_refs(struct ref_cache *refs) { if (!refs->loose) { refs->loose = create_dir_entry(""); - get_ref_dir(refs, "refs/", - &search_for_subdir(&refs->loose->u.subdir, - "refs/", 1)->u.subdir); + read_loose_refs(refs, "refs/", + &search_for_subdir(&refs->loose->u.subdir, + "refs/", 1)->u.subdir); } return &refs->loose->u.subdir; } From d7826d5427724d878e52bce49139f96187527fc3 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:27:03 +0200 Subject: [PATCH 23/54] get_ref_dir(): add function for getting a ref_dir from a ref_entry Convert all accesses of a ref_dir within a ref_entry to use this function. This function will later be responsible for reading loose references from disk on demand. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/refs.c b/refs.c index beb186c7c9..8d76004afc 100644 --- a/refs.c +++ b/refs.c @@ -171,6 +171,12 @@ struct ref_entry { char name[FLEX_ARRAY]; }; +static struct ref_dir *get_ref_dir(struct ref_entry *entry) +{ + assert(entry->flag & REF_DIR); + return &entry->u.subdir; +} + static struct ref_entry *create_ref_entry(const char *refname, const unsigned char *sha1, int flag, int check_name) @@ -195,7 +201,7 @@ static void clear_ref_dir(struct ref_dir *dir); static void free_ref_entry(struct ref_entry *entry) { if (entry->flag & REF_DIR) - clear_ref_dir(&entry->u.subdir); + clear_ref_dir(get_ref_dir(entry)); free(entry); } @@ -320,7 +326,7 @@ static struct ref_dir *find_containing_dir(struct ref_dir *dir, entry = search_for_subdir(dir, dirname.buf, mkdir); if (!entry) break; - dir = &entry->u.subdir; + dir = get_ref_dir(entry); } strbuf_release(&dirname); @@ -449,8 +455,9 @@ static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset, struct ref_entry *entry = dir->entries[i]; int retval; if (entry->flag & REF_DIR) { - sort_ref_dir(&entry->u.subdir); - retval = do_for_each_ref_in_dir(&entry->u.subdir, 0, + struct ref_dir *subdir = get_ref_dir(entry); + sort_ref_dir(subdir); + retval = do_for_each_ref_in_dir(subdir, 0, base, fn, trim, flags, cb_data); } else { retval = do_one_ref(base, fn, trim, flags, cb_data, entry); @@ -495,10 +502,12 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1, if (cmp == 0) { if ((e1->flag & REF_DIR) && (e2->flag & REF_DIR)) { /* Both are directories; descend them in parallel. */ - sort_ref_dir(&e1->u.subdir); - sort_ref_dir(&e2->u.subdir); + struct ref_dir *subdir1 = get_ref_dir(e1); + struct ref_dir *subdir2 = get_ref_dir(e2); + sort_ref_dir(subdir1); + sort_ref_dir(subdir2); retval = do_for_each_ref_in_dirs( - &e1->u.subdir, &e2->u.subdir, + subdir1, subdir2, base, fn, trim, flags, cb_data); i1++; i2++; @@ -521,9 +530,10 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1, i2++; } if (e->flag & REF_DIR) { - sort_ref_dir(&e->u.subdir); + struct ref_dir *subdir = get_ref_dir(e); + sort_ref_dir(subdir); retval = do_for_each_ref_in_dir( - &e->u.subdir, 0, + subdir, 0, base, fn, trim, flags, cb_data); } else { retval = do_one_ref(base, fn, trim, flags, cb_data, e); @@ -751,11 +761,11 @@ static struct ref_dir *get_packed_refs(struct ref_cache *refs) packed_refs_file = git_path("packed-refs"); f = fopen(packed_refs_file, "r"); if (f) { - read_packed_refs(f, &refs->packed->u.subdir); + read_packed_refs(f, get_ref_dir(refs->packed)); fclose(f); } } - return &refs->packed->u.subdir; + return get_ref_dir(refs->packed); } void add_packed_ref(const char *refname, const unsigned char *sha1) @@ -809,7 +819,7 @@ static void read_loose_refs(struct ref_cache *refs, const char *dirname, } else if (S_ISDIR(st.st_mode)) { strbuf_addch(&refname, '/'); read_loose_refs(refs, refname.buf, - &search_for_subdir(dir, refname.buf, 1)->u.subdir); + get_ref_dir(search_for_subdir(dir, refname.buf, 1))); } else { if (*refs->name) { hashclr(sha1); @@ -836,10 +846,10 @@ static struct ref_dir *get_loose_refs(struct ref_cache *refs) if (!refs->loose) { refs->loose = create_dir_entry(""); read_loose_refs(refs, "refs/", - &search_for_subdir(&refs->loose->u.subdir, - "refs/", 1)->u.subdir); + get_ref_dir(search_for_subdir(get_ref_dir(refs->loose), + "refs/", 1))); } - return &refs->loose->u.subdir; + return get_ref_dir(refs->loose); } /* We allow "recursive" symbolic refs. Only within reason, though */ From 3f3aa1bc6215fad0d31d1eae5f4c599c6847a02f Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:27:04 +0200 Subject: [PATCH 24/54] search_for_subdir(): return (ref_dir *) instead of (ref_entry *) That is what all the callers want, so give it to them. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/refs.c b/refs.c index 8d76004afc..4af08809ab 100644 --- a/refs.c +++ b/refs.c @@ -289,8 +289,8 @@ static struct ref_entry *search_ref_dir(struct ref_dir *dir, const char *refname * directory if it is missing; otherwise, return NULL if the desired * directory cannot be found. */ -static struct ref_entry *search_for_subdir(struct ref_dir *dir, - const char *subdirname, int mkdir) +static struct ref_dir *search_for_subdir(struct ref_dir *dir, + const char *subdirname, int mkdir) { struct ref_entry *entry = search_ref_dir(dir, subdirname); if (!entry) { @@ -299,8 +299,7 @@ static struct ref_entry *search_for_subdir(struct ref_dir *dir, entry = create_dir_entry(subdirname); add_entry_to_dir(dir, entry); } - assert(entry->flag & REF_DIR); - return entry; + return get_ref_dir(entry); } /* @@ -319,14 +318,14 @@ static struct ref_dir *find_containing_dir(struct ref_dir *dir, const char *slash; strbuf_init(&dirname, PATH_MAX); for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { - struct ref_entry *entry; + struct ref_dir *subdir; strbuf_add(&dirname, refname + dirname.len, (slash + 1) - (refname + dirname.len)); - entry = search_for_subdir(dir, dirname.buf, mkdir); - if (!entry) + subdir = search_for_subdir(dir, dirname.buf, mkdir); + if (!subdir) break; - dir = get_ref_dir(entry); + dir = subdir; } strbuf_release(&dirname); @@ -819,7 +818,7 @@ static void read_loose_refs(struct ref_cache *refs, const char *dirname, } else if (S_ISDIR(st.st_mode)) { strbuf_addch(&refname, '/'); read_loose_refs(refs, refname.buf, - get_ref_dir(search_for_subdir(dir, refname.buf, 1))); + search_for_subdir(dir, refname.buf, 1)); } else { if (*refs->name) { hashclr(sha1); @@ -846,8 +845,8 @@ static struct ref_dir *get_loose_refs(struct ref_cache *refs) if (!refs->loose) { refs->loose = create_dir_entry(""); read_loose_refs(refs, "refs/", - get_ref_dir(search_for_subdir(get_ref_dir(refs->loose), - "refs/", 1))); + search_for_subdir(get_ref_dir(refs->loose), + "refs/", 1)); } return get_ref_dir(refs->loose); } From f006c42a11d5e97f5abc07b23f353ca64d5180e2 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:27:05 +0200 Subject: [PATCH 25/54] struct ref_dir: store a reference to the enclosing ref_cache This means that a directory ref_entry contains all of the information needed by read_loose_refs(). Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/refs.c b/refs.c index 4af08809ab..6720b90201 100644 --- a/refs.c +++ b/refs.c @@ -106,6 +106,8 @@ struct ref_value { unsigned char peeled[20]; }; +struct ref_cache; + struct ref_dir { int nr, alloc; @@ -117,6 +119,9 @@ struct ref_dir { */ int sorted; + /* A pointer to the ref_cache that contains this ref_dir. */ + struct ref_cache *ref_cache; + struct ref_entry **entries; }; @@ -234,12 +239,14 @@ static void clear_ref_dir(struct ref_dir *dir) * dirname is the name of the directory with a trailing slash (e.g., * "refs/heads/") or "" for the top-level directory. */ -static struct ref_entry *create_dir_entry(const char *dirname) +static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, + const char *dirname) { struct ref_entry *direntry; int len = strlen(dirname); direntry = xcalloc(1, sizeof(struct ref_entry) + len + 1); memcpy(direntry->name, dirname, len + 1); + direntry->u.subdir.ref_cache = ref_cache; direntry->flag = REF_DIR; return direntry; } @@ -296,7 +303,7 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir, if (!entry) { if (!mkdir) return NULL; - entry = create_dir_entry(subdirname); + entry = create_dir_entry(dir->ref_cache, subdirname); add_entry_to_dir(dir, entry); } return get_ref_dir(entry); @@ -753,7 +760,7 @@ static struct ref_dir *get_packed_refs(struct ref_cache *refs) const char *packed_refs_file; FILE *f; - refs->packed = create_dir_entry(""); + refs->packed = create_dir_entry(refs, ""); if (*refs->name) packed_refs_file = git_path_submodule(refs->name, "packed-refs"); else @@ -843,7 +850,7 @@ static void read_loose_refs(struct ref_cache *refs, const char *dirname, static struct ref_dir *get_loose_refs(struct ref_cache *refs) { if (!refs->loose) { - refs->loose = create_dir_entry(""); + refs->loose = create_dir_entry(refs, ""); read_loose_refs(refs, "refs/", search_for_subdir(get_ref_dir(refs->loose), "refs/", 1)); From 423a1afc0bc48bb5520d518b521ecf9d79f59bb3 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:27:06 +0200 Subject: [PATCH 26/54] read_loose_refs(): eliminate ref_cache argument The ref_cache can now be read from the ref_dir. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/refs.c b/refs.c index 6720b90201..746661ecf4 100644 --- a/refs.c +++ b/refs.c @@ -785,9 +785,9 @@ void add_packed_ref(const char *refname, const unsigned char *sha1) * dirname must end with '/'. dir must be the directory entry * corresponding to dirname. */ -static void read_loose_refs(struct ref_cache *refs, const char *dirname, - struct ref_dir *dir) +static void read_loose_refs(const char *dirname, struct ref_dir *dir) { + struct ref_cache *refs = dir->ref_cache; DIR *d; const char *path; struct dirent *de; @@ -824,7 +824,7 @@ static void read_loose_refs(struct ref_cache *refs, const char *dirname, ; /* silently ignore */ } else if (S_ISDIR(st.st_mode)) { strbuf_addch(&refname, '/'); - read_loose_refs(refs, refname.buf, + read_loose_refs(refname.buf, search_for_subdir(dir, refname.buf, 1)); } else { if (*refs->name) { @@ -851,7 +851,7 @@ static struct ref_dir *get_loose_refs(struct ref_cache *refs) { if (!refs->loose) { refs->loose = create_dir_entry(refs, ""); - read_loose_refs(refs, "refs/", + read_loose_refs("refs/", search_for_subdir(get_ref_dir(refs->loose), "refs/", 1)); } From 28e6a34e254a1ee5fc3d106e4a6117fd02254589 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Fri, 27 Apr 2012 00:27:07 +0200 Subject: [PATCH 27/54] refs: read loose references lazily Instead of reading the whole directory of loose references the first time any are needed, only read them on demand, one directory at a time. Use a new ref_entry flag bit REF_INCOMPLETE to indicate that the entry represents a REF_DIR that hasn't been read yet. Whenever any entries from such a directory are needed, read all of the loose references from that directory. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- refs.c | 125 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 29 deletions(-) diff --git a/refs.c b/refs.c index 746661ecf4..9f2da16642 100644 --- a/refs.c +++ b/refs.c @@ -101,6 +101,12 @@ int check_refname_format(const char *refname, int flags) struct ref_entry; +/* + * Information used (along with the information in ref_entry) to + * describe a single cached reference. This data structure only + * occurs embedded in a union in struct ref_entry, and only when + * (ref_entry->flag & REF_DIR) is zero. + */ struct ref_value { unsigned char sha1[20]; unsigned char peeled[20]; @@ -108,6 +114,32 @@ struct ref_value { struct ref_cache; +/* + * Information used (along with the information in ref_entry) to + * describe a level in the hierarchy of references. This data + * structure only occurs embedded in a union in struct ref_entry, and + * only when (ref_entry.flag & REF_DIR) is set. In that case, + * (ref_entry.flag & REF_INCOMPLETE) determines whether the references + * in the directory have already been read: + * + * (ref_entry.flag & REF_INCOMPLETE) unset -- a directory of loose + * or packed references, already read. + * + * (ref_entry.flag & REF_INCOMPLETE) set -- a directory of loose + * references that hasn't been read yet (nor has any of its + * subdirectories). + * + * Entries within a directory are stored within a growable array of + * pointers to ref_entries (entries, nr, alloc). Entries 0 <= i < + * sorted are sorted by their component name in strcmp() order and the + * remaining entries are unsorted. + * + * Loose references are read lazily, one directory at a time. When a + * directory of loose references is read, then all of the references + * in that directory are stored, and REF_INCOMPLETE stubs are created + * for any subdirectories, but the subdirectories themselves are not + * read. The reading is triggered by get_ref_dir(). + */ struct ref_dir { int nr, alloc; @@ -127,19 +159,33 @@ struct ref_dir { /* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */ #define REF_KNOWS_PEELED 0x08 + +/* ref_entry represents a directory of references */ #define REF_DIR 0x10 +/* + * Entry has not yet been read from disk (used only for REF_DIR + * entries representing loose references) + */ +#define REF_INCOMPLETE 0x20 + /* * A ref_entry represents either a reference or a "subdirectory" of - * references. Each directory in the reference namespace is - * represented by a ref_entry with (flags & REF_DIR) set and - * containing a subdir member that holds the entries in that - * directory. References are represented by a ref_entry with (flags & - * REF_DIR) unset and a value member that describes the reference's - * value. The flag member is at the ref_entry level, but it is also - * needed to interpret the contents of the value field (in other - * words, a ref_value object is not very much use without the - * enclosing ref_entry). + * references. + * + * Each directory in the reference namespace is represented by a + * ref_entry with (flags & REF_DIR) set and containing a subdir member + * that holds the entries in that directory that have been read so + * far. If (flags & REF_INCOMPLETE) is set, then the directory and + * its subdirectories haven't been read yet. REF_INCOMPLETE is only + * used for loose reference directories. + * + * References are represented by a ref_entry with (flags & REF_DIR) + * unset and a value member that describes the reference's value. The + * flag member is at the ref_entry level, but it is also needed to + * interpret the contents of the value field (in other words, a + * ref_value object is not very much use without the enclosing + * ref_entry). * * Reference names cannot end with slash and directories' names are * always stored with a trailing slash (except for the top-level @@ -176,10 +222,18 @@ struct ref_entry { char name[FLEX_ARRAY]; }; +static void read_loose_refs(const char *dirname, struct ref_dir *dir); + static struct ref_dir *get_ref_dir(struct ref_entry *entry) { + struct ref_dir *dir; assert(entry->flag & REF_DIR); - return &entry->u.subdir; + dir = &entry->u.subdir; + if (entry->flag & REF_INCOMPLETE) { + read_loose_refs(entry->name, dir); + entry->flag &= ~REF_INCOMPLETE; + } + return dir; } static struct ref_entry *create_ref_entry(const char *refname, @@ -240,14 +294,14 @@ static void clear_ref_dir(struct ref_dir *dir) * "refs/heads/") or "" for the top-level directory. */ static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, - const char *dirname) + const char *dirname, int incomplete) { struct ref_entry *direntry; int len = strlen(dirname); direntry = xcalloc(1, sizeof(struct ref_entry) + len + 1); memcpy(direntry->name, dirname, len + 1); direntry->u.subdir.ref_cache = ref_cache; - direntry->flag = REF_DIR; + direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0); return direntry; } @@ -263,7 +317,7 @@ static void sort_ref_dir(struct ref_dir *dir); /* * Return the entry with the given refname from the ref_dir * (non-recursively), sorting dir if necessary. Return NULL if no - * such entry is found. + * such entry is found. dir must already be complete. */ static struct ref_entry *search_ref_dir(struct ref_dir *dir, const char *refname) { @@ -294,7 +348,7 @@ static struct ref_entry *search_ref_dir(struct ref_dir *dir, const char *refname * recursing). Sort dir if necessary. subdirname must be a directory * name (i.e., end in '/'). If mkdir is set, then create the * directory if it is missing; otherwise, return NULL if the desired - * directory cannot be found. + * directory cannot be found. dir must already be complete. */ static struct ref_dir *search_for_subdir(struct ref_dir *dir, const char *subdirname, int mkdir) @@ -303,7 +357,13 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir, if (!entry) { if (!mkdir) return NULL; - entry = create_dir_entry(dir->ref_cache, subdirname); + /* + * Since dir is complete, the absence of a subdir + * means that the subdir really doesn't exist; + * therefore, create an empty record for it but mark + * the record complete. + */ + entry = create_dir_entry(dir->ref_cache, subdirname, 0); add_entry_to_dir(dir, entry); } return get_ref_dir(entry); @@ -313,10 +373,10 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir, * If refname is a reference name, find the ref_dir within the dir * tree that should hold refname. If refname is a directory name * (i.e., ends in '/'), then return that ref_dir itself. dir must - * represent the top-level directory. Sort ref_dirs and recurse into - * subdirectories as necessary. If mkdir is set, then create any - * missing directories; otherwise, return NULL if the desired - * directory cannot be found. + * represent the top-level directory and must already be complete. + * Sort ref_dirs and recurse into subdirectories as necessary. If + * mkdir is set, then create any missing directories; otherwise, + * return NULL if the desired directory cannot be found. */ static struct ref_dir *find_containing_dir(struct ref_dir *dir, const char *refname, int mkdir) @@ -760,7 +820,7 @@ static struct ref_dir *get_packed_refs(struct ref_cache *refs) const char *packed_refs_file; FILE *f; - refs->packed = create_dir_entry(refs, ""); + refs->packed = create_dir_entry(refs, "", 0); if (*refs->name) packed_refs_file = git_path_submodule(refs->name, "packed-refs"); else @@ -781,9 +841,9 @@ void add_packed_ref(const char *refname, const unsigned char *sha1) } /* - * Read the loose references for refs from the namespace dirname. - * dirname must end with '/'. dir must be the directory entry - * corresponding to dirname. + * Read the loose references from the namespace dirname into dir + * (without recursing). dirname must end with '/'. dir must be the + * directory entry corresponding to dirname. */ static void read_loose_refs(const char *dirname, struct ref_dir *dir) { @@ -824,8 +884,8 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) ; /* silently ignore */ } else if (S_ISDIR(st.st_mode)) { strbuf_addch(&refname, '/'); - read_loose_refs(refname.buf, - search_for_subdir(dir, refname.buf, 1)); + add_entry_to_dir(dir, + create_dir_entry(refs, refname.buf, 1)); } else { if (*refs->name) { hashclr(sha1); @@ -850,10 +910,17 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) static struct ref_dir *get_loose_refs(struct ref_cache *refs) { if (!refs->loose) { - refs->loose = create_dir_entry(refs, ""); - read_loose_refs("refs/", - search_for_subdir(get_ref_dir(refs->loose), - "refs/", 1)); + /* + * Mark the top-level directory complete because we + * are about to read the only subdirectory that can + * hold references: + */ + refs->loose = create_dir_entry(refs, "", 0); + /* + * Create an incomplete entry for "refs/": + */ + add_entry_to_dir(get_ref_dir(refs->loose), + create_dir_entry(refs, "refs/", 1)); } return get_ref_dir(refs->loose); } From 21dfc09d0aac0c1909958b09358957e9388ed238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 3 May 2012 20:12:00 +0700 Subject: [PATCH 28/54] branch: remove lego in i18n tracking info strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/branch.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index d51648fee4..0e060f2e4a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -391,6 +391,7 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int show_upstream_ref) { int ours, theirs; + char *ref = NULL; struct branch *branch = branch_get(branch_name); if (!stat_tracking_info(branch, &ours, &theirs)) { @@ -401,16 +402,29 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, return; } - strbuf_addch(stat, '['); if (show_upstream_ref) - strbuf_addf(stat, "%s: ", - shorten_unambiguous_ref(branch->merge[0]->dst, 0)); - if (!ours) - strbuf_addf(stat, _("behind %d] "), theirs); - else if (!theirs) - strbuf_addf(stat, _("ahead %d] "), ours); - else - strbuf_addf(stat, _("ahead %d, behind %d] "), ours, theirs); + ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + if (!ours) { + if (ref) + strbuf_addf(stat, _("[%s: behind %d]"), ref, theirs); + else + strbuf_addf(stat, _("[behind %d]"), theirs); + + } else if (!theirs) { + if (ref) + strbuf_addf(stat, _("[%s: ahead %d]"), ref, ours); + else + strbuf_addf(stat, _("[ahead %d]"), ours); + } else { + if (ref) + strbuf_addf(stat, _("[%s: ahead %d, behind %d]"), + ref, ours, theirs); + else + strbuf_addf(stat, _("[ahead %d, behind %d]"), + ours, theirs); + } + strbuf_addch(stat, ' '); + free(ref); } static int matches_merge_filter(struct commit *commit) From 663c1295d8a6a591a805692d311e1d709e3193e2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 May 2012 15:12:54 -0700 Subject: [PATCH 29/54] refs: fix find_containing_dir() regression The function used to return NULL when asked to find the containing directory for a ref that does not exist, allowing the caller to omit iteration altogether. But a misconversion in an earlier change "refs.c: extract function search_for_subdir()" started returning the top-level directory entry, forcing callers to walk everything. Signed-off-by: Junio C Hamano --- refs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/refs.c b/refs.c index 9f2da16642..af5da5f1c1 100644 --- a/refs.c +++ b/refs.c @@ -390,8 +390,10 @@ static struct ref_dir *find_containing_dir(struct ref_dir *dir, refname + dirname.len, (slash + 1) - (refname + dirname.len)); subdir = search_for_subdir(dir, dirname.buf, mkdir); - if (!subdir) + if (!subdir) { + dir = NULL; break; + } dir = subdir; } From 7904af1c10d2feb2f6d1dcb22454d93168a88c49 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 4 May 2012 01:23:14 -0400 Subject: [PATCH 30/54] t1411: add more selector index/date tests We already check that @{now} and "--date" cause the displayed selector to use the date for both the multiline and oneline formats. However, we miss several cases: 1. The --format=%gd selector is not tested at all. 2. We do not check how the log.date config interacts with the "--date" magic (according to f4ea32f, it should not impact the output). Doing so reveals that the combination of both (log.date combined with the %gd format) does not behave as expected. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t1411-reflog-show.sh | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index caa687b5b4..4706f4c50c 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -64,6 +64,14 @@ test_expect_success 'using @{now} syntax shows reflog date (oneline)' ' test_cmp expect actual ' +cat >expect <<'EOF' +HEAD@{Thu Apr 7 15:13:13 2005 -0700} +EOF +test_expect_success 'using @{now} syntax shows reflog date (format=%gd)' ' + git log -g -1 --format=%gd HEAD@{now} >actual && + test_cmp expect actual +' + cat >expect <<'EOF' Reflog: HEAD@{1112911993 -0700} (C O Mitter ) Reflog message: commit (initial): one @@ -82,6 +90,43 @@ test_expect_success 'using --date= shows reflog date (oneline)' ' test_cmp expect actual ' +cat >expect <<'EOF' +HEAD@{1112911993 -0700} +EOF +test_expect_success 'using --date= shows reflog date (format=%gd)' ' + git log -g -1 --format=%gd --date=raw >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +Reflog: HEAD@{0} (C O Mitter ) +Reflog message: commit (initial): one +EOF +test_expect_success 'log.date does not invoke "--date" magic (multiline)' ' + test_config log.date raw && + git log -g -1 >tmp && + grep ^Reflog actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +e46513e HEAD@{0}: commit (initial): one +EOF +test_expect_success 'log.date does not invoke "--date" magic (oneline)' ' + test_config log.date raw && + git log -g -1 --oneline >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +HEAD@{0} +EOF +test_expect_failure 'log.date does not invoke "--date" magic (format=%gd)' ' + test_config log.date raw && + git log -g -1 --format=%gd >actual && + test_cmp expect actual +' + : >expect test_expect_success 'empty reflog file' ' git branch empty && From f026c7563a249da9279e664fed16fcd5f55c62db Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 4 May 2012 01:25:18 -0400 Subject: [PATCH 31/54] log: respect date_mode_explicit with --format:%gd When we show a reflog selector (e.g., via "git log -g"), we perform some DWIM magic: while we normally show the entry's index (e.g., HEAD@{1}), if the user has given us a date with "--date", then we show a date-based select (e.g., HEAD@{yesterday}). However, we don't want to trigger this magic if the alternate date format we got was from the "log.date" configuration; that is not sufficiently strong context for us to invoke this particular magic. To fix this, commit f4ea32f (improve reflog date/number heuristic, 2009-09-24) introduced a "date_mode_explicit" flag in rev_info. This flag is set only when we see a "--date" option on the command line, and we a vanilla date to the reflog code if the date was not explicit. Later, commit 8f8f547 (Introduce new pretty formats %g[sdD] for reflog information, 2009-10-19) added another way to show selectors, and it did not respect the date_mode_explicit flag from f4ea32f. This patch propagates the date_mode_explicit flag to the pretty-print code, which can then use it to pass the appropriate date field to the reflog code. This brings the behavior of "%gd" in line with the other formats, and means that its output is independent of any user configuration. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/rev-list.c | 1 + commit.h | 1 + log-tree.c | 1 + pretty.c | 4 +++- t/t1411-reflog-show.sh | 2 +- 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 56727e8c1d..fe0fb20d2d 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -104,6 +104,7 @@ static void show_commit(struct commit *commit, void *data) struct pretty_print_context ctx = {0}; ctx.abbrev = revs->abbrev; ctx.date_mode = revs->date_mode; + ctx.date_mode_explicit = revs->date_mode_explicit; ctx.fmt = revs->commit_format; pretty_print_commit(&ctx, commit, &buf); if (revs->graph) { diff --git a/commit.h b/commit.h index 14f6a5a2ed..048f31ed93 100644 --- a/commit.h +++ b/commit.h @@ -82,6 +82,7 @@ struct pretty_print_context { const char *after_subject; int preserve_subject; enum date_mode date_mode; + unsigned date_mode_explicit:1; int need_8bit_cte; int show_notes; struct reflog_walk_info *reflog_info; diff --git a/log-tree.c b/log-tree.c index 24c295ea1d..5f9e59a10c 100644 --- a/log-tree.c +++ b/log-tree.c @@ -511,6 +511,7 @@ void show_log(struct rev_info *opt) if (ctx.need_8bit_cte >= 0) ctx.need_8bit_cte = has_non_ascii(opt->add_signoff); ctx.date_mode = opt->date_mode; + ctx.date_mode_explicit = opt->date_mode_explicit; ctx.abbrev = opt->diffopt.abbrev; ctx.after_subject = extra_headers; ctx.preserve_subject = opt->preserve_subject; diff --git a/pretty.c b/pretty.c index f45eb54e4c..efd62e8ae7 100644 --- a/pretty.c +++ b/pretty.c @@ -956,7 +956,9 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (c->pretty_ctx->reflog_info) get_reflog_selector(sb, c->pretty_ctx->reflog_info, - c->pretty_ctx->date_mode, + c->pretty_ctx->date_mode_explicit ? + c->pretty_ctx->date_mode : + DATE_NORMAL, (placeholder[1] == 'd')); return 2; case 's': /* reflog message */ diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index 4706f4c50c..88247f874e 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -121,7 +121,7 @@ test_expect_success 'log.date does not invoke "--date" magic (oneline)' ' cat >expect <<'EOF' HEAD@{0} EOF -test_expect_failure 'log.date does not invoke "--date" magic (format=%gd)' ' +test_expect_success 'log.date does not invoke "--date" magic (format=%gd)' ' test_config log.date raw && git log -g -1 --format=%gd >actual && test_cmp expect actual From a763126b5c0120057908e939a0ff7cc95f899f69 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 4 May 2012 01:26:26 -0400 Subject: [PATCH 32/54] reflog-walk: clean up "flag" field of commit_reflog struct When we prepare to walk a reflog, we parse the specification and pull some information from it, such as which reflog to look in (e.g., HEAD), and where to start (e.g., HEAD@{10} or HEAD@{yesterday}). The resulting struct has a "recno" field to show where in the reflog we are starting. It also has a "flag" field; if true, it means the recno field came from parsing a date like HEAD@{yesterday}. There are two problems with this: 1. "flag" is an absolutely terrible name, as it conveys nothing about the meaning 2. you can tell "HEAD" from "HEAD@{yesterday}", but you can't differentiate "HEAD" from "HEAD{0}" This patch converts the flag into a tri-state (and gives it a better name!). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- reflog-walk.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/reflog-walk.c b/reflog-walk.c index 5d81d39a52..80bffb0a00 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -122,7 +122,12 @@ static void add_commit_info(struct commit *commit, void *util, } struct commit_reflog { - int flag, recno; + int recno; + enum selector_type { + SELECTOR_NONE, + SELECTOR_INDEX, + SELECTOR_DATE + } selector; struct complete_reflogs *reflogs; }; @@ -146,6 +151,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info, struct complete_reflogs *reflogs; char *branch, *at = strchr(name, '@'); struct commit_reflog *commit_reflog; + enum selector_type selector = SELECTOR_NONE; if (commit->object.flags & UNINTERESTING) die ("Cannot walk reflogs for %s", name); @@ -158,7 +164,10 @@ int add_reflog_for_walk(struct reflog_walk_info *info, if (*ep != '}') { recno = -1; timestamp = approxidate(at + 2); + selector = SELECTOR_DATE; } + else + selector = SELECTOR_INDEX; } else recno = 0; @@ -196,7 +205,6 @@ int add_reflog_for_walk(struct reflog_walk_info *info, commit_reflog = xcalloc(sizeof(struct commit_reflog), 1); if (recno < 0) { - commit_reflog->flag = 1; commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp); if (commit_reflog->recno < 0) { free(branch); @@ -205,6 +213,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info, } } else commit_reflog->recno = reflogs->nr - recno - 1; + commit_reflog->selector = selector; commit_reflog->reflogs = reflogs; add_commit_info(commit, commit_reflog, &info->reflogs); @@ -263,7 +272,7 @@ void get_reflog_selector(struct strbuf *sb, } strbuf_addf(sb, "%s@{", printed_ref); - if (commit_reflog->flag || dmode) { + if (commit_reflog->selector == SELECTOR_DATE || dmode) { info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); } else { From 794151e9b595ddc2700c0801caabfd27be763e12 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 4 May 2012 01:27:25 -0400 Subject: [PATCH 33/54] reflog-walk: always make HEAD@{0} show indexed selectors When we are showing reflog selectors during a walk, we infer from context whether the user wanted to see the index in each selector, or the reflog date. The current rules are: 1. if the user asked for an explicit date format in the output, show the date 2. if the user asked for ref@{now}, show the date 3. if neither is true, show the index However, if we see "ref@{0}", that should be a strong clue that the user wants to see the counted version. In fact, it should be much stronger than the date format in (1). The user may have been setting the date format to use in another part of the output (e.g., in --format="%gd (%ad)", they may have wanted to influence the author date). This patch flips the rules to: 1. if the user asked for ref@{0}, always show the index 2. if the user asked for ref@{now}, always show the date 3. otherwise, we have just "ref"; show them counted by default, but respect the presence of "--date" as a clue that the user wanted them date-based Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- reflog-walk.c | 3 ++- t/t1411-reflog-show.sh | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/reflog-walk.c b/reflog-walk.c index 80bffb0a00..b84e80f2ca 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -272,7 +272,8 @@ void get_reflog_selector(struct strbuf *sb, } strbuf_addf(sb, "%s@{", printed_ref); - if (commit_reflog->selector == SELECTOR_DATE || dmode) { + if (commit_reflog->selector == SELECTOR_DATE || + (commit_reflog->selector == SELECTOR_NONE && dmode)) { info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); } else { diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index 88247f874e..7d9b5e33df 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -127,6 +127,14 @@ test_expect_success 'log.date does not invoke "--date" magic (format=%gd)' ' test_cmp expect actual ' +cat >expect <<'EOF' +HEAD@{0} +EOF +test_expect_success '--date magic does not override explicit @{0} syntax' ' + git log -g -1 --format=%gd --date=raw HEAD@{0} >actual && + test_cmp expect actual +' + : >expect test_expect_success 'empty reflog file' ' git branch empty && From 70de5e65e8c947626ab9aa2aeb790fd969951bc2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 2 May 2012 15:12:10 -0700 Subject: [PATCH 34/54] Makefile: NO_INSTALL_HARDLINKS Your filesystem may support hardlinks, but you may choose not to use them when installing git-foo builtins and favor symblic links or copies for whatever reason. The installation procedure of git-gui/ directory is not touched with this patch and git-citool still ends up being a hardlink to git-gui, but it needs to be addressed separately. Signed-off-by: Junio C Hamano --- Makefile | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e4f8e0ef08..010e0b5cb0 100644 --- a/Makefile +++ b/Makefile @@ -244,6 +244,9 @@ all:: # Define NO_CROSS_DIRECTORY_HARDLINKS if you plan to distribute the installed # programs as a tar, where bin/ and libexec/ might be on different file systems. # +# Define NO_INSTALL_HARDLINKS if you prefer to use either symbolic links or +# copies to install built-in git commands e.g. git-cat-file. +# # Define USE_NED_ALLOCATOR if you want to replace the platforms default # memory allocators with the nedmalloc allocator written by Niall Douglas. # @@ -1777,6 +1780,10 @@ ifdef ASCIIDOC7 export ASCIIDOC7 endif +ifdef NO_INSTALL_HARDLINKS + export NO_INSTALL_HARDLINKS +endif + ### profile feedback build # @@ -2482,19 +2489,21 @@ endif { test "$$bindir/" = "$$execdir/" || \ for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \ $(RM) "$$execdir/$$p" && \ - test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \ + test -z "$(NO_INSTALL_HARDLINKS)$(NO_CROSS_DIRECTORY_HARDLINKS)" && \ ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \ cp "$$bindir/$$p" "$$execdir/$$p" || exit; \ done; \ } && \ for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \ $(RM) "$$bindir/$$p" && \ + test -z "$(NO_INSTALL_HARDLINKS)" && \ ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \ ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \ cp "$$bindir/git$X" "$$bindir/$$p" || exit; \ done && \ for p in $(BUILT_INS); do \ $(RM) "$$execdir/$$p" && \ + test -z "$(NO_INSTALL_HARDLINKS)" && \ ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \ ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \ cp "$$execdir/git$X" "$$execdir/$$p" || exit; \ @@ -2502,6 +2511,7 @@ endif remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \ for p in $$remote_curl_aliases; do \ $(RM) "$$execdir/$$p" && \ + test -z "$(NO_INSTALL_HARDLINKS)" && \ ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \ ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \ cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \ From f94920993a0a1ba7916618e2ab458d5d4fb21e94 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Fri, 4 May 2012 20:13:14 +0200 Subject: [PATCH 35/54] t2020-checkout-detach: check for the number of orphaned commits Change the test that orphans commits to leave 2 commits behind. Add a test that leaves only one of these behind. The next patch will truncate the list of orphaned commits earlier. With this preliminary update, its effect will become more obvious. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- t/t2020-checkout-detach.sh | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index 068fba4c8e..202ca90c34 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -11,14 +11,13 @@ check_not_detached () { git symbolic-ref -q HEAD >/dev/null } -ORPHAN_WARNING='you are leaving .* commit.*behind' PREV_HEAD_DESC='Previous HEAD position was' check_orphan_warning() { - test_i18ngrep "$ORPHAN_WARNING" "$1" && + test_i18ngrep "you are leaving $2 behind" "$1" && test_i18ngrep ! "$PREV_HEAD_DESC" "$1" } check_no_orphan_warning() { - test_i18ngrep ! "$ORPHAN_WARNING" "$1" && + test_i18ngrep ! "you are leaving .* commit.*behind" "$1" && test_i18ngrep "$PREV_HEAD_DESC" "$1" } @@ -110,12 +109,24 @@ test_expect_success 'checkout warns on orphan commits' ' git checkout --detach two && echo content >orphan && git add orphan && - git commit -a -m orphan && + git commit -a -m orphan1 && + echo new content >orphan && + git commit -a -m orphan2 && + orphan2=$(git rev-parse HEAD) && git checkout master 2>stderr ' test_expect_success 'checkout warns on orphan commits: output' ' - check_orphan_warning stderr + check_orphan_warning stderr "2 commits" +' + +test_expect_success 'checkout warns orphaning 1 of 2 commits' ' + git checkout "$orphan2" && + git checkout HEAD^ 2>stderr +' + +test_expect_success 'checkout warns orphaning 1 of 2 commits: output' ' + check_orphan_warning stderr "2 commits" ' test_expect_success 'checkout does not warn leaving ref tip' ' From 5d8863954f077d2c262d5cc4fc669947ff23d6d5 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Fri, 4 May 2012 20:14:48 +0200 Subject: [PATCH 36/54] checkout (detached): truncate list of orphaned commits at the new HEAD When git checkout switches from a detached HEAD to any other commit, then all orphaned commits were listed in a warning: Warning: you are leaving 2 commits behind...: a5e5396 another fixup 6aa1af6 fixup foo But if the new commit is actually one from this list (6aa1af6 in this example), then the list in the warning can be truncated at the new HEAD, because history beginning at HEAD is not "left behind". This makes it so. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- builtin/checkout.c | 13 +++++++------ t/t2020-checkout-detach.sh | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index 6b9061f26f..160f678b8c 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -671,10 +671,10 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) * HEAD. If it is not reachable from any ref, this is the last chance * for the user to do so without resorting to reflog. */ -static void orphaned_commit_warning(struct commit *commit) +static void orphaned_commit_warning(struct commit *old, struct commit *new) { struct rev_info revs; - struct object *object = &commit->object; + struct object *object = &old->object; struct object_array refs; init_revisions(&revs, NULL); @@ -684,16 +684,17 @@ static void orphaned_commit_warning(struct commit *commit) add_pending_object(&revs, object, sha1_to_hex(object->sha1)); for_each_ref(add_pending_uninteresting_ref, &revs); + add_pending_sha1(&revs, "HEAD", new->object.sha1, UNINTERESTING); refs = revs.pending; revs.leak_pending = 1; if (prepare_revision_walk(&revs)) die(_("internal error in revision walk")); - if (!(commit->object.flags & UNINTERESTING)) - suggest_reattach(commit, &revs); + if (!(old->object.flags & UNINTERESTING)) + suggest_reattach(old, &revs); else - describe_detached_head(_("Previous HEAD position was"), commit); + describe_detached_head(_("Previous HEAD position was"), old); clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS); free(refs.objects); @@ -730,7 +731,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) } if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) - orphaned_commit_warning(old.commit); + orphaned_commit_warning(old.commit, new->commit); update_refs_for_switch(opts, &old, new); diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index 202ca90c34..f63333b64e 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -126,7 +126,7 @@ test_expect_success 'checkout warns orphaning 1 of 2 commits' ' ' test_expect_success 'checkout warns orphaning 1 of 2 commits: output' ' - check_orphan_warning stderr "2 commits" + check_orphan_warning stderr "1 commit" ' test_expect_success 'checkout does not warn leaving ref tip' ' From 2385f24625cda7cc050996d9966e57be2d4d11a3 Mon Sep 17 00:00:00 2001 From: Angus Hammond Date: Sun, 6 May 2012 19:17:15 +0100 Subject: [PATCH 37/54] grep.c: remove redundant line of code Signed-off-by: Angus Hammond Signed-off-by: Junio C Hamano --- grep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grep.c b/grep.c index d03d9e24c2..4336113442 100644 --- a/grep.c +++ b/grep.c @@ -289,7 +289,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt) if (!opt->header_list) return NULL; - p = opt->header_list; + for (p = opt->header_list; p; p = p->next) { if (p->token != GREP_PATTERN_HEAD) die("bug: a non-header pattern in grep header list."); From 481ed2360295f6f5cea43aabb138eaae52539c1e Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 7 May 2012 21:23:13 +0200 Subject: [PATCH 38/54] clone: fix progress-regression In 5bd631b3 ("clone: support multiple levels of verbosity"), the default behavior to show progress of the implicit checkout in the clone-command regressed so that progress was only shown if the verbose-option was specified. Fix this by making option_verbosity == 0 output progress as well. Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano --- builtin/clone.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/clone.c b/builtin/clone.c index 5f20082d6d..87f2657171 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -732,7 +732,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) opts.update = 1; opts.merge = 1; opts.fn = oneway_merge; - opts.verbose_update = (option_verbosity > 0); + opts.verbose_update = (option_verbosity >= 0); opts.src_index = &the_index; opts.dst_index = &the_index; From 55ccf85a524f6a2d8cb5582d80a2927ff11dfb64 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 7 May 2012 14:11:32 -0700 Subject: [PATCH 39/54] reflog-walk: tell explicit --date=default from not having --date at all Introduction of opt->date_mode_explicit was a step in the right direction, but lost that crucial bit at the very end of the callchain, and the callee could not tell an explicitly specified "I want *date* but in default format" from the built-in default value passed when there was no --date specified. Signed-off-by: Junio C Hamano --- log-tree.c | 7 +++---- pretty.c | 5 ++--- reflog-walk.c | 8 ++++---- reflog-walk.h | 4 ++-- t/t1411-reflog-show.sh | 8 ++++---- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/log-tree.c b/log-tree.c index 5f9e59a10c..588117e013 100644 --- a/log-tree.c +++ b/log-tree.c @@ -493,10 +493,9 @@ void show_log(struct rev_info *opt) * graph info here. */ show_reflog_message(opt->reflog_info, - opt->commit_format == CMIT_FMT_ONELINE, - opt->date_mode_explicit ? - opt->date_mode : - DATE_NORMAL); + opt->commit_format == CMIT_FMT_ONELINE, + opt->date_mode, + opt->date_mode_explicit); if (opt->commit_format == CMIT_FMT_ONELINE) return; } diff --git a/pretty.c b/pretty.c index efd62e8ae7..25944de034 100644 --- a/pretty.c +++ b/pretty.c @@ -956,9 +956,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, if (c->pretty_ctx->reflog_info) get_reflog_selector(sb, c->pretty_ctx->reflog_info, - c->pretty_ctx->date_mode_explicit ? - c->pretty_ctx->date_mode : - DATE_NORMAL, + c->pretty_ctx->date_mode, + c->pretty_ctx->date_mode_explicit, (placeholder[1] == 'd')); return 2; case 's': /* reflog message */ diff --git a/reflog-walk.c b/reflog-walk.c index b84e80f2ca..0c904fb2d1 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -252,7 +252,7 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) void get_reflog_selector(struct strbuf *sb, struct reflog_walk_info *reflog_info, - enum date_mode dmode, + enum date_mode dmode, int force_date, int shorten) { struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; @@ -273,7 +273,7 @@ void get_reflog_selector(struct strbuf *sb, strbuf_addf(sb, "%s@{", printed_ref); if (commit_reflog->selector == SELECTOR_DATE || - (commit_reflog->selector == SELECTOR_NONE && dmode)) { + (commit_reflog->selector == SELECTOR_NONE && force_date)) { info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); } else { @@ -302,7 +302,7 @@ void get_reflog_message(struct strbuf *sb, } void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, - enum date_mode dmode) + enum date_mode dmode, int force_date) { if (reflog_info && reflog_info->last_commit_reflog) { struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; @@ -310,7 +310,7 @@ void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, struct strbuf selector = STRBUF_INIT; info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; - get_reflog_selector(&selector, reflog_info, dmode, 0); + get_reflog_selector(&selector, reflog_info, dmode, force_date, 0); if (oneline) { printf("%s: %s", selector.buf, info->message); } diff --git a/reflog-walk.h b/reflog-walk.h index 7bd2cd4c4e..3adccb018b 100644 --- a/reflog-walk.h +++ b/reflog-walk.h @@ -11,12 +11,12 @@ extern int add_reflog_for_walk(struct reflog_walk_info *info, extern void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit); extern void show_reflog_message(struct reflog_walk_info *info, int, - enum date_mode); + enum date_mode, int force_date); extern void get_reflog_message(struct strbuf *sb, struct reflog_walk_info *reflog_info); extern void get_reflog_selector(struct strbuf *sb, struct reflog_walk_info *reflog_info, - enum date_mode dmode, + enum date_mode dmode, int force_date, int shorten); #endif diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index 7d9b5e33df..9a105fe21f 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -73,20 +73,20 @@ test_expect_success 'using @{now} syntax shows reflog date (format=%gd)' ' ' cat >expect <<'EOF' -Reflog: HEAD@{1112911993 -0700} (C O Mitter ) +Reflog: HEAD@{Thu Apr 7 15:13:13 2005 -0700} (C O Mitter ) Reflog message: commit (initial): one EOF test_expect_success 'using --date= shows reflog date (multiline)' ' - git log -g -1 --date=raw >tmp && + git log -g -1 --date=default >tmp && grep ^Reflog actual && test_cmp expect actual ' cat >expect <<'EOF' -e46513e HEAD@{1112911993 -0700}: commit (initial): one +e46513e HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one EOF test_expect_success 'using --date= shows reflog date (oneline)' ' - git log -g -1 --oneline --date=raw >actual && + git log -g -1 --oneline --date=default >actual && test_cmp expect actual ' From c598c5aa01c9a6a877986ac4a99074fc65445141 Mon Sep 17 00:00:00 2001 From: Florian Achleitner Date: Sat, 5 May 2012 12:03:52 +0200 Subject: [PATCH 40/54] Documentation/git-config: describe and clarify "--local " option Describe config file selection in git-config. While the usage message of git-config shows --local, the documentation page did not contain anything about that. Signed-off-by: Florian Achleitner Signed-off-by: Junio C Hamano --- Documentation/git-config.txt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index e7ecf5d803..800a5cd79d 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -44,11 +44,15 @@ a "true" or "false" string for bool), or '--path', which does some path expansion (see '--path' below). If no type specifier is passed, no checks or transformations are performed on the value. -The file-option can be one of '--system', '--global' or '--file' -which specify where the values will be read from or written to. -The default is to assume the config file of the current repository, -.git/config unless defined otherwise with GIT_DIR and GIT_CONFIG -(see <>). +When reading, the values are read from the system, global and +repository local configuration files by default, and options +'--system', '--global', '--local' and '--file ' can be +used to tell the command to read from only that location (see <>). + +When writing, the new value is written to the repository local +configuration file by default, and options '--system', '--global', +'--file ' can be used to tell the command to write to +that location (you can say '--local' but that is the default). This command will fail (with exit code ret) if: From 036dbbfb2d9127dcef3d742b99ac8677006f6d33 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 7 May 2012 15:18:26 -0400 Subject: [PATCH 41/54] commit: refactor option parsing The options are declared as a static global, but really they need only be accessible from cmd_commit. Additionally, declare the "struct wt_status" in cmd_commit and cmd_status as static at the top of each function; this will let the options lists reference them directly, which will facilitate further cleanups. Signed-off-by: Jeff King --- builtin/commit.c | 117 ++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index b257ae8774..864ed2edc2 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -129,59 +129,6 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) return 0; } -static struct option builtin_commit_options[] = { - OPT__QUIET(&quiet, "suppress summary after successful commit"), - OPT__VERBOSE(&verbose, "show diff in commit message template"), - - OPT_GROUP("Commit message options"), - OPT_FILENAME('F', "file", &logfile, "read message from file"), - OPT_STRING(0, "author", &force_author, "author", "override author for commit"), - OPT_STRING(0, "date", &force_date, "date", "override date for commit"), - OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m), - OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"), - OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"), - OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"), - OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"), - OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"), - OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), - OPT_FILENAME('t', "template", &template_file, "use specified template file"), - OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"), - OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), - OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"), - { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", - "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, - /* end commit message options */ - - OPT_GROUP("Commit contents options"), - OPT_BOOLEAN('a', "all", &all, "commit all changed files"), - OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"), - OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), - OPT_BOOLEAN('p', "patch", &patch_interactive, "interactively add changes"), - OPT_BOOLEAN('o', "only", &only, "commit only specified files"), - OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), - OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), - OPT_SET_INT(0, "short", &status_format, "show status concisely", - STATUS_FORMAT_SHORT), - OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"), - OPT_SET_INT(0, "porcelain", &status_format, - "machine-readable output", STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, - "terminate entries with NUL"), - OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), - OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - /* end commit contents options */ - - { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, - "ok to record an empty change", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, - "ok to record a change with an empty message", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - - OPT_END() -}; - static void determine_whence(struct wt_status *s) { if (file_exists(git_path("MERGE_HEAD"))) @@ -1046,6 +993,7 @@ static const char *read_commit_message(const char *name) } static int parse_and_validate_options(int argc, const char *argv[], + const struct option *options, const char * const usage[], const char *prefix, struct commit *current_head, @@ -1053,8 +1001,7 @@ static int parse_and_validate_options(int argc, const char *argv[], { int f = 0; - argc = parse_options(argc, argv, prefix, builtin_commit_options, usage, - 0); + argc = parse_options(argc, argv, prefix, options, usage, 0); if (force_author && !strchr(force_author, '>')) force_author = find_author_by_nickname(force_author); @@ -1222,7 +1169,7 @@ static int git_status_config(const char *k, const char *v, void *cb) int cmd_status(int argc, const char **argv, const char *prefix) { - struct wt_status s; + static struct wt_status s; int fd; unsigned char sha1[20]; static struct option builtin_status_options[] = { @@ -1422,6 +1369,60 @@ static int run_rewrite_hook(const unsigned char *oldsha1, int cmd_commit(int argc, const char **argv, const char *prefix) { + static struct wt_status s; + static struct option builtin_commit_options[] = { + OPT__QUIET(&quiet, "suppress summary after successful commit"), + OPT__VERBOSE(&verbose, "show diff in commit message template"), + + OPT_GROUP("Commit message options"), + OPT_FILENAME('F', "file", &logfile, "read message from file"), + OPT_STRING(0, "author", &force_author, "author", "override author for commit"), + OPT_STRING(0, "date", &force_date, "date", "override date for commit"), + OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m), + OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"), + OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"), + OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"), + OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"), + OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"), + OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), + OPT_FILENAME('t', "template", &template_file, "use specified template file"), + OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"), + OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), + OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", + "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + /* end commit message options */ + + OPT_GROUP("Commit contents options"), + OPT_BOOLEAN('a', "all", &all, "commit all changed files"), + OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"), + OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), + OPT_BOOLEAN('p', "patch", &patch_interactive, "interactively add changes"), + OPT_BOOLEAN('o', "only", &only, "commit only specified files"), + OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), + OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), + OPT_SET_INT(0, "short", &status_format, "show status concisely", + STATUS_FORMAT_SHORT), + OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"), + OPT_SET_INT(0, "porcelain", &status_format, + "machine-readable output", STATUS_FORMAT_PORCELAIN), + OPT_BOOLEAN('z', "null", &null_termination, + "terminate entries with NUL"), + OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), + OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"), + { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + /* end commit contents options */ + + { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, + "ok to record an empty change", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, + "ok to record a change with an empty message", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + + OPT_END() + }; + struct strbuf sb = STRBUF_INIT; struct strbuf author_ident = STRBUF_INIT; const char *index_file, *reflog_msg; @@ -1431,7 +1432,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct commit_list *parents = NULL, **pptr = &parents; struct stat statbuf; int allow_fast_forward = 1; - struct wt_status s; struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; @@ -1449,7 +1449,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!current_head || parse_commit(current_head)) die(_("could not parse HEAD commit")); } - argc = parse_and_validate_options(argc, argv, builtin_commit_usage, + argc = parse_and_validate_options(argc, argv, builtin_commit_options, + builtin_commit_usage, prefix, current_head, &s); if (dry_run) return dry_run_commit(argc, argv, prefix, current_head, &s); From 3207a3a29179c247d1ee9552511123e426845acb Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 7 May 2012 15:44:44 -0400 Subject: [PATCH 42/54] status: refactor null_termination option This option is passed separately to the wt_status printing functions, whereas every other formatting option is contained in the wt_status struct itself. Let's do the same here, so we can avoid passing it around through the call stack. Signed-off-by: Jeff King --- builtin/commit.c | 17 ++++++++--------- wt-status.c | 26 +++++++++++++------------- wt-status.h | 5 +++-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index 864ed2edc2..85cbeef0f4 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -109,7 +109,6 @@ static int show_ignored_in_status; static const char *only_include_assumed; static struct strbuf message = STRBUF_INIT; -static int null_termination; static enum { STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, @@ -460,10 +459,10 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int switch (status_format) { case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s, null_termination, status_show_branch); + wt_shortstatus_print(s, status_show_branch); break; case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(s, null_termination); + wt_porcelain_print(s); break; case STATUS_FORMAT_LONG: wt_status_print(s); @@ -1082,7 +1081,7 @@ static int parse_and_validate_options(int argc, const char *argv[], if (all && argc > 0) die(_("Paths with -a does not make sense.")); - if (null_termination && status_format == STATUS_FORMAT_LONG) + if (s->null_termination && status_format == STATUS_FORMAT_LONG) status_format = STATUS_FORMAT_PORCELAIN; if (status_format != STATUS_FORMAT_LONG) dry_run = 1; @@ -1181,7 +1180,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT_SET_INT(0, "porcelain", &status_format, "machine-readable output", STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, + OPT_BOOLEAN('z', "null", &s.null_termination, "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", @@ -1206,7 +1205,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) builtin_status_options, builtin_status_usage, 0); - if (null_termination && status_format == STATUS_FORMAT_LONG) + if (s.null_termination && status_format == STATUS_FORMAT_LONG) status_format = STATUS_FORMAT_PORCELAIN; handle_untracked_files_arg(&s); @@ -1231,10 +1230,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) switch (status_format) { case STATUS_FORMAT_SHORT: - wt_shortstatus_print(&s, null_termination, status_show_branch); + wt_shortstatus_print(&s, status_show_branch); break; case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(&s, null_termination); + wt_porcelain_print(&s); break; case STATUS_FORMAT_LONG: s.verbose = verbose; @@ -1406,7 +1405,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, "machine-readable output", STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, + OPT_BOOLEAN('z', "null", &s.null_termination, "terminate entries with NUL"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"), diff --git a/wt-status.c b/wt-status.c index 9ffc535f1a..afb4bd7d76 100644 --- a/wt-status.c +++ b/wt-status.c @@ -777,7 +777,7 @@ void wt_status_print(struct wt_status *s) } } -static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it, +static void wt_shortstatus_unmerged(struct string_list_item *it, struct wt_status *s) { struct wt_status_change_data *d = it->util; @@ -793,7 +793,7 @@ static void wt_shortstatus_unmerged(int null_termination, struct string_list_ite case 7: how = "UU"; break; /* both modified */ } color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how); - if (null_termination) { + if (s->null_termination) { fprintf(stdout, " %s%c", it->string, 0); } else { struct strbuf onebuf = STRBUF_INIT; @@ -804,7 +804,7 @@ static void wt_shortstatus_unmerged(int null_termination, struct string_list_ite } } -static void wt_shortstatus_status(int null_termination, struct string_list_item *it, +static void wt_shortstatus_status(struct string_list_item *it, struct wt_status *s) { struct wt_status_change_data *d = it->util; @@ -818,7 +818,7 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item else putchar(' '); putchar(' '); - if (null_termination) { + if (s->null_termination) { fprintf(stdout, "%s%c", it->string, 0); if (d->head_path) fprintf(stdout, "%s%c", d->head_path, 0); @@ -846,10 +846,10 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item } } -static void wt_shortstatus_other(int null_termination, struct string_list_item *it, +static void wt_shortstatus_other(struct string_list_item *it, struct wt_status *s, const char *sign) { - if (null_termination) { + if (s->null_termination) { fprintf(stdout, "%s %s%c", sign, it->string, 0); } else { struct strbuf onebuf = STRBUF_INIT; @@ -917,7 +917,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) color_fprintf_ln(s->fp, header_color, "]"); } -void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch) +void wt_shortstatus_print(struct wt_status *s, int show_branch) { int i; @@ -931,28 +931,28 @@ void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_br it = &(s->change.items[i]); d = it->util; if (d->stagemask) - wt_shortstatus_unmerged(null_termination, it, s); + wt_shortstatus_unmerged(it, s); else - wt_shortstatus_status(null_termination, it, s); + wt_shortstatus_status(it, s); } for (i = 0; i < s->untracked.nr; i++) { struct string_list_item *it; it = &(s->untracked.items[i]); - wt_shortstatus_other(null_termination, it, s, "??"); + wt_shortstatus_other(it, s, "??"); } for (i = 0; i < s->ignored.nr; i++) { struct string_list_item *it; it = &(s->ignored.items[i]); - wt_shortstatus_other(null_termination, it, s, "!!"); + wt_shortstatus_other(it, s, "!!"); } } -void wt_porcelain_print(struct wt_status *s, int null_termination) +void wt_porcelain_print(struct wt_status *s) { s->use_color = 0; s->relative_paths = 0; s->prefix = NULL; - wt_shortstatus_print(s, null_termination, 0); + wt_shortstatus_print(s, 0); } diff --git a/wt-status.h b/wt-status.h index 682b4c8f7d..34a8d7614f 100644 --- a/wt-status.h +++ b/wt-status.h @@ -56,6 +56,7 @@ struct wt_status { enum untracked_status_type show_untracked_files; const char *ignore_submodule_arg; char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN]; + int null_termination; /* These are computed during processing of the individual sections */ int commitable; @@ -72,8 +73,8 @@ void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); -void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch); -void wt_porcelain_print(struct wt_status *s, int null_termination); +void wt_shortstatus_print(struct wt_status *s, int show_branch); +void wt_porcelain_print(struct wt_status *s); void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...) ; From a5985237878481af5fbca349d0d1ad7d6b2d2bcb Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 7 May 2012 17:02:18 -0400 Subject: [PATCH 43/54] status: fix null termination with "-b" When the "-z" option is given to status, we are supposed to NUL-terminate each record. However, the "-b" code to show the tracking branch did not respect this, and always ended with a newline. Signed-off-by: Jeff King --- t/t7508-status.sh | 9 +++++++++ wt-status.c | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/t/t7508-status.sh b/t/t7508-status.sh index fc57b135c5..24728facf9 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -271,6 +271,15 @@ test_expect_success 'status -s -b' ' ' +test_expect_success 'status -s -z -b' ' + tr "\\n" Q expect.q && + mv expect.q expect && + git status -s -z -b >output && + nul_to_q output.q && + mv output.q output && + test_cmp expect output +' + test_expect_success 'setup dir3' ' mkdir dir3 && : >dir3/untracked1 && diff --git a/wt-status.c b/wt-status.c index afb4bd7d76..b5305ae5f4 100644 --- a/wt-status.c +++ b/wt-status.c @@ -889,8 +889,8 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) if (s->is_initial) color_fprintf(s->fp, header_color, _("Initial commit on ")); if (!stat_tracking_info(branch, &num_ours, &num_theirs)) { - color_fprintf_ln(s->fp, branch_color_local, - "%s", branch_name); + color_fprintf(s->fp, branch_color_local, "%s", branch_name); + fputc(s->null_termination ? '\0' : '\n', s->fp); return; } @@ -914,7 +914,8 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) color_fprintf(s->fp, branch_color_remote, "%d", num_theirs); } - color_fprintf_ln(s->fp, header_color, "]"); + color_fprintf(s->fp, header_color, "]"); + fputc(s->null_termination ? '\0' : '\n', s->fp); } void wt_shortstatus_print(struct wt_status *s, int show_branch) From d4a6bf1fb64d904e210fbf7c5b330b06438a5bd5 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 7 May 2012 17:09:04 -0400 Subject: [PATCH 44/54] status: respect "-b" for porcelain format There is no reason not to, as the user has to explicitly ask for it, so we are not breaking compatibility by doing so. We can do this simply by moving the "show_branch" flag into the wt_status struct. As a bonus, this saves us from passing it explicitly, simplifying the code. Signed-off-by: Jeff King --- Documentation/git-status.txt | 2 +- builtin/commit.c | 9 ++++----- t/t7508-status.sh | 7 ++++++- wt-status.c | 6 +++--- wt-status.h | 3 ++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 3d51717bbe..16ae5c3f27 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -177,7 +177,7 @@ order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL and the terminating newline (but a space still separates the status field from the first filename). Third, filenames containing special characters are not specially formatted; no quoting or -backslash-escaping is performed. Fourth, there is no branch line. +backslash-escaping is performed. CONFIGURATION ------------- diff --git a/builtin/commit.c b/builtin/commit.c index 85cbeef0f4..e2d9cbe3e3 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -114,7 +114,6 @@ static enum { STATUS_FORMAT_SHORT, STATUS_FORMAT_PORCELAIN } status_format = STATUS_FORMAT_LONG; -static int status_show_branch; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -459,7 +458,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int switch (status_format) { case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s, status_show_branch); + wt_shortstatus_print(s); break; case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(s); @@ -1175,7 +1174,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT__VERBOSE(&verbose, "be verbose"), OPT_SET_INT('s', "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), - OPT_BOOLEAN('b', "branch", &status_show_branch, + OPT_BOOLEAN('b', "branch", &s.show_branch, "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, "machine-readable output", @@ -1230,7 +1229,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) switch (status_format) { case STATUS_FORMAT_SHORT: - wt_shortstatus_print(&s, status_show_branch); + wt_shortstatus_print(&s); break; case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(&s); @@ -1402,7 +1401,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), OPT_SET_INT(0, "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), - OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"), + OPT_BOOLEAN(0, "branch", &s.show_branch, "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, "machine-readable output", STATUS_FORMAT_PORCELAIN), OPT_BOOLEAN('z', "null", &s.null_termination, diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 24728facf9..5d0e79fe2a 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -656,9 +656,14 @@ test_expect_success 'status --porcelain ignores color.status' ' git config --unset color.status git config --unset color.ui -test_expect_success 'status --porcelain ignores -b' ' +test_expect_success 'status --porcelain respects -b' ' git status --porcelain -b >output && + { + echo "## master" && + cat expect + } >tmp && + mv tmp expect && test_cmp expect output ' diff --git a/wt-status.c b/wt-status.c index b5305ae5f4..bc268ceda0 100644 --- a/wt-status.c +++ b/wt-status.c @@ -918,11 +918,11 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) fputc(s->null_termination ? '\0' : '\n', s->fp); } -void wt_shortstatus_print(struct wt_status *s, int show_branch) +void wt_shortstatus_print(struct wt_status *s) { int i; - if (show_branch) + if (s->show_branch) wt_shortstatus_print_tracking(s); for (i = 0; i < s->change.nr; i++) { @@ -955,5 +955,5 @@ void wt_porcelain_print(struct wt_status *s) s->use_color = 0; s->relative_paths = 0; s->prefix = NULL; - wt_shortstatus_print(s, 0); + wt_shortstatus_print(s); } diff --git a/wt-status.h b/wt-status.h index 34a8d7614f..ab3c7cc8a1 100644 --- a/wt-status.h +++ b/wt-status.h @@ -57,6 +57,7 @@ struct wt_status { const char *ignore_submodule_arg; char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN]; int null_termination; + int show_branch; /* These are computed during processing of the individual sections */ int commitable; @@ -73,7 +74,7 @@ void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); -void wt_shortstatus_print(struct wt_status *s, int show_branch); +void wt_shortstatus_print(struct wt_status *s); void wt_porcelain_print(struct wt_status *s); void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...) From 4d2292e9a90465cbfa23f1c170c11423d11830df Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 7 May 2012 15:35:03 -0400 Subject: [PATCH 45/54] status: refactor colopts handling The current code reads the config and command-line options into a separate "colopts" variable, and then copies the contents of that variable into the "struct wt_status". We can eliminate the extra variable and copy just write straight into the wt_status struct. This simplifies the "status" code a little bit. Unfortunately, it makes the "commit" code one line more complex; a side effect of the separate variable was that "commit" did not copy the colopts variable, so any column.status configuration had no effect. The result still ends up cleaner, though. In the previous version, it was unclear whether commit simply forgot to copy the colopt variable, or whether it was intentional. Now it explicitly turns off column options. Furthermore, if commit later learns to respect column.status, this will make the end result simpler. I punted on just adding that feature now, because it was sufficiently non-obvious that it should not go into a refactoring patch. Signed-off-by: Jeff King --- builtin/commit.c | 9 ++++----- wt-status.h | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index b15e3119d6..a2ec73d738 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -89,7 +89,6 @@ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; static char *sign_commit; -static unsigned int colopts; /* * The default commit message cleanup mode will remove the lines @@ -1121,7 +1120,7 @@ static int git_status_config(const char *k, const char *v, void *cb) struct wt_status *s = cb; if (!prefixcmp(k, "column.")) - return git_column_config(k, v, "status", &colopts); + return git_column_config(k, v, "status", &s->colopts); if (!strcmp(k, "status.submodulesummary")) { int is_bool; s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); @@ -1187,7 +1186,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when", "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_COLUMN(0, "column", &colopts, "list untracked files in columns"), + OPT_COLUMN(0, "column", &s.colopts, "list untracked files in columns"), OPT_END(), }; @@ -1201,8 +1200,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_status_options, builtin_status_usage, 0); - finalize_colopts(&colopts, -1); - s.colopts = colopts; + finalize_colopts(&s.colopts, -1); if (s.null_termination && status_format == STATUS_FORMAT_LONG) status_format = STATUS_FORMAT_PORCELAIN; @@ -1439,6 +1437,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) wt_status_prepare(&s); git_config(git_commit_config, &s); determine_whence(&s); + s.colopts = 0; if (get_sha1("HEAD", sha1)) current_head = NULL; diff --git a/wt-status.h b/wt-status.h index f4c949bed2..14aa9f7e13 100644 --- a/wt-status.h +++ b/wt-status.h @@ -56,7 +56,7 @@ struct wt_status { enum untracked_status_type show_untracked_files; const char *ignore_submodule_arg; char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN]; - int colopts; + unsigned colopts; int null_termination; int show_branch; From 49dc2cc2c97cd76435d7f1609f2c573b64efd5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Tue, 1 May 2012 13:25:24 +0200 Subject: [PATCH 46/54] dir: convert to strbuf The functions read_directory_recursive() and treat_leading_path() both use buffers sized to fit PATH_MAX characters. The latter can be made to overrun its buffer, e.g. like this: $ a=0123456789abcdef $ a=$a$a$a$a$a$a$a$a $ a=$a$a$a$a$a$a$a$a $ a=$a$a$a$a$a$a$a$a $ git add $a/a Instead of trying to add a check and potentionally forgetting to address similar cases, convert the involved functions and their helpers to use struct strbuf. The patch is suprisingly large because the helpers treat_path() and treat_one_path() modify the buffer as well and thus need to be converted, too. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- dir.c | 75 ++++++++++++++++++++++++++++------------------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/dir.c b/dir.c index 08281d2ef7..6d0ea9e70b 100644 --- a/dir.c +++ b/dir.c @@ -866,14 +866,14 @@ enum path_treatment { }; static enum path_treatment treat_one_path(struct dir_struct *dir, - char *path, int *len, + struct strbuf *path, const struct path_simplify *simplify, int dtype, struct dirent *de) { - int exclude = excluded(dir, path, &dtype); + int exclude = excluded(dir, path->buf, &dtype); if (exclude && (dir->flags & DIR_COLLECT_IGNORED) - && exclude_matches_pathspec(path, *len, simplify)) - dir_add_ignored(dir, path, *len); + && exclude_matches_pathspec(path->buf, path->len, simplify)) + dir_add_ignored(dir, path->buf, path->len); /* * Excluded? If we don't explicitly want to show @@ -883,7 +883,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, return path_ignored; if (dtype == DT_UNKNOWN) - dtype = get_dtype(de, path, *len); + dtype = get_dtype(de, path->buf, path->len); /* * Do we want to see just the ignored files? @@ -900,9 +900,8 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, default: return path_ignored; case DT_DIR: - memcpy(path + *len, "/", 2); - (*len)++; - switch (treat_directory(dir, path, *len, simplify)) { + strbuf_addch(path, '/'); + switch (treat_directory(dir, path->buf, path->len, simplify)) { case show_directory: if (exclude != !!(dir->flags & DIR_SHOW_IGNORED)) @@ -923,26 +922,21 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, static enum path_treatment treat_path(struct dir_struct *dir, struct dirent *de, - char *path, int path_max, + struct strbuf *path, int baselen, - const struct path_simplify *simplify, - int *len) + const struct path_simplify *simplify) { int dtype; if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) return path_ignored; - *len = strlen(de->d_name); - /* Ignore overly long pathnames! */ - if (*len + baselen + 8 > path_max) - return path_ignored; - memcpy(path + baselen, de->d_name, *len + 1); - *len += baselen; - if (simplify_away(path, *len, simplify)) + strbuf_setlen(path, baselen); + strbuf_addstr(path, de->d_name); + if (simplify_away(path->buf, path->len, simplify)) return path_ignored; dtype = DTYPE(de); - return treat_one_path(dir, path, len, simplify, dtype, de); + return treat_one_path(dir, path, simplify, dtype, de); } /* @@ -964,16 +958,15 @@ static int read_directory_recursive(struct dir_struct *dir, if (fdir) { struct dirent *de; - char path[PATH_MAX + 1]; - memcpy(path, base, baselen); + struct strbuf path = STRBUF_INIT; + + strbuf_add(&path, base, baselen); while ((de = readdir(fdir)) != NULL) { - int len; - switch (treat_path(dir, de, path, sizeof(path), - baselen, simplify, &len)) { + switch (treat_path(dir, de, &path, baselen, simplify)) { case path_recurse: contents += read_directory_recursive - (dir, path, len, 0, simplify); + (dir, path.buf, path.len, 0, simplify); continue; case path_ignored: continue; @@ -984,10 +977,11 @@ static int read_directory_recursive(struct dir_struct *dir, if (check_only) goto exit_early; else - dir_add_name(dir, path, len); + dir_add_name(dir, path.buf, path.len); } exit_early: closedir(fdir); + strbuf_release(&path); } return contents; @@ -1051,8 +1045,8 @@ static int treat_leading_path(struct dir_struct *dir, const char *path, int len, const struct path_simplify *simplify) { - char pathbuf[PATH_MAX]; - int baselen, blen; + struct strbuf sb = STRBUF_INIT; + int baselen, rc = 0; const char *cp; while (len && path[len - 1] == '/') @@ -1067,19 +1061,22 @@ static int treat_leading_path(struct dir_struct *dir, baselen = len; else baselen = cp - path; - memcpy(pathbuf, path, baselen); - pathbuf[baselen] = '\0'; - if (!is_directory(pathbuf)) - return 0; - if (simplify_away(pathbuf, baselen, simplify)) - return 0; - blen = baselen; - if (treat_one_path(dir, pathbuf, &blen, simplify, + strbuf_setlen(&sb, 0); + strbuf_add(&sb, path, baselen); + if (!is_directory(sb.buf)) + break; + if (simplify_away(sb.buf, sb.len, simplify)) + break; + if (treat_one_path(dir, &sb, simplify, DT_DIR, NULL) == path_ignored) - return 0; /* do not recurse into it */ - if (len <= baselen) - return 1; /* finished checking */ + break; /* do not recurse into it */ + if (len <= baselen) { + rc = 1; + break; /* finished checking */ + } } + strbuf_release(&sb); + return rc; } int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec) From 4c5197d10f01f1dce817af05fd0837bce84708b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 6 May 2012 20:13:22 +0700 Subject: [PATCH 47/54] apply: remove lego in i18n string in gitdiff_verify_name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It marks the string "...inconsistent %s filename..." where %s is either "old" or "new" from caller. Make it two strings "...inconsistent new filename..." and "...inconsistent old filename...". Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/apply.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/builtin/apply.c b/builtin/apply.c index 725712d788..1793c33ee9 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -919,7 +919,10 @@ static int gitdiff_hdrend(const char *line, struct patch *patch) * their names against any previous information, just * to make sure.. */ -static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) +#define DIFF_OLD_NAME 0 +#define DIFF_NEW_NAME 1 + +static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, int side) { if (!orig_name && !isnull) return find_name(line, NULL, p_value, TERM_TAB); @@ -934,7 +937,9 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr); another = find_name(line, NULL, p_value, TERM_TAB); if (!another || memcmp(another, name, len + 1)) - die(_("git apply: bad git-diff - inconsistent %s filename on line %d"), oldnew, linenr); + die((side == DIFF_NEW_NAME) ? + _("git apply: bad git-diff - inconsistent new filename on line %d") : + _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr); free(another); return orig_name; } @@ -949,7 +954,8 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, static int gitdiff_oldname(const char *line, struct patch *patch) { char *orig = patch->old_name; - patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old"); + patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, + DIFF_OLD_NAME); if (orig != patch->old_name) free(orig); return 0; @@ -958,7 +964,8 @@ static int gitdiff_oldname(const char *line, struct patch *patch) static int gitdiff_newname(const char *line, struct patch *patch) { char *orig = patch->new_name; - patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new"); + patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, + DIFF_NEW_NAME); if (orig != patch->new_name) free(orig); return 0; From 8338f771fe9720766dfcb25b6dca58832e24a0fd Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Tue, 8 May 2012 19:22:33 +0200 Subject: [PATCH 48/54] checkout: do not corrupt HEAD on empty repo In abe1998 ("git checkout -b: allow switching out of an unborn branch"), a code-path overly-optimisticly assumed that a branch-name was specified. This is not always the case, and as a result a NULL-pointer was attempted printed to .git/HEAD. This could lead to at least two different failure modes: 1) vsnprintf formated the NULL-string as something useful (e.g "(null)") 2) vsnprintf crashed Neither were very convenient for formatting a new HEAD-reference. To fix this, reintroduce some strictness so we only take this new codepath if a banch-name was specified. Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano --- builtin/checkout.c | 2 +- t/t2015-checkout-unborn.sh | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index b76e2c0451..44ecc22e52 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1100,7 +1100,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (opts.writeout_stage) die(_("--ours/--theirs is incompatible with switching branches.")); - if (!new.commit) { + if (!new.commit && opts.new_branch) { unsigned char rev[20]; int flag; diff --git a/t/t2015-checkout-unborn.sh b/t/t2015-checkout-unborn.sh index 6352b74e2e..37bdcedcc9 100755 --- a/t/t2015-checkout-unborn.sh +++ b/t/t2015-checkout-unborn.sh @@ -46,4 +46,15 @@ test_expect_success 'checking out another branch from unborn state' ' test_cmp expect actual ' +test_expect_success 'checking out in a newly created repo' ' + test_create_repo empty && + ( + cd empty && + git symbolic-ref HEAD >expect && + test_must_fail git checkout && + git symbolic-ref HEAD >actual && + test_cmp expect actual + ) +' + test_done From 5ad3e16a6f075deff3cddd6d12ae89fafb0c718d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 May 2012 10:45:42 -0700 Subject: [PATCH 49/54] Update draft release notes to 1.7.10.2 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/1.7.10.2.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Documentation/RelNotes/1.7.10.2.txt b/Documentation/RelNotes/1.7.10.2.txt index 04fe29404c..55e960a54b 100644 --- a/Documentation/RelNotes/1.7.10.2.txt +++ b/Documentation/RelNotes/1.7.10.2.txt @@ -24,6 +24,9 @@ Fixes since v1.7.10.1 be both revision name and a pathname, even though $name can never be a path in the context of the command. + * The "include.path" facility in the configuration mechanism added in + 1.7.10 forgot to interpret "~/path" and "~user/path" as it should. + * "git config --rename-section" to rename an existing section into a bogus one did not check the new name. @@ -33,11 +36,33 @@ Fixes since v1.7.10.1 * The report from "git fetch" said "new branch" even for a non branch ref. + * The http-backend (the server side of the smart http transfer) used + to overwrite GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL with the + value obtained from REMOTE_USER unconditionally, making it + impossible for the server side site-specific customization to use + different identity sources to affect the names logged. It now uses + REMOTE_USER only as a fallback value. + + * "log --graph" was not very friendly with "--stat" option and its + output had line breaks at wrong places. + * Octopus merge strategy did not reduce heads that are recorded in the final commit correctly. + * "git push" over smart-http lost progress output a few releases ago; + this release resurrects it. + + * The insn sheet given by "rebase -i" did not make it clear that the + insn lines can be re-ordered to affect the order of the commits in + the resulting history. + + * A contrib script "rerere-train" did not work out of the box unless + user futzed with her $PATH. + * The i18n of error message "git stash save" was not properly done. + * "git submodule" used a sed script that some platforms mishandled. + * When using a Perl script on a system where "perl" found on user's $PATH could be ancient or otherwise broken, we allow builders to specify the path to a good copy of Perl with $PERL_PATH. The From b7fbce05e530a5c7d6bcbadcb3a53aca815d5cc5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 10 May 2012 11:04:31 -0700 Subject: [PATCH 50/54] The tenth batch of topics Signed-off-by: Junio C Hamano --- Documentation/RelNotes/1.7.11.txt | 38 +++++++++++++++++++------------ 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/Documentation/RelNotes/1.7.11.txt b/Documentation/RelNotes/1.7.11.txt index 8dfeddcd68..37f374bf1d 100644 --- a/Documentation/RelNotes/1.7.11.txt +++ b/Documentation/RelNotes/1.7.11.txt @@ -25,13 +25,15 @@ UI, Workflows & Features tracking. Also "branch" learned the "-q"uiet option to squelch informational message. + * Your build platform may support hardlinks but you may prefer not to + use them, e.g. when installing to DESTDIR to make a tarball and + untarring on a filesystem that has poor support for hardlinks. + There is a Makefile option NO_INSTALL_HARDLINKS for you. + * The smart-http backend used to always override GIT_COMMITTER_* variables with REMOTE_USER and REMOTE_ADDR, but these variables are now preserved when set. - * "include.path" mechanism of the configuration files learned to - understand "~/path" and "~user/path". - * "git am" learned the "--include" option, which is an opposite of existing the "--exclude" option. @@ -83,6 +85,9 @@ Performance and Internal Implementation (please report possible regressions) * An experimental "version 4" format of the index file has been introduced to reduce on-disk footprint and I/O overhead. + * "git archive" learned to produce its output without reading the + blob object it writes out in memory in its entirety. + * The code to compute hash values for lines used by the internal diff engine was optimized on little-endian machines, using the same trick the kernel folks came up with. @@ -124,6 +129,22 @@ Unless otherwise noted, all the fixes since v1.7.10 in the maintenance releases are contained in this release (see release notes to them for details). + * When checking out another commit from an already detached state, we + used to report all commits that are not reachable from any of the + refs as lossage, but some of them might be reachable from the new + HEAD, and there is no need to warn about them. + (merge 5d88639 js/checkout-detach-count later to maint). + + * Some time ago, "git clone" lost the progress output for its + "checkout" phase; when run without any "--quiet" option, it should + give progress to the lengthy operation. + (merge 8f63da1 ef/maint-clone-progress-fix later to maint). + + * "git status --porcelain" ignored "--branch" option by mistake. The + output for "git status --branch -z" was also incorrect and did not + terminate the record for the current branch name with NUL as asked. + (merge d4a6bf1 jk/maint-status-porcelain-z-b later to maint). + * "git diff --stat" used to fully count a binary file with modified execution bits whose contents is unmodified, which was not quite right. @@ -132,14 +153,3 @@ details). NUL. The fix is not entirely correct when the output also asks for --patch and/or --stat, though. (merge fafd382 jk/maint-tformat-with-z later to maint). - - * "git push" over smart-http lost progress output a few releases ago. - (merge e304aeb jk/maint-push-progress later to maint). - - * A contrib script "rerere-train" did not work out of the box unless - user futzed with her $PATH. - (merge 53876fc jc/rerere-train later to maint). - - * "log --graph" was not very friendly with "--stat" option and its - output had line breaks at wrong places. - (merge bafa16e lp/diffstat-with-graph later to maint). From e6a1c43aafa8b221e05e57132549dcb3cd7866cf Mon Sep 17 00:00:00 2001 From: Heiko Voigt Date: Thu, 10 May 2012 20:59:04 +0200 Subject: [PATCH 51/54] document submdule.$name.update=none option for gitmodules This option was not yet described in the gitmodules documentation. We only described it in the 'git submodule' command documentation but gitmodules is the more natural place to look. A short reference in the 'git submodule' documentation should be sufficient since the details can now be found in the documentation to gitmodules. Signed-off-by: Heiko Voigt Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 7 ++----- Documentation/gitmodules.txt | 5 ++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index c243ee552b..c83a856574 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -140,7 +140,8 @@ update:: checkout the commit specified in the index of the containing repository. This will make the submodules HEAD be detached unless `--rebase` or `--merge` is specified or the key `submodule.$name.update` is set to - `rebase`, `merge` or `none`. + `rebase`, `merge` or `none`. `none` can be overriden by specifying + `--checkout`. + If the submodule is not yet initialized, and you just want to use the setting as stored in .gitmodules, you can automatically initialize the @@ -148,10 +149,6 @@ submodule with the `--init` option. + If `--recursive` is specified, this command will recurse into the registered submodules, and update any nested submodules within. -+ -If the configuration key `submodule.$name.update` is set to `none` the -submodule with name `$name` will not be updated by default. This can be -overriden by adding `--checkout` to the command. summary:: Show commit summary between the given commit (defaults to HEAD) and diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 4e1fd52e7d..4effd78902 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -41,8 +41,11 @@ submodule..update:: the commit specified in the superproject. If 'merge', the commit specified in the superproject will be merged into the current branch in the submodule. + If 'none', the submodule with name `$name` will not be updated + by default. + This config option is overridden if 'git submodule update' is given - the '--merge' or '--rebase' options. + the '--merge', '--rebase' or '--checkout' options. submodule..fetchRecurseSubmodules:: This option can be used to control recursive fetching of this From b6555d731e0828c356247a7836bdbd6e31c7a639 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 11 May 2012 11:25:28 -0700 Subject: [PATCH 52/54] Git 1.7.10.2 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/1.7.10.2.txt | 14 ++++++++++++++ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Documentation/RelNotes/1.7.10.2.txt b/Documentation/RelNotes/1.7.10.2.txt index 55e960a54b..7a7e9d6fd1 100644 --- a/Documentation/RelNotes/1.7.10.2.txt +++ b/Documentation/RelNotes/1.7.10.2.txt @@ -14,6 +14,9 @@ Fixes since v1.7.10.1 * HTTP transport that requires authentication did not work correctly when multiple connections are used simultaneously. + * Minor memory leak during unpack_trees (hence "merge" and "checkout" + to check out another branch) has been plugged. + * In the older days, the header "Conflicts:" in "cherry-pick" and "merge" was separated by a blank line from the list of paths that follow for readability, but when "merge" was rewritten in C, we lost it by @@ -52,13 +55,24 @@ Fixes since v1.7.10.1 * "git push" over smart-http lost progress output a few releases ago; this release resurrects it. + * The error and advice messages given by "git push" when it fails due + to non-ff were not very helpful to new users; it has been broken + into three cases, and each is given a separate advice message. + * The insn sheet given by "rebase -i" did not make it clear that the insn lines can be re-ordered to affect the order of the commits in the resulting history. + * "git repack" used to write out unreachable objects as loose objects + when repacking, even if such loose objects will immediately pruned + due to its age. + * A contrib script "rerere-train" did not work out of the box unless user futzed with her $PATH. + * "git rev-parse --show-prefix" used to emit nothing when run at the + top-level of the working tree, but now it gives a blank line. + * The i18n of error message "git stash save" was not properly done. * "git submodule" used a sed script that some platforms mishandled. diff --git a/Documentation/git.txt b/Documentation/git.txt index 8527775988..c543213126 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,9 +44,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.10.1/git.html[documentation for release 1.7.10.1] +* link:v1.7.10.2/git.html[documentation for release 1.7.10.2] * release notes for + link:RelNotes/1.7.10.2.txt[1.7.10.2], link:RelNotes/1.7.10.1.txt[1.7.10.1], link:RelNotes/1.7.10.txt[1.7.10]. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 692222afb3..1df7b0ff66 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.10.1 +DEF_VER=v1.7.10.2 LF=' ' From cd07cc53125fb2ca77a644831d4e5d9517bb7a05 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 11 May 2012 11:40:43 -0700 Subject: [PATCH 53/54] Update draft release notes to 1.7.11 (11th batch) Signed-off-by: Junio C Hamano --- Documentation/RelNotes/1.7.11.txt | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Documentation/RelNotes/1.7.11.txt b/Documentation/RelNotes/1.7.11.txt index 37f374bf1d..9ef7e18ca5 100644 --- a/Documentation/RelNotes/1.7.11.txt +++ b/Documentation/RelNotes/1.7.11.txt @@ -53,9 +53,6 @@ UI, Workflows & Features * The "fmt-merge-msg" command learns to list the primary contributors involved in the side topic you are merging. - * The cases "git push" fails due to non-ff can be broken into three - categories; each case is given a separate advise message. - * "git rebase" learned to optionally keep commits that do not introduce any change in the original history. @@ -94,21 +91,11 @@ Performance and Internal Implementation (please report possible regressions) * "git apply" had some memory leaks plugged. - * "git repack" used to write out unreachable objects as loose objects - when repacking, even if such loose objects will immediately pruned - due to its age. - * Setting up a revision traversal with many starting points was inefficient as these were placed in a date-order priority queue one-by-one. Now they are collected in the queue unordered first, and sorted immediately before getting used. - * "git rev-parse --show-prefix" used to emit nothing when run at the - top-level of the working tree, but now it gives a blank line. - - * Minor memory leak during unpack_trees (hence "merge" and "checkout" - to check out another branch) has been plugged. - * More lower-level commands learned to use the streaming API to read from the object store without keeping everything in core. @@ -129,6 +116,14 @@ Unless otherwise noted, all the fixes since v1.7.10 in the maintenance releases are contained in this release (see release notes to them for details). + * The DWIM behaviour for "log --pretty=format:%gd -g" was somewhat + broken and gave undue precedence to configured log.date, causing + "git stash list" to show "stash@{time stamp string}". + (merge 55ccf85 jk/maint-reflog-walk-count-vs-time later to maint). + + * Running "git checkout" on an unborn branch used to corrupt HEAD. + (merge 8338f77 ef/checkout-empty later to maint). + * When checking out another commit from an already detached state, we used to report all commits that are not reachable from any of the refs as lossage, but some of them might be reachable from the new From 6b179adfe93d13c2f993668cd12eec52860486ac Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Mon, 14 May 2012 17:35:18 +0200 Subject: [PATCH 54/54] completion: add new __git_complete helper This simplifies the completions, and would make it easier to define aliases in the future. Signed-off-by: Felipe Contreras Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 70 ++++++++++++-------------- t/t9902-completion.sh | 2 +- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9f56ec7a6b..d60bb8ac8f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2603,21 +2603,6 @@ _git () { local i c=1 command __git_dir - if [[ -n ${ZSH_VERSION-} ]]; then - emulate -L bash - setopt KSH_TYPESET - - # workaround zsh's bug that leaves 'words' as a special - # variable in versions < 4.3.12 - typeset -h words - - # workaround zsh's bug that quotes spaces in the COMPREPLY - # array if IFS doesn't contain spaces. - typeset -h IFS - fi - - local cur words cword prev - _get_comp_words_by_ref -n =: cur words cword prev while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -2667,22 +2652,6 @@ _git () _gitk () { - if [[ -n ${ZSH_VERSION-} ]]; then - emulate -L bash - setopt KSH_TYPESET - - # workaround zsh's bug that leaves 'words' as a special - # variable in versions < 4.3.12 - typeset -h words - - # workaround zsh's bug that quotes spaces in the COMPREPLY - # array if IFS doesn't contain spaces. - typeset -h IFS - fi - - local cur words cword prev - _get_comp_words_by_ref -n =: cur words cword prev - __git_has_doubledash && return local g="$(__gitdir)" @@ -2703,16 +2672,43 @@ _gitk () __git_complete_revlist } -complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \ - || complete -o default -o nospace -F _git git -complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \ - || complete -o default -o nospace -F _gitk gitk +__git_func_wrap () +{ + if [[ -n ${ZSH_VERSION-} ]]; then + emulate -L bash + setopt KSH_TYPESET + + # workaround zsh's bug that leaves 'words' as a special + # variable in versions < 4.3.12 + typeset -h words + + # workaround zsh's bug that quotes spaces in the COMPREPLY + # array if IFS doesn't contain spaces. + typeset -h IFS + fi + local cur words cword prev + _get_comp_words_by_ref -n =: cur words cword prev + $1 +} + +# Setup completion for certain functions defined above by setting common +# variables and workarounds. +# This is NOT a public function; use at your own risk. +__git_complete () +{ + local wrapper="__git_wrap${2}" + eval "$wrapper () { __git_func_wrap $2 ; }" + complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \ + || complete -o default -o nospace -F $wrapper $1 +} + +__git_complete git _git +__git_complete gitk _gitk # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then -complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \ - || complete -o default -o nospace -F _git git.exe +__git_complete git.exe _git fi diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 5bda6b6e18..0f09fd6109 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -63,7 +63,7 @@ run_completion () local _cword _words=( $1 ) (( _cword = ${#_words[@]} - 1 )) - _git && print_comp + __git_wrap_git && print_comp } test_completion ()