diff options
40 files changed, 1410 insertions, 408 deletions
diff --git a/.gitignore b/.gitignore index e90e2c3503..d190c0ad07 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,4 @@ git-core-*/?* *.dsc *.deb git-core.spec +*.exe diff --git a/Documentation/Makefile b/Documentation/Makefile index bb21d6af44..3cfa360a9e 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -17,14 +17,14 @@ DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES)) DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT)) DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT)) -prefix=$(HOME) +prefix?=$(HOME) bin=$(prefix)/bin mandir=$(prefix)/man man1=$(mandir)/man1 man7=$(mandir)/man7 # DESTDIR= -INSTALL=install +INSTALL?=install # # Please note that there is a minor bug in asciidoc. diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 8cbbb4b853..e095f93c08 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -9,7 +9,7 @@ git-apply - Apply patch on a GIT index file and a work tree SYNOPSIS -------- -'git-apply' [--no-merge] [--stat] [--summary] [--check] [--index] [--show-files] [--apply] [<patch>...] +'git-apply' [--stat] [--summary] [--check] [--index] [--show-files] [--apply] [<patch>...] DESCRIPTION ----------- @@ -22,11 +22,6 @@ OPTIONS The files to read patch from. '-' can be used to read from the standard input. ---no-merge:: - The default mode of operation is the merge behaviour - which is not implemented yet. This flag explicitly - tells the program not to use the merge behaviour. - --stat:: Instead of applying the patch, output diffstat for the input. Turns off "apply". diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt index bb543788c0..8f01ca6a16 100644 --- a/Documentation/git-applymbox.txt +++ b/Documentation/git-applymbox.txt @@ -8,7 +8,7 @@ git-applymbox - Apply a series of patches in a mailbox SYNOPSIS -------- -'git-applymbox' [-u] [-k] [-q] ( -c .dotest/<num> | <mbox> ) [ <signoff> ] +'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/<num> | <mbox> ) [ <signoff> ] DESCRIPTION ----------- @@ -33,6 +33,14 @@ OPTIONS munging, and is most useful when used to read back 'git format-patch --mbox' output. +-m:: + Patches are applied with `git-apply` command, and unless + it cleanly applies without fuzz, the processing fails. + With this flag, if a tree that the patch applies cleanly + is found in a repository, the patch is applied to the + tree and then a 3-way merge between the resulting tree + and the current tree. + -u:: By default, the commit log message, author name and author email are taken from the e-mail without any diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index a1483ffd0f..f3ef4c1e04 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -8,7 +8,7 @@ git-format-patch - Prepare patches for e-mail submission. SYNOPSIS -------- -'git-format-patch' [-n][-o <dir>][-k][--mbox][--diff-options] <his> [<mine>] +'git-format-patch' [-n][-o <dir>|--stdout][-k][--mbox][--diff-options] <his> [<mine>] DESCRIPTION ----------- @@ -54,6 +54,10 @@ OPTIONS concatenated together and fed to `git-applymbox`. Implies --author and --date. +--stdout:: + This flag generates the mbox formatted output to the + standard output, instead of saving them into a file per + patch and implies --mbox. Author ------ diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt index 557d2e9056..03a9477664 100644 --- a/Documentation/git-mailsplit.txt +++ b/Documentation/git-mailsplit.txt @@ -7,7 +7,7 @@ git-mailsplit - Totally braindamaged mbox splitter program. SYNOPSIS -------- -'git-mailsplit' <mbox> <directory> +'git-mailsplit' [-d<prec>] [<mbox>] <directory> DESCRIPTION ----------- @@ -17,14 +17,23 @@ directory so you can process them further from there. OPTIONS ------- <mbox>:: - Mbox file to split. + Mbox file to split. If not given, the mbox is read from + the standard input. <directory>:: Directory in which to place the individual messages. +-d<prec>:: + Instead of the default 4 digits with leading zeros, + different precision can be specified for the generated + filenames. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> +and Junio C Hamano <junkio@cox.net> + Documentation -------------- diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index e11f51c266..3984812cec 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -8,19 +8,27 @@ git-tag - Create a tag object signed with GPG SYNOPSIS -------- -'git-tag' [-a | -s] [-f] [-m <msg>] <name> [<head>] +'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg>] <name> [<head>] DESCRIPTION ----------- -Adds a "tag" reference in .git/refs/tags/ +Adds a 'tag' reference in .git/refs/tags/ -Unless "-f" is given, the tag must not yet exist in ".git/refs/tags" +Unless `-f` is given, the tag must not yet exist in +`.git/refs/tags/` directory. -If "-s" or "-a" is passed, the user will be prompted for a tag message. -and a tag object is created. Otherwise just the SHA1 object -name of the commit object is written. +If one of `-a`, `-s`, or `-u <key-id>` is passed, the command +creates a 'tag' object, and requires the tag message. Unless +`-m <msg>` is given, an editor is started for the user to type +in the tag message. -A GnuPG signed tag object will be created when "-s" is used. +Otherwise just the SHA1 object name of the commit object is +written (i.e. an lightweight tag). + +A GnuPG signed tag object will be created when `-s` or `-u +<key-id>` is used. When `-u <key-id>` is not used, the +committer identity for the current user is used to find the +GnuPG key for signing. Author @@ -27,8 +27,12 @@ # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). # +# Define NO_MMAP if you want to avoid mmap. +# # Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. # +# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). +# # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you # sufficient guarantee that no collisions between objects will ever happen. @@ -48,7 +52,7 @@ # DEFINES += -DUSE_STDEV -GIT_VERSION = 0.99.8 +GIT_VERSION = 0.99.8.GIT CFLAGS = -g -O2 -Wall ALL_CFLAGS = $(CFLAGS) $(PLATFORM_DEFINES) $(DEFINES) @@ -83,7 +87,7 @@ SCRIPT_SH = \ git-repack.sh git-request-pull.sh git-reset.sh \ git-resolve.sh git-revert.sh git-sh-setup.sh git-status.sh \ git-tag.sh git-verify-tag.sh git-whatchanged.sh git.sh \ - git-applymbox.sh git-applypatch.sh \ + git-applymbox.sh git-applypatch.sh git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ git-merge-resolve.sh git-grep.sh @@ -96,31 +100,31 @@ SCRIPT_PYTHON = \ # The ones that do not have to link with lcrypto nor lz. SIMPLE_PROGRAMS = \ - git-get-tar-commit-id git-mailinfo git-mailsplit git-stripspace \ - git-daemon git-var + git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \ + git-stripspace$X git-var$X git-daemon$X # ... and all the rest PROGRAMS = \ - git-apply git-cat-file \ - git-checkout-index git-clone-pack git-commit-tree \ - git-convert-objects git-diff-files \ - git-diff-index git-diff-stages \ - git-diff-tree git-fetch-pack git-fsck-objects \ - git-hash-object git-init-db \ - git-local-fetch git-ls-files git-ls-tree git-merge-base \ - git-merge-index git-mktag git-pack-objects git-patch-id \ - git-peek-remote git-prune-packed git-read-tree \ - git-receive-pack git-rev-list git-rev-parse \ - git-send-pack git-show-branch \ - git-show-index git-ssh-fetch \ - git-ssh-upload git-tar-tree git-unpack-file \ - git-unpack-objects git-update-index git-update-server-info \ - git-upload-pack git-verify-pack git-write-tree \ - git-update-ref git-symbolic-ref \ + git-apply$X git-cat-file$X \ + git-checkout-index$X git-clone-pack$X git-commit-tree$X \ + git-convert-objects$X git-diff-files$X \ + git-diff-index$X git-diff-stages$X \ + git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \ + git-hash-object$X git-init-db$X \ + git-local-fetch$X git-ls-files$X git-ls-tree$X git-merge-base$X \ + git-merge-index$X git-mktag$X git-pack-objects$X git-patch-id$X \ + git-peek-remote$X git-prune-packed$X git-read-tree$X \ + git-receive-pack$X git-rev-list$X git-rev-parse$X \ + git-send-pack$X git-show-branch$X \ + git-show-index$X git-ssh-fetch$X \ + git-ssh-upload$X git-tar-tree$X git-unpack-file$X \ + git-unpack-objects$X git-update-index$X git-update-server-info$X \ + git-upload-pack$X git-verify-pack$X git-write-tree$X \ + git-update-ref$X git-symbolic-ref$X \ $(SIMPLE_PROGRAMS) # Backward compatibility -- to be removed after 1.0 -PROGRAMS += git-ssh-pull git-ssh-push +PROGRAMS += git-ssh-pull$X git-ssh-push$X GIT_LIST_TWEAK = @@ -162,11 +166,19 @@ LIBS += -lz # # Platform specific tweaks # -ifeq ($(shell uname -s),Darwin) + +# We choose to avoid "if .. else if .. else .. endif endif" +# because maintaining the nesting to match is a pain. If +# we had "elif" things would have been much nicer... +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') +uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') + +ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease endif -ifeq ($(shell uname -s),SunOS) +ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash @@ -176,13 +188,20 @@ ifeq ($(shell uname -s),SunOS) TAR = gtar PLATFORM_DEFINES += -D__EXTENSIONS__ endif -ifneq (,$(findstring arm,$(shell uname -m))) - ARM_SHA1 = YesPlease +ifeq ($(uname_O),Cygwin) + NO_STRCASESTR = YesPlease + NEEDS_LIBICONV = YesPlease + NO_IPV6 = YesPlease + X = .exe + PLATFORM_DEFINES += -DUSE_SYMLINK_HEAD=0 endif -ifeq ($(shell uname -s),OpenBSD) +ifeq ($(uname_S),OpenBSD) NEEDS_LIBICONV = YesPlease PLATFORM_DEFINES += -I/usr/local/include -L/usr/local/lib endif +ifneq (,$(findstring arm,$(uname_M))) + ARM_SHA1 = YesPlease +endif ifndef NO_CURL ifdef CURLDIR @@ -192,7 +211,7 @@ ifndef NO_CURL else CURL_LIBCURL = -lcurl endif - PROGRAMS += git-http-fetch + PROGRAMS += git-http-fetch$X endif ifndef SHELL_PATH @@ -249,6 +268,13 @@ ifdef NO_STRCASESTR DEFINES += -Dstrcasestr=gitstrcasestr -DNO_STRCASESTR=1 LIB_OBJS += compat/strcasestr.o endif +ifdef NO_MMAP + DEFINES += -Dmmap=gitfakemmap -Dmunmap=gitfakemunmap -DNO_MMAP + LIB_OBJS += compat/mmap.o +endif +ifdef NO_IPV6 + DEFINES += -DNO_IPV6 -Dsockaddr_storage=sockaddr_in +endif ifdef PPC_SHA1 SHA1_HEADER = "ppc/sha1.h" @@ -275,7 +301,7 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ gitk -export TAR INSTALL DESTDIR SHELL_PATH +export prefix TAR INSTALL DESTDIR SHELL_PATH ### Build rules all: $(PROGRAMS) $(SCRIPTS) @@ -287,25 +313,31 @@ git: git.sh Makefile rm -f $@+ $@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + -e 's/@@X@@/$(X)/g' \ $(GIT_LIST_TWEAK) <$@.sh >$@+ chmod +x $@+ mv $@+ $@ $(filter-out git,$(patsubst %.sh,%,$(SCRIPT_SH))) : % : %.sh rm -f $@ - sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' $@.sh >$@ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' \ + -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + $@.sh >$@ chmod +x $@ $(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl rm -f $@ - sed -e '1s|#!.*perl|#!$(PERL_PATH)|' $@.perl >$@ + sed -e '1s|#!.*perl|#!$(PERL_PATH)|' \ + -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + $@.perl >$@ chmod +x $@ $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py rm -f $@ sed -e '1s|#!.*python|#!$(PYTHON_PATH)|' \ -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR)|g' \ - $@.py >$@ + -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + $@.py >$@ chmod +x $@ %.o: %.c @@ -313,30 +345,30 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py %.o: %.S $(CC) -o $*.o -c $(ALL_CFLAGS) $< -git-%: %.o $(LIB_FILE) +git-%$X: %.o $(LIB_FILE) $(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIBS) -git-mailinfo : SIMPLE_LIB += $(LIB_4_ICONV) +git-mailinfo$X : SIMPLE_LIB += $(LIB_4_ICONV) $(SIMPLE_PROGRAMS) : $(LIB_FILE) -$(SIMPLE_PROGRAMS) : git-% : %.o +$(SIMPLE_PROGRAMS) : git-%$X : %.o $(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIB_FILE) $(SIMPLE_LIB) -git-http-fetch: fetch.o -git-local-fetch: fetch.o -git-ssh-fetch: rsh.o fetch.o -git-ssh-upload: rsh.o -git-ssh-pull: rsh.o fetch.o -git-ssh-push: rsh.o +git-http-fetch$X: fetch.o +git-local-fetch$X: fetch.o +git-ssh-fetch$X: rsh.o fetch.o +git-ssh-upload$X: rsh.o +git-ssh-pull$X: rsh.o fetch.o +git-ssh-push$X: rsh.o -git-http-fetch: LIBS += $(CURL_LIBCURL) -git-rev-list: LIBS += $(OPENSSL_LIBSSL) +git-http-fetch$X: LIBS += $(CURL_LIBCURL) +git-rev-list$X: LIBS += $(OPENSSL_LIBSSL) init-db.o: init-db.c $(CC) -c $(ALL_CFLAGS) \ -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir)"' $*.c $(LIB_OBJS): $(LIB_H) -$(patsubst git-%,%.o,$(PROGRAMS)): $(LIB_H) +$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) @@ -351,10 +383,10 @@ doc: test: all $(MAKE) -C t/ all -test-date: test-date.c date.o +test-date$X: test-date.c date.o $(CC) $(ALL_CFLAGS) -o $@ test-date.c date.o -test-delta: test-delta.c diff-delta.o patch-delta.o +test-delta$X: test-delta.c diff-delta.o patch-delta.o $(CC) $(ALL_CFLAGS) -o $@ $^ check: @@ -5,26 +5,17 @@ * * This applies patches on top of some (arbitrary) version of the SCM. * - * NOTE! It does all its work in the index file, and only cares about - * the files in the working directory if you tell it to "merge" the - * patch apply. - * - * Even when merging it always takes the source from the index, and - * uses the working tree as a "branch" for a 3-way merge. */ #include <ctype.h> #include <fnmatch.h> #include "cache.h" -// We default to the merge behaviour, since that's what most people would -// expect. -// // --check turns on checking that the working tree matches the // files that are being modified, but doesn't apply the patch // --stat does just a diffstat, and doesn't actually apply // --show-files shows the directory changes +// --show-index-info shows the old and new index info for paths if available. // -static int merge_patch = 1; static int check_index = 0; static int write_index = 0; static int diffstat = 0; @@ -32,8 +23,9 @@ static int summary = 0; static int check = 0; static int apply = 1; static int show_files = 0; +static int show_index_info = 0; static const char apply_usage[] = -"git-apply [--no-merge] [--stat] [--summary] [--check] [--index] [--apply] [--show-files] <patch>..."; +"git-apply [--stat] [--summary] [--check] [--index] [--apply] [--show-files] [--show-index-info] <patch>..."; /* * For "diff-stat" like behaviour, we keep track of the biggest change @@ -66,6 +58,8 @@ struct patch { struct fragment *fragments; char *result; unsigned long resultsize; + char old_sha1_prefix[41]; + char new_sha1_prefix[41]; struct patch *next; }; @@ -344,6 +338,38 @@ static int gitdiff_dissimilarity(const char *line, struct patch *patch) return 0; } +static int gitdiff_index(const char *line, struct patch *patch) +{ + /* index line is N hexadecimal, "..", N hexadecimal, + * and optional space with octal mode. + */ + const char *ptr, *eol; + int len; + + ptr = strchr(line, '.'); + if (!ptr || ptr[1] != '.' || 40 <= ptr - line) + return 0; + len = ptr - line; + memcpy(patch->old_sha1_prefix, line, len); + patch->old_sha1_prefix[len] = 0; + + line = ptr + 2; + ptr = strchr(line, ' '); + eol = strchr(line, '\n'); + + if (!ptr || eol < ptr) + ptr = eol; + len = ptr - line; + + if (40 <= len) + return 0; + memcpy(patch->new_sha1_prefix, line, len); + patch->new_sha1_prefix[len] = 0; + if (*ptr == ' ') + patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8); + return 0; +} + /* * This is normal for a diff that doesn't change anything: we'll fall through * into the next diff. Tell the parser to break out. @@ -448,6 +474,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch { "rename to ", gitdiff_renamedst }, { "similarity index ", gitdiff_similarity }, { "dissimilarity index ", gitdiff_dissimilarity }, + { "index ", gitdiff_index }, { "", gitdiff_unrecognized }, }; int i; @@ -676,7 +703,10 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s /* We allow "\ No newline at end of file". Depending * on locale settings when the patch was produced we * don't know what this line looks like. The only - * thing we do know is that it begins with "\ ". */ + * thing we do know is that it begins with "\ ". + * Checking for 12 is just for sanity check -- any + * l10n of "\ No newline..." is at least that long. + */ case '\\': if (len < 12 || memcmp(line, "\\ ", 2)) return -1; @@ -1030,17 +1060,39 @@ static int check_patch(struct patch *patch) if (old_name) { int changed; + int stat_ret = lstat(old_name, &st); - if (lstat(old_name, &st) < 0) - return error("%s: %s", old_name, strerror(errno)); if (check_index) { int pos = cache_name_pos(old_name, strlen(old_name)); if (pos < 0) - return error("%s: does not exist in index", old_name); + return error("%s: does not exist in index", + old_name); + if (stat_ret < 0) { + struct checkout costate; + if (errno != ENOENT) + return error("%s: %s", old_name, + strerror(errno)); + /* checkout */ + costate.base_dir = ""; + costate.base_dir_len = 0; + costate.force = 0; + costate.quiet = 0; + costate.not_new = 0; + costate.refresh_cache = 1; + if (checkout_entry(active_cache[pos], + &costate) || + lstat(old_name, &st)) + return -1; + } + changed = ce_match_stat(active_cache[pos], &st); if (changed) - return error("%s: does not match index", old_name); + return error("%s: does not match index", + old_name); } + else if (stat_ret < 0) + return error("%s: %s", old_name, strerror(errno)); + if (patch->is_new < 0) patch->is_new = 0; st.st_mode = ntohl(create_ce_mode(st.st_mode)); @@ -1121,6 +1173,36 @@ static void show_file_list(struct patch *patch) } } +static inline int is_null_sha1(const unsigned char *sha1) +{ + return !memcmp(sha1, null_sha1, 20); +} + +static void show_index_list(struct patch *list) +{ + struct patch *patch; + + /* Once we start supporting the reverse patch, it may be + * worth showing the new sha1 prefix, but until then... + */ + for (patch = list; patch; patch = patch->next) { + const unsigned char *sha1_ptr; + unsigned char sha1[20]; + const char *name; + + name = patch->old_name ? patch->old_name : patch->new_name; + if (patch->is_new) + sha1_ptr = null_sha1; + else if (get_sha1(patch->old_sha1_prefix, sha1)) + die("sha1 information is lacking or useless (%s).", + name); + else + sha1_ptr = sha1; + printf("%06o %s %s\n",patch->old_mode, + sha1_to_hex(sha1_ptr), name); + } +} + static void stat_patch_list(struct patch *patch) { int files, adds, dels; @@ -1461,6 +1543,9 @@ static int apply_patch(int fd) if (show_files) show_file_list(list); + if (show_index_info) + show_index_list(list); + if (diffstat) stat_patch_list(list); @@ -1492,11 +1577,6 @@ int main(int argc, char **argv) excludes = x; continue; } - /* NEEDSWORK: this does not do anything at this moment. */ - if (!strcmp(arg, "--no-merge")) { - merge_patch = 0; - continue; - } if (!strcmp(arg, "--stat")) { apply = 0; diffstat = 1; @@ -1524,6 +1604,11 @@ int main(int argc, char **argv) show_files = 1; continue; } + if (!strcmp(arg, "--show-index-info")) { + apply = 0; + show_index_info = 1; + continue; + } fd = open(arg, O_RDONLY); if (fd < 0) usage(apply_usage); @@ -11,7 +11,9 @@ #include <string.h> #include <errno.h> #include <limits.h> +#ifndef NO_MMAP #include <sys/mman.h> +#endif #include <sys/param.h> #include <netinet/in.h> #include <sys/types.h> @@ -165,6 +167,7 @@ extern int ce_match_stat(struct cache_entry *ce, struct stat *st); extern int ce_modified(struct cache_entry *ce, struct stat *st); extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type); +extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); struct cache_file { @@ -355,4 +358,18 @@ extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long /* Dumb servers support */ extern int update_server_info(int); +#ifdef NO_MMAP + +#ifndef PROT_READ +#define PROT_READ 1 +#define PROT_WRITE 2 +#define MAP_PRIVATE 1 +#define MAP_FAILED ((void*)-1) +#endif + +extern void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset); +extern int gitfakemunmap(void *start, size_t length); + +#endif + #endif /* CACHE_H */ diff --git a/checkout-index.c b/checkout-index.c index f32513c507..97845324be 100644 --- a/checkout-index.c +++ b/checkout-index.c @@ -63,15 +63,20 @@ static int checkout_file(const char *name) static int checkout_all(void) { - int i; + int i, errs = 0; for (i = 0; i < active_nr ; i++) { struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) continue; if (checkout_entry(ce, &state) < 0) - return -1; + errs++; } + if (errs) + /* we have already done our error reporting. + * exit with the same code as die(). + */ + exit(128); return 0; } diff --git a/compat/mmap.c b/compat/mmap.c new file mode 100644 index 0000000000..a051c4767d --- /dev/null +++ b/compat/mmap.c @@ -0,0 +1,50 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include "../cache.h" + +void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset) +{ + int n = 0; + + if (start != NULL || !(flags & MAP_PRIVATE)) + die("Invalid usage of gitfakemmap."); + + if (lseek(fd, offset, SEEK_SET) < 0) { + errno = EINVAL; + return MAP_FAILED; + } + + start = xmalloc(length); + if (start == NULL) { + errno = ENOMEM; + return MAP_FAILED; + } + + while (n < length) { + int count = read(fd, start+n, length-n); + + if (count == 0) { + memset(start+n, 0, length-n); + break; + } + + if (count < 0) { + free(start); + errno = EACCES; + return MAP_FAILED; + } + + n += count; + } + + return start; +} + +int gitfakemunmap(void *start, size_t length) +{ + free(start); + return 0; +} + @@ -290,6 +290,8 @@ static enum protocol get_protocol(const char *name) #define STR_(s) # s #define STR(s) STR_(s) +#ifndef NO_IPV6 + static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path) { int sockfd = -1; @@ -346,6 +348,77 @@ static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path) return 0; } +#else /* NO_IPV6 */ + +static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path) +{ + int sockfd = -1; + char *colon, *end; + char *port = STR(DEFAULT_GIT_PORT), *ep; + struct hostent *he; + struct sockaddr_in sa; + char **ap; + unsigned int nport; + + if (host[0] == '[') { + end = strchr(host + 1, ']'); + if (end) { + *end = 0; + end++; + host++; + } else + end = host; + } else + end = host; + colon = strchr(end, ':'); + + if (colon) { + *colon = 0; + port = colon + 1; + } + + + he = gethostbyname(host); + if (!he) + die("Unable to look up %s (%s)", host, hstrerror(h_errno)); + nport = strtoul(port, &ep, 10); + if ( ep == port || *ep ) { + /* Not numeric */ + struct servent *se = getservbyname(port,"tcp"); + if ( !se ) + die("Unknown port %s\n", port); + nport = se->s_port; + } + + for (ap = he->h_addr_list; *ap; ap++) { + sockfd = socket(he->h_addrtype, SOCK_STREAM, 0); + if (sockfd < 0) + continue; + + memset(&sa, 0, sizeof sa); + sa.sin_family = he->h_addrtype; + sa.sin_port = htons(nport); + memcpy(&sa.sin_addr, ap, he->h_length); + + if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) { + close(sockfd); + sockfd = -1; + continue; + } + break; + } + + if (sockfd < 0) + die("unable to connect a socket (%s)", strerror(errno)); + + fd[0] = sockfd; + fd[1] = sockfd; + packet_write(sockfd, "%s %s\n", prog, path); + return 0; +} + +#endif /* NO_IPV6 */ + /* * Yeah, yeah, fixme. Need to pass in the heads etc. */ @@ -4,6 +4,7 @@ #include <sys/wait.h> #include <sys/socket.h> #include <sys/time.h> +#include <sys/poll.h> #include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> @@ -141,7 +142,7 @@ static int upload(char *dir, int dirlen) * is ok with us doing this. */ if ((!export_all_trees && access("git-daemon-export-ok", F_OK)) || - access("objects/00", X_OK) || + access("objects/", X_OK) || access("HEAD", R_OK)) { logerror("Not a valid git-daemon-enabled repository: '%s'", dir); return -1; @@ -328,6 +329,7 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen) inet_ntop(AF_INET, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf)); port = sin_addr->sin_port; +#ifndef NO_IPV6 } else if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6_addr = (void *) addr; @@ -337,6 +339,7 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen) strcat(buf, "]"); port = sin6_addr->sin6_port; +#endif } loginfo("Connection from %s:%d", addrbuf, port); @@ -369,16 +372,16 @@ static void child_handler(int signo) } } -static int serve(int port) +#ifndef NO_IPV6 + +static int socksetup(int port, int **socklist_p) { - struct addrinfo hints, *ai0, *ai; - int gai; int socknum = 0, *socklist = NULL; int maxfd = -1; - fd_set fds_init, fds; char pbuf[NI_MAXSERV]; - signal(SIGCHLD, child_handler); + struct addrinfo hints, *ai0, *ai; + int gai; sprintf(pbuf, "%d", port); memset(&hints, 0, sizeof(hints)); @@ -391,8 +394,6 @@ static int serve(int port) if (gai) die("getaddrinfo() failed: %s\n", gai_strerror(gai)); - FD_ZERO(&fds_init); - for (ai = ai0; ai; ai = ai->ai_next) { int sockfd; int *newlist; @@ -431,23 +432,63 @@ static int serve(int port) socklist = newlist; socklist[socknum++] = sockfd; - FD_SET(sockfd, &fds_init); if (maxfd < sockfd) maxfd = sockfd; } freeaddrinfo(ai0); - if (socknum == 0) - die("unable to allocate any listen sockets on port %u", port); + *socklist_p = socklist; + return socknum; +} + +#else /* NO_IPV6 */ + +static int socksetup(int port, int **socklist_p) +{ + struct sockaddr_in sin; + int sockfd; + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + return 0; + + memset(&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(port); + + if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) { + close(sockfd); + return 0; + } + *socklist_p = xmalloc(sizeof(int)); + **socklist_p = sockfd; +} + +#endif + +static int service_loop(int socknum, int *socklist) +{ + struct pollfd *pfd; + int i; + + pfd = xcalloc(socknum, sizeof(struct pollfd)); + + for (i = 0; i < socknum; i++) { + pfd[i].fd = socklist[i]; + pfd[i].events = POLLIN; + } + + signal(SIGCHLD, child_handler); + for (;;) { int i; - fds = fds_init; - - if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 0) { + + if (poll(pfd, socknum, 0) < 0) { if (errno != EINTR) { - error("select failed, resuming: %s", + error("poll failed, resuming: %s", strerror(errno)); sleep(1); } @@ -455,12 +496,10 @@ static int serve(int port) } for (i = 0; i < socknum; i++) { - int sockfd = socklist[i]; - - if (FD_ISSET(sockfd, &fds)) { + if (pfd[i].revents & POLLIN) { struct sockaddr_storage ss; - int sslen = sizeof(ss); - int incoming = accept(sockfd, (struct sockaddr *)&ss, &sslen); + unsigned int sslen = sizeof(ss); + int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen); if (incoming < 0) { switch (errno) { case EAGAIN: @@ -477,6 +516,17 @@ static int serve(int port) } } +static int serve(int port) +{ + int socknum, *socklist; + + socknum = socksetup(port, &socklist); + if (socknum == 0) + die("unable to allocate any listen sockets on port %u", port); + + return service_loop(socknum, socklist); +} + int main(int argc, char **argv) { int port = DEFAULT_GIT_PORT; @@ -526,7 +576,7 @@ int main(int argc, char **argv) if (inetd_mode) { fclose(stderr); //FIXME: workaround return execute(); + } else { + return serve(port); } - - return serve(port); } @@ -596,15 +596,31 @@ static void run_external_diff(const char *pgm, remove_tempfile(); } +static void diff_fill_sha1_info(struct diff_filespec *one) +{ + if (DIFF_FILE_VALID(one)) { + if (!one->sha1_valid) { + struct stat st; + if (stat(one->path, &st) < 0) + die("stat %s", one->path); + if (index_path(one->sha1, one->path, &st, 0)) + die("cannot hash %s\n", one->path); + } + } + else + memset(one->sha1, 0, 20); +} + static void run_diff(struct diff_filepair *p) { const char *pgm = external_diff(); - char msg_[PATH_MAX*2+200], *xfrm_msg; + char msg[PATH_MAX*2+300], *xfrm_msg; struct diff_filespec *one; struct diff_filespec *two; const char *name; const char *other; int complete_rewrite = 0; + int len; if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ @@ -616,39 +632,60 @@ static void run_diff(struct diff_filepair *p) name = p->one->path; other = (strcmp(name, p->two->path) ? p->two->path : NULL); one = p->one; two = p->two; + + diff_fill_sha1_info(one); + diff_fill_sha1_info(two); + + len = 0; switch (p->status) { case DIFF_STATUS_COPIED: - sprintf(msg_, - "similarity index %d%%\n" - "copy from %s\n" - "copy to %s", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name, other); - xfrm_msg = msg_; + len += snprintf(msg + len, sizeof(msg) - len, + "similarity index %d%%\n" + "copy from %s\n" + "copy to %s\n", + (int)(0.5 + p->score * 100.0/MAX_SCORE), + name, other); break; case DIFF_STATUS_RENAMED: - sprintf(msg_, - "similarity index %d%%\n" - "rename from %s\n" - "rename to %s", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name, other); - xfrm_msg = msg_; + len += snprintf(msg + len, sizeof(msg) - len, + "similarity index %d%%\n" + "rename from %s\n" + "rename to %s\n", + (int)(0.5 + p->score * 100.0/MAX_SCORE), + name, other); break; case DIFF_STATUS_MODIFIED: if (p->score) { - sprintf(msg_, - "dissimilarity index %d%%", - (int)(0.5 + p->score * 100.0/MAX_SCORE)); - xfrm_msg = msg_; + len += snprintf(msg + len, sizeof(msg) - len, + "dissimilarity index %d%%\n", + (int)(0.5 + p->score * + 100.0/MAX_SCORE)); complete_rewrite = 1; break; } /* fallthru */ default: - xfrm_msg = NULL; + /* nothing */ + ; } + if (memcmp(one->sha1, two->sha1, 20)) { + char one_sha1[41]; + memcpy(one_sha1, sha1_to_hex(one->sha1), 41); + + len += snprintf(msg + len, sizeof(msg) - len, + "index %.7s..%.7s", one_sha1, + sha1_to_hex(two->sha1)); + if (one->mode == two->mode) + len += snprintf(msg + len, sizeof(msg) - len, + " %06o", one->mode); + len += snprintf(msg + len, sizeof(msg) - len, "\n"); + } + + if (len) + msg[--len] = 0; + xfrm_msg = len ? msg : NULL; + if (!pgm && DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && (S_IFMT & one->mode) != (S_IFMT & two->mode)) { @@ -132,7 +132,7 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state) if (!state->force) { if (!state->quiet) fprintf(stderr, "git-checkout-index: %s already exists\n", path); - return 0; + return -1; } /* diff --git a/fsck-objects.c b/fsck-objects.c index 65cec7d12b..17d05363e0 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -329,9 +329,8 @@ static int fsck_dir(int i, char *path) DIR *dir = opendir(path); struct dirent *de; - if (!dir) { - return error("missing sha1 directory '%s'", path); - } + if (!dir) + return 0; while ((de = readdir(dir)) != NULL) { char name[100]; diff --git a/git-am.sh b/git-am.sh new file mode 100755 index 0000000000..9e41e70313 --- /dev/null +++ b/git-am.sh @@ -0,0 +1,337 @@ +#!/bin/sh +# +# +. git-sh-setup || die "Not a git archive" + +files=$(git-diff-index --cached --name-only HEAD) || exit +if [ "$files" ]; then + echo "Dirty index: cannot apply patches (dirty: $files)" >&2 + exit 1 +fi + +usage () { + echo >&2 "usage: $0 [--signoff] [--dotest=<dir>] [--utf8] [--3way] <mbox>" + echo >&2 " or, when resuming" + echo >&2 " $0 [--skip]" + exit 1; +} + +stop_here () { + echo "$1" >"$dotest/next" + exit 1 +} + +go_next () { + rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \ + "$dotest/patch" "$dotest/info" + echo "$next" >"$dotest/next" + this=$next +} + +fall_back_3way () { + O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd` + + rm -fr "$dotest"/patch-merge-* + mkdir "$dotest/patch-merge-tmp-dir" + + # First see if the patch records the index info that we can use. + if git-apply --show-index-info "$dotest/patch" \ + >"$dotest/patch-merge-index-info" 2>/dev/null && + GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-update-index --index-info <"$dotest/patch-merge-index-info" && + GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-write-tree >"$dotest/patch-merge-base+" && + # index has the base tree now. + ( + cd "$dotest/patch-merge-tmp-dir" && + GIT_INDEX_FILE="../patch-merge-tmp-index" \ + GIT_OBJECT_DIRECTORY="$O_OBJECT" \ + git-apply --index <../patch + ) + then + echo Using index info to reconstruct a base tree... + mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base" + mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index" + else + # Otherwise, try nearby trees that can be used to apply the + # patch. + ( + N=10 + + # Hoping the patch is against our recent commits... + git-rev-list --max-count=$N HEAD + + # or hoping the patch is against known tags... + git-ls-remote --tags . + ) | + while read base junk + do + # See if we have it as a tree... + git-cat-file tree "$base" >/dev/null 2>&1 || continue + + rm -fr "$dotest"/patch-merge-* && + mkdir "$dotest/patch-merge-tmp-dir" || break + ( + cd "$dotest/patch-merge-tmp-dir" && + GIT_INDEX_FILE=../patch-merge-tmp-index && + GIT_OBJECT_DIRECTORY="$O_OBJECT" && + export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY && + git-read-tree "$base" && + git-apply --index && + mv ../patch-merge-tmp-index ../patch-merge-index && + echo "$base" >../patch-merge-base + ) <"$dotest/patch" 2>/dev/null && break + done + fi + + test -f "$dotest/patch-merge-index" && + his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) && + orig_tree=$(cat "$dotest/patch-merge-base") && + rm -fr "$dotest"/patch-merge-* || exit 1 + + echo Falling back to patching base and 3-way merge... + + # This is not so wrong. Depending on which base we picked, + # orig_tree may be wildly different from ours, but his_tree + # has the same set of wildly different changes in parts the + # patch did not touch, so resolve ends up cancelling them, + # saying that we reverted all those changes. + + git-merge-resolve $orig_tree -- HEAD $his_tree || { + echo Failed to merge in the changes. + exit 1 + } +} + +prec=4 +dotest=.dotest sign= utf8= keep= skip= interactive= + +while case "$#" in 0) break;; esac +do + case "$1" in + -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*) + dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;; + -d|--d|--do|--dot|--dote|--dotes|--dotest) + case "$#" in 1) usage ;; esac; shift + dotest="$1"; shift;; + + -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\ + --interacti|--interactiv|--interactive) + interactive=t; shift ;; + + -3|--3|--3w|--3wa|--3way) + threeway=t; shift ;; + -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) + sign=t; shift ;; + -u|--u|--ut|--utf|--utf8) + utf8=t; shift ;; + -k|--k|--ke|--kee|--keep) + keep=t; shift ;; + + --sk|--ski|--skip) + skip=t; shift ;; + + --) + shift; break ;; + -*) + usage ;; + *) + break ;; + esac +done + +if test -d "$dotest" && + last=$(cat "$dotest/last") && + next=$(cat "$dotest/next") && + test $# != 0 && + test "$next" -gt "$last" +then + rm -fr "$dotest" +fi + +if test -d "$dotest" +then + test ",$#," = ",0," || + die "previous dotest directory $dotest still exists but mbox given." +else + # Make sure we are not given --skip + test ",$skip," = ,, || + die "we are not resuming." + + # Start afresh. + mkdir -p "$dotest" || exit + + # cat does the right thing for us, including '-' to mean + # standard input. + cat "$@" | + git-mailsplit -d$prec "$dotest/" >"$dotest/last" || { + rm -fr "$dotest" + exit 1 + } + + echo "$sign" >"$dotest/sign" + echo "$utf8" >"$dotest/utf8" + echo "$keep" >"$dotest/keep" + echo "$threeway" >"$dotest/3way" + echo 1 >"$dotest/next" +fi + +if test "$(cat "$dotest/utf8")" = t +then + utf8=-u +fi +if test "$(cat "$dotest/keep")" = t +then + keep=-k +fi +if test "$(cat "$dotest/sign")" = t +then + SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e ' + s/>.*/>/ + s/^/Signed-off-by: /' + ` +else + SIGNOFF= +fi +threeway=$(cat "$dotest/3way") + +last=`cat "$dotest/last"` +this=`cat "$dotest/next"` +if test "$skip" = t +then + this=`expr "$this" + 1` +fi + +if test "$this" -gt "$last" +then + echo Nothing to do. + rm -fr "$dotest" + exit +fi + +while test "$this" -le "$last" +do + msgnum=`printf "%0${prec}d" $this` + next=`expr "$this" + 1` + test -f "$dotest/$msgnum" || { + go_next + continue + } + git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \ + <"$dotest/$msgnum" >"$dotest/info" || + stop_here $this + git-stripspace < "$dotest/msg" > "$dotest/msg-clean" + + GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")" + GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")" + GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")" + SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")" + + case "$keep_subject" in -k) SUBJECT="[PATCH] $SUBJECT" ;; esac + if test '' != "$SIGNOFF" + then + LAST_SIGNED_OFF_BY=` + sed -ne '/^Signed-off-by: /p' "$dotest/msg-clean" | + tail -n 1 + ` + ADD_SIGNOFF=$(test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || { + test '' = "$LAST_SIGNED_OFF_BY" && echo + echo "$SIGNOFF" + }) + else + ADD_SIGNOFF= + fi + { + echo "$SUBJECT" + if test -s "$dotest/msg-clean" + then + echo + cat "$dotest/msg-clean" + fi + if test '' != "$ADD_SIGNOFF" + then + echo "$ADD_SIGNOFF" + fi + } >"$dotest/final-commit" + + if test "$interactive" = t + then + action=again + while test "$action" = again + do + echo "Commit Body is:" + echo "--------------------------" + cat "$dotest/final-commit" + echo "--------------------------" + echo -n "Apply? [y]es/[n]o/[e]dit/[a]ccept all " + read reply + case "$reply" in + y*|Y*) action=yes ;; + a*|A*) action=yes interactive= ;; + n*|N*) action=skip ;; + e*|E*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit" + action=again ;; + esac + done + else + action=yes + fi + + if test $action = skip + then + go_next + continue + fi + + if test -x "$GIT_DIR"/hooks/applypatch-msg + then + "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" || + stop_here $this + fi + + echo + echo "Applying '$SUBJECT'" + echo + + git-apply --index "$dotest/patch"; apply_status=$? + if test $apply_status = 1 && test "$threeway" = t + then + (fall_back_3way) || stop_here $this + + # Applying the patch to an earlier tree and merging the + # result may have produced the same tree as ours. + if test '' = "$(git-diff-index --cached --name-only -z HEAD)" + then + echo No changes -- Patch already applied. + go_next + continue + fi + fi + if test $apply_status != 0 + then + echo Patch failed at $msgnum. + stop_here $this + fi + + if test -x "$GIT_DIR"/hooks/pre-applypatch + then + "$GIT_DIR"/hooks/pre-applypatch || stop_here $this + fi + + tree=$(git-write-tree) && + echo Wrote tree $tree && + parent=$(git-rev-parse --verify HEAD) && + commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") && + echo Committed: $commit && + git-update-ref HEAD $commit $parent || + stop_here $this + + if test -x "$GIT_DIR"/hooks/post-applypatch + then + "$GIT_DIR"/hooks/post-applypatch + fi + + go_next +done + +rm -fr "$dotest" diff --git a/git-applymbox.sh b/git-applymbox.sh index e2bfd02870..4e77132ab5 100755 --- a/git-applymbox.sh +++ b/git-applymbox.sh @@ -9,8 +9,6 @@ ## You give it a mbox-format collection of emails, and it will try to ## apply them to the kernel using "applypatch" ## -## applymbox [-u] [-k] [-q] (-c .dotest/msg-number | mail_archive) [Signoff_file]" -## ## The patch application may fail in the middle. In which case: ## (1) look at .dotest/patch and fix it up to apply ## (2) re-run applymbox with -c .dotest/msg-number for the current one. @@ -21,7 +19,7 @@ . git-sh-setup || die "Not a git archive" usage () { - echo >&2 "applymbox [-u] [-k] [-q] (-c .dotest/<num> | mbox) [signoff]" + echo >&2 "applymbox [-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]" exit 1 } @@ -33,6 +31,7 @@ do -k) keep_subject=-k ;; -q) query_apply=t ;; -c) continue="$2"; resume=f; shift ;; + -m) fallback_3way=t ;; -*) usage ;; *) break ;; esac @@ -43,7 +42,8 @@ case "$continue" in '') rm -rf .dotest mkdir .dotest - git-mailsplit "$1" .dotest || exit 1 + num_msgs=$(git-mailsplit "$1" .dotest) || exit 1 + echo "$num_msgs patch(es) to process." shift esac @@ -56,6 +56,9 @@ fi case "$query_apply" in t) touch .dotest/.query_apply esac +case "$fall_back_3way" in +t) : >.dotest/.3way +esac case "$keep_subject" in -k) : >.dotest/.keep_subject esac @@ -80,7 +83,11 @@ do do git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff" case "$?" in - 0 | 2 ) + 0) + # Remove the cleanly applied one to reduce clutter. + rm -f .dotest/$i + ;; + 2) # 2 is a special exit code from applypatch to indicate that # the patch wasn't applied, but continue anyway ;; diff --git a/git-applypatch.sh b/git-applypatch.sh index 9f5a45bb2b..66fd19ae2d 100755 --- a/git-applypatch.sh +++ b/git-applypatch.sh @@ -22,6 +22,8 @@ query_apply=.dotest/.query_apply ## if this file exists. keep_subject=.dotest/.keep_subject +## We do not attempt the 3-way merge fallback unless this file exists. +fall_back_3way=.dotest/.3way MSGFILE=$1 PATCHFILE=$2 @@ -29,10 +31,10 @@ INFO=$3 SIGNOFF=$4 EDIT=${VISUAL:-${EDITOR:-vi}} -export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)" -export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" -export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' .dotest/info)" -export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)" +export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")" +export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")" +export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")" +export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")" if test '' != "$SIGNOFF" then @@ -54,8 +56,10 @@ then sed -ne '/^Signed-off-by: /p' "$MSGFILE" | tail -n 1 ` - test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || - echo "$SIGNOFF" >>"$MSGFILE" + test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || { + test '' = "$LAST_SIGNED_OFF_BY" && echo + echo "$SIGNOFF" + } >>"$MSGFILE" fi fi @@ -99,7 +103,81 @@ echo echo Applying "'$SUBJECT'" echo -git-apply --index "$PATCHFILE" || exit 1 +git-apply --index "$PATCHFILE" || { + + # git-apply exits with status 1 when the patch does not apply, + # but it die()s with other failures, most notably upon corrupt + # patch. In the latter case, there is no point to try applying + # it to another tree and do 3-way merge. + test $? = 1 || exit 1 + + test -f "$fall_back_3way" || exit 1 + + # Here if we know which revision the patch applies to, + # we create a temporary working tree and index, apply the + # patch, and attempt 3-way merge with the resulting tree. + + O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd` + rm -fr .patch-merge-* + + ( + N=10 + + # if the patch records the base tree... + sed -ne ' + /^diff /q + /^applies-to: \([0-9a-f]*\)$/{ + s//\1/p + q + } + ' "$PATCHFILE" + + # or hoping the patch is against our recent commits... + git-rev-list --max-count=$N HEAD + + # or hoping the patch is against known tags... + git-ls-remote --tags . + ) | + while read base junk + do + # Try it if we have it as a tree. + git-cat-file tree "$base" >/dev/null 2>&1 || continue + + rm -fr .patch-merge-tmp-* && + mkdir .patch-merge-tmp-dir || break + ( + cd .patch-merge-tmp-dir && + GIT_INDEX_FILE=../.patch-merge-tmp-index && + GIT_OBJECT_DIRECTORY="$O_OBJECT" && + export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY && + git-read-tree "$base" && + git-apply --index && + mv ../.patch-merge-tmp-index ../.patch-merge-index && + echo "$base" >../.patch-merge-base + ) <"$PATCHFILE" 2>/dev/null && break + done + + test -f .patch-merge-index && + his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) && + orig_tree=$(cat .patch-merge-base) && + rm -fr .patch-merge-* || exit 1 + + echo Falling back to patching base and 3-way merge using $orig_tree... + + # This is not so wrong. Depending on which base we picked, + # orig_tree may be wildly different from ours, but his_tree + # has the same set of wildly different changes in parts the + # patch did not touch, so resolve ends up cancelling them, + # saying that we reverted all those changes. + + if git-merge-resolve $orig_tree -- HEAD $his_tree + then + echo Done. + else + echo Failed to merge in the changes. + exit 1 + fi +} if test -x "$GIT_DIR"/hooks/pre-applypatch then diff --git a/git-commit.sh b/git-commit.sh index 591fcdceb1..5bdee54a46 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -129,7 +129,7 @@ then elif test "$use_commit" != "" then git-cat-file commit "$use_commit" | sed -e '1,/^$/d' -fi | git-stripspace >.editmsg +fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG case "$signoff" in t) @@ -139,7 +139,7 @@ t) s/>.*/>/ s/^/Signed-off-by: / ' - } >>.editmsg + } >>"$GIT_DIR"/COMMIT_EDITMSG ;; esac @@ -153,7 +153,7 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then echo "# $GIT_DIR/MERGE_HEAD" echo "# and try again" echo "#" -fi >>.editmsg +fi >>"$GIT_DIR"/COMMIT_EDITMSG PARENTS="-p HEAD" if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1 @@ -197,16 +197,16 @@ else fi PARENTS="" fi -git-status >>.editmsg +git-status >>"$GIT_DIR"/COMMIT_EDITMSG if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ] then - rm -f .editmsg + rm -f "$GIT_DIR/COMMIT_EDITMSG" git-status exit 1 fi case "$no_edit" in '') - ${VISUAL:-${EDITOR:-vi}} .editmsg + ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG" ;; esac @@ -214,16 +214,20 @@ case "$verify" in t) if test -x "$GIT_DIR"/hooks/commit-msg then - "$GIT_DIR"/hooks/commit-msg .editmsg || exit + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit fi esac -grep -v '^#' < .editmsg | git-stripspace > .cmitmsg -grep -v -i '^Signed-off-by' .cmitmsg >.cmitchk -if test -s .cmitchk +grep -v '^#' < "$GIT_DIR"/COMMIT_EDITMSG | +git-stripspace > "$GIT_DIR"/COMMIT_MSG + +if cnt=`grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG | + git-stripspace | + wc -l` && + test 0 -lt $cnt then tree=$(git-write-tree) && - commit=$(cat .cmitmsg | git-commit-tree $tree $PARENTS) && + commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) && git-update-ref HEAD $commit $current && rm -f -- "$GIT_DIR/MERGE_HEAD" else @@ -231,7 +235,7 @@ else false fi ret="$?" -rm -f .cmitmsg .editmsg .cmitchk +rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0 then diff --git a/git-format-patch.sh b/git-format-patch.sh index 2844799535..09b635e492 100755 --- a/git-format-patch.sh +++ b/git-format-patch.sh @@ -6,7 +6,7 @@ . git-sh-setup || die "Not a git archive." usage () { - echo >&2 "usage: $0"' [-n] [-o dir] [--keep-subject] [--mbox] [--check] [--signoff] [-<diff options>...] upstream [ our-head ] + echo >&2 "usage: $0"' [-n] [-o dir | --stdout] [--keep-subject] [--mbox] [--check] [--signoff] [-<diff options>...] upstream [ our-head ] Prepare each commit with its patch since our-head forked from upstream, one file per patch, for e-mail submission. Each output file is @@ -49,6 +49,8 @@ do numbered=t ;; -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) signoff=t ;; + --st|--std|--stdo|--stdou|--stdout) + stdout=t mbox=t date=t author=t ;; -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\ --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\ --output-direc=*|--output-direct=*|--output-directo=*|\ @@ -141,25 +143,7 @@ do esac done >$series -total=`wc -l <$series | tr -dc "[0-9]"` -i=1 -while read commit -do - git-cat-file commit "$commit" | git-stripspace >$commsg - title=`sed -ne "$titleScript" <$commsg` - case "$numbered" in - '') num= ;; - *) - case $total in - 1) num= ;; - *) num=' '`printf "%d/%d" $i $total` ;; - esac - esac - - file=`printf '%04d-%stxt' $i "$title"` - i=`expr "$i" + 1` - echo "* $file" - { +process_one () { mailScript=' /./d /^$/n' @@ -178,6 +162,7 @@ do echo 'From nobody Mon Sep 17 00:00:00 2001' ;# UNIX "From" line ;; esac + eval "$(sed -ne "$whosepatchScript" $commsg)" test "$author,$au" = ",$me" || { mailScript="$mailScript"' @@ -196,7 +181,9 @@ Date: '"$ad" n b body' - sed -ne "$mailScript" <$commsg + (cat $commsg ; echo; echo) | + sed -ne "$mailScript" | + git-stripspace test "$signoff" = "t" && { offsigner=`git-var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/'` @@ -212,21 +199,49 @@ Date: '"$ad" echo git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary echo + git-cat-file commit "$commit^" | sed -e 's/^tree /applies-to: /' -e q git-diff-tree -p $diff_opts "$commit" + echo "---" + echo "@@GIT_VERSION@@" case "$mbox" in t) echo ;; esac - } >"$outdir$file" - case "$check" in - t) - # This is slightly modified from Andrew Morton's Perfect Patch. - # Lines you introduce should not have trailing whitespace. - # Also check for an indentation that has SP before a TAB. - grep -n '^+\([ ]* .*\|.*[ ]\)$' "$outdir$file" - - : do not exit with non-zero because we saw no problem in the last one. +} + +total=`wc -l <$series | tr -dc "[0-9]"` +i=1 +while read commit +do + git-cat-file commit "$commit" | git-stripspace >$commsg + title=`sed -ne "$titleScript" <$commsg` + case "$numbered" in + '') num= ;; + *) + case $total in + 1) num= ;; + *) num=' '`printf "%d/%d" $i $total` ;; + esac esac + + file=`printf '%04d-%stxt' $i "$title"` + if test '' = "$stdout" + then + echo "* $file" + process_one >"$outdir$file" + if test t = "$check" + then + # This is slightly modified from Andrew Morton's Perfect Patch. + # Lines you introduce should not have trailing whitespace. + # Also check for an indentation that has SP before a TAB. + grep -n '^+\([ ]* .*\|.*[ ]\)$' "$outdir$file" + : + fi + else + echo >&2 "* $file" + process_one + fi + i=`expr "$i" + 1` done <$series diff --git a/git-ls-remote.sh b/git-ls-remote.sh index bfbd5a4d5a..f0f0b07f6f 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -1,6 +1,6 @@ #!/bin/sh # -. git-sh-setup || die "Not a git archive" +. git-sh-setup usage () { echo >&2 "usage: $0 [--heads] [--tags] <repository> <refs>..." diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 4d8a572a99..5e75e15a7e 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -1,6 +1,6 @@ #!/bin/sh -. git-sh-setup || die "Not a git archive" +. git-sh-setup get_data_source () { case "$1" in diff --git a/git-rename.perl b/git-rename.perl index a28c8c83bb..3b1127b1b2 100755 --- a/git-rename.perl +++ b/git-rename.perl @@ -15,7 +15,7 @@ sub usage($); my $GIT_DIR = $ENV{'GIT_DIR'} || ".git"; unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && - -d $GIT_DIR . "/objects/00" && -d $GIT_DIR . "/refs") { + -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") { usage("Git repository not found."); } diff --git a/git-sh-setup.sh b/git-sh-setup.sh index a0172686a9..dbb98842bf 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -22,4 +22,4 @@ refs/*) : ;; *) false ;; esac && [ -d "$GIT_DIR/refs" ] && -[ -d "$GIT_OBJECT_DIRECTORY/00" ] +[ -d "$GIT_OBJECT_DIRECTORY/" ] diff --git a/git-shortlog.perl b/git-shortlog.perl index 8f0984be02..0b14f833ee 100755 --- a/git-shortlog.perl +++ b/git-shortlog.perl @@ -2,55 +2,13 @@ use strict; -# -# Even with git, we don't always have name translations. -# So have an email->real name table to translate the -# (hopefully few) missing names -# -my %mailmap = ( - 'R.Marek@sh.cvut.cz' => 'Rudolf Marek', - 'Ralf.Wildenhues@gmx.de' => 'Ralf Wildenhues', - 'aherrman@de.ibm.com' => 'Andreas Herrmann', - 'akpm@osdl.org' => 'Andrew Morton', - 'andrew.vasquez@qlogic.com' => 'Andrew Vasquez', - 'aquynh@gmail.com' => 'Nguyen Anh Quynh', - 'axboe@suse.de' => 'Jens Axboe', - 'blaisorblade@yahoo.it' => 'Paolo \'Blaisorblade\' Giarrusso', - 'bunk@stusta.de' => 'Adrian Bunk', - 'domen@coderock.org' => 'Domen Puncer', - 'dougg@torque.net' => 'Douglas Gilbert', - 'dwmw2@shinybook.infradead.org' => 'David Woodhouse', - 'ecashin@coraid.com' => 'Ed L Cashin', - 'felix@derklecks.de' => 'Felix Moeller', - 'fzago@systemfabricworks.com' => 'Frank Zago', - 'gregkh@suse.de' => 'Greg Kroah-Hartman', - 'hch@lst.de' => 'Christoph Hellwig', - 'htejun@gmail.com' => 'Tejun Heo', - 'jejb@mulgrave.(none)' => 'James Bottomley', - 'jejb@titanic.il.steeleye.com' => 'James Bottomley', - 'jgarzik@pretzel.yyz.us' => 'Jeff Garzik', - 'johnpol@2ka.mipt.ru' => 'Evgeniy Polyakov', - 'kay.sievers@vrfy.org' => 'Kay Sievers', - 'minyard@acm.org' => 'Corey Minyard', - 'mshah@teja.com' => 'Mitesh shah', - 'pj@ludd.ltu.se' => 'Peter A Jonsson', - 'rmps@joel.ist.utl.pt' => 'Rui Saraiva', - 'santtu.hyrkko@gmail.com' => 'Santtu Hyrkkö', - 'simon@thekelleys.org.uk' => 'Simon Kelley', - 'ssant@in.ibm.com' => 'Sachin P Sant', - 'terra@gnome.org' => 'Morten Welinder', - 'tony.luck@intel.com' => 'Tony Luck', - 'welinder@anemone.rentec.com' => 'Morten Welinder', - 'welinder@darter.rentec.com' => 'Morten Welinder', - 'welinder@troll.com' => 'Morten Welinder', -); - +my (%mailmap); +my (%email); my (%map); my $pstate = 1; my $n_records = 0; my $n_output = 0; - sub shortlog_entry($$) { my ($name, $desc) = @_; my $key = $name; @@ -108,41 +66,35 @@ sub changelog_input { if ($pstate == 1) { my ($email); - next unless /^[Aa]uthor:? (.*)<(.*)>.*$/; - + next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/; + $n_records++; - + $author = $1; $email = $2; $desc = undef; - # trim trailing whitespace. - # why doesn't chomp work? - while ($author && ($author =~ /\s$/)) { - chop $author; - } - # cset author fixups if (exists $mailmap{$email}) { $author = $mailmap{$email}; } elsif (exists $mailmap{$author}) { $author = $mailmap{$author}; - } elsif ((!$author) || ($author eq "")) { + } elsif (!$author) { $author = $email; } - + $email{$author}{$email}++; $pstate++; } - + # skip to blank line elsif ($pstate == 2) { next unless /^\s*$/; $pstate++; } - + # skip to non-blank line elsif ($pstate == 3) { - next unless /^\s*(\S.*)$/; + next unless /^\s*?(.*)/; # skip lines that are obviously not # a 1-line cset description @@ -150,9 +102,9 @@ sub changelog_input { chomp; $desc = $1; - + &shortlog_entry($author, $desc); - + $pstate = 1; } @@ -162,16 +114,87 @@ sub changelog_input { } } +sub read_mailmap { + my ($fh, $mailmap) = @_; + while (<$fh>) { + chomp; + if (/^([^#].*?)\s*<(.*)>/) { + $mailmap->{$2} = $1; + } + } +} + +sub setup_mailmap { + read_mailmap(\*DATA, \%mailmap); + if (-f '.mailmap') { + my $fh = undef; + open $fh, '<', '.mailmap'; + read_mailmap($fh, \%mailmap); + close $fh; + } +} + sub finalize { #print "\n$n_records records parsed.\n"; if ($n_records != $n_output) { die "parse error: input records != output records\n"; } + if (0) { + for my $author (sort keys %email) { + my $e = $email{$author}; + for my $email (sort keys %$e) { + print STDERR "$author <$email>\n"; + } + } + } } +&setup_mailmap; &changelog_input; &shortlog_output; &finalize; exit(0); + +__DATA__ +# +# Even with git, we don't always have name translations. +# So have an email->real name table to translate the +# (hopefully few) missing names +# +Adrian Bunk <bunk@stusta.de> +Andreas Herrmann <aherrman@de.ibm.com> +Andrew Morton <akpm@osdl.org> +Andrew Vasquez <andrew.vasquez@qlogic.com> +Christoph Hellwig <hch@lst.de> +Corey Minyard <minyard@acm.org> +David Woodhouse <dwmw2@shinybook.infradead.org> +Domen Puncer <domen@coderock.org> +Douglas Gilbert <dougg@torque.net> +Ed L Cashin <ecashin@coraid.com> +Evgeniy Polyakov <johnpol@2ka.mipt.ru> +Felix Moeller <felix@derklecks.de> +Frank Zago <fzago@systemfabricworks.com> +Greg Kroah-Hartman <gregkh@suse.de> +James Bottomley <jejb@mulgrave.(none)> +James Bottomley <jejb@titanic.il.steeleye.com> +Jeff Garzik <jgarzik@pretzel.yyz.us> +Jens Axboe <axboe@suse.de> +Kay Sievers <kay.sievers@vrfy.org> +Mitesh shah <mshah@teja.com> +Morten Welinder <terra@gnome.org> +Morten Welinder <welinder@anemone.rentec.com> +Morten Welinder <welinder@darter.rentec.com> +Morten Welinder <welinder@troll.com> +Nguyen Anh Quynh <aquynh@gmail.com> +Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it> +Peter A Jonsson <pj@ludd.ltu.se> +Ralf Wildenhues <Ralf.Wildenhues@gmx.de> +Rudolf Marek <R.Marek@sh.cvut.cz> +Rui Saraiva <rmps@joel.ist.utl.pt> +Sachin P Sant <ssant@in.ibm.com> +Santtu Hyrkk,Av(B <santtu.hyrkko@gmail.com> +Simon Kelley <simon@thekelleys.org.uk> +Tejun Heo <htejun@gmail.com> +Tony Luck <tony.luck@intel.com> diff --git a/git-tag.sh b/git-tag.sh index 400bdb9843..25c1a0e88e 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -4,7 +4,7 @@ . git-sh-setup || die "Not a git archive" usage () { - echo >&2 "Usage: git-tag [-a | -s] [-f] [-m "tag message"] tagname [head]" + echo >&2 "Usage: git-tag [-a | -s | -u <key-id>] [-f] [-m <msg>] <tagname> [<head>]" exit 1 } @@ -12,6 +12,7 @@ annotate= signed= force= message= +username= while case "$#" in 0) break ;; esac do case "$1" in @@ -30,6 +31,12 @@ do shift message="$1" ;; + -u) + annotate=1 + signed=1 + shift + username="$1" + ;; -*) usage ;; @@ -50,6 +57,7 @@ shift object=$(git-rev-parse --verify --default HEAD "$@") || exit 1 type=$(git-cat-file -t $object) || exit 1 tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1 +: ${username:=$(expr "$tagger" : '\(.*>\)')} trap 'rm -f .tmp-tag* .tagmsg .editmsg' 0 @@ -65,13 +73,15 @@ if [ "$annotate" ]; then grep -v '^#' < .editmsg | git-stripspace > .tagmsg - [ -s .tagmsg ] || exit + [ -s .tagmsg ] || { + echo >&2 "No tag message?" + exit 1 + } ( echo -e "object $object\ntype $type\ntag $name\ntagger $tagger\n"; cat .tagmsg ) > .tmp-tag rm -f .tmp-tag.asc .tagmsg if [ "$signed" ]; then - me=$(expr "$tagger" : '\(.*>\)') && - gpg -bsa -u "$me" .tmp-tag && + gpg -bsa -u "$username" .tmp-tag && cat .tmp-tag.asc >>.tmp-tag || die "failed to sign the tag with GPG." fi @@ -11,7 +11,17 @@ case "$#" in echo "git version @@GIT_VERSION@@" exit 0 ;; esac - test -x $path/git-$cmd && exec $path/git-$cmd "$@" ;; + + test -x $path/git-$cmd && exec $path/git-$cmd "$@" + + case '@@X@@' in + '') + ;; + *) + test -x $path/git-$cmd@@X@@ && exec $path/git-$cmd@@X@@ "$@" + ;; + esac + ;; esac echo "Usage: git COMMAND [OPTIONS] [TARGET]" diff --git a/mailsplit.c b/mailsplit.c index 7afea1aaca..0f8100dcca 100644 --- a/mailsplit.c +++ b/mailsplit.c @@ -9,30 +9,14 @@ #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> -#include <sys/mman.h> #include <string.h> #include <stdio.h> #include <ctype.h> #include <assert.h> +#include "cache.h" -static int usage(void) -{ - fprintf(stderr, "mailsplit <mbox> <directory>\n"); - exit(1); -} - -static int linelen(const char *map, unsigned long size) -{ - int len = 0, c; - - do { - c = *map; - map++; - size--; - len++; - } while (size && c != '\n'); - return len; -} +static const char git_mailsplit_usage[] = +"git-mailsplit [-d<prec>] [<mbox>] <directory>"; static int is_from_line(const char *line, int len) { @@ -65,81 +49,110 @@ static int is_from_line(const char *line, int len) return 1; } -static int parse_email(const void *map, unsigned long size) +/* Could be as small as 64, enough to hold a Unix "From " line. */ +static char buf[4096]; + +/* Called with the first line (potentially partial) + * already in buf[] -- normally that should begin with + * the Unix "From " line. Write it into the specified + * file. + */ +static int split_one(FILE *mbox, const char *name) { - unsigned long offset; + FILE *output = NULL; + int len = strlen(buf); + int fd; + int status = 0; - if (size < 6 || memcmp("From ", map, 5)) + if (!is_from_line(buf, len)) goto corrupt; - /* Make sure we don't trigger on this first line */ - map++; size--; offset=1; + fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); + if (fd < 0) + die("cannot open output file %s", name); + output = fdopen(fd, "w"); - /* - * Search for a line beginning with "From ", and - * having something that looks like a date format. + /* Copy it out, while searching for a line that begins with + * "From " and having something that looks like a date format. */ - do { - int len = linelen(map, size); - if (is_from_line(map, len)) - return offset; - map += len; - size -= len; - offset += len; - } while (size); - return offset; - -corrupt: + for (;;) { + int is_partial = (buf[len-1] != '\n'); + + if (fputs(buf, output) == EOF) + die("cannot write output"); + + if (fgets(buf, sizeof(buf), mbox) == NULL) { + if (feof(mbox)) { + status = 1; + break; + } + die("cannot read mbox"); + } + len = strlen(buf); + if (!is_partial && is_from_line(buf, len)) + break; /* done with one message */ + } + fclose(output); + return status; + + corrupt: + if (output) + fclose(output); + unlink(name); fprintf(stderr, "corrupt mailbox\n"); exit(1); } -int main(int argc, char **argv) +int main(int argc, const char **argv) { - int fd, nr; - struct stat st; - unsigned long size; - void *map; - - if (argc != 3) - usage(); - fd = open(argv[1], O_RDONLY); - if (fd < 0) { - perror(argv[1]); - exit(1); - } - if (chdir(argv[2]) < 0) - usage(); - if (fstat(fd, &st) < 0) { - perror("stat"); - exit(1); + int i, nr, nr_prec = 4; + FILE *mbox = NULL; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + /* do flags here */ + if (!strncmp(arg, "-d", 2)) { + nr_prec = strtol(arg + 2, NULL, 10); + if (nr_prec < 3 || 10 <= nr_prec) + usage(git_mailsplit_usage); + continue; + } } - size = st.st_size; - map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (map == MAP_FAILED) { - perror("mmap"); - close(fd); - exit(1); + + /* Either one remaining arg (dir), or two (mbox and dir) */ + switch (argc - i) { + case 1: + mbox = stdin; + break; + case 2: + if ((mbox = fopen(argv[i], "r")) == NULL) + die("cannot open mbox %s for reading", argv[i]); + break; + default: + usage(git_mailsplit_usage); } - close(fd); + if (chdir(argv[argc - 1]) < 0) + usage(git_mailsplit_usage); + nr = 0; - do { + if (fgets(buf, sizeof(buf), mbox) == NULL) + die("cannot read mbox"); + + for (;;) { char name[10]; - unsigned long len = parse_email(map, size); - assert(len <= size); - sprintf(name, "%04d", ++nr); - fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (fd < 0) { - perror(name); - exit(1); - } - if (write(fd, map, len) != len) { - perror("write"); + + sprintf(name, "%0*d", nr_prec, ++nr); + switch (split_one(mbox, name)) { + case 0: + break; + case 1: + printf("%d\n", nr); + return 0; + default: exit(1); } - close(fd); - map += len; - size -= len; - } while (size > 0); - return 0; + } } diff --git a/prune-packed.c b/prune-packed.c index 5306e8e5ef..73f0f3a462 100644 --- a/prune-packed.c +++ b/prune-packed.c @@ -26,6 +26,9 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len) else if (unlink(pathname) < 0) error("unable to unlink %s", pathname); } + pathname[len] = 0; + if (rmdir(pathname)) + mkdir(pathname, 0777); } static void prune_packed_objects(void) @@ -46,7 +49,7 @@ static void prune_packed_objects(void) sprintf(pathname + len, "%02x/", i); d = opendir(pathname); if (!d) - die("unable to open %s", pathname); + continue; prune_dir(i, d, pathname, len + 3); closedir(d); } diff --git a/server-info.c b/server-info.c index a9e5607f2f..3c08a288db 100644 --- a/server-info.c +++ b/server-info.c @@ -59,6 +59,16 @@ static struct object *parse_object_cheap(const unsigned char *sha1) struct commit *commit = (struct commit *)o; free(commit->buffer); commit->buffer = NULL; + } else if (o->type == tree_type) { + struct tree *tree = (struct tree *)o; + struct tree_entry_list *e, *n; + for (e = tree->entries; e; e = n) { + free(e->name); + e->name = NULL; + n = e->next; + free(e); + } + tree->entries = NULL; } return o; } diff --git a/sha1_file.c b/sha1_file.c index 895c1fab6f..baaa4c00da 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1248,6 +1248,73 @@ char *write_sha1_file_prepare(void *buf, return sha1_file_name(sha1); } +/* + * Link the tempfile to the final place, possibly creating the + * last directory level as you do so. + * + * Returns the errno on failure, 0 on success. + */ +static int link_temp_to_file(const char *tmpfile, char *filename) +{ + int ret; + + if (!link(tmpfile, filename)) + return 0; + + /* + * Try to mkdir the last path component if that failed + * with an ENOENT. + * + * Re-try the "link()" regardless of whether the mkdir + * succeeds, since a race might mean that somebody + * else succeeded. + */ + ret = errno; + if (ret == ENOENT) { + char *dir = strrchr(filename, '/'); + if (dir) { + *dir = 0; + mkdir(filename, 0777); + *dir = '/'; + if (!link(tmpfile, filename)) + return 0; + ret = errno; + } + } + return ret; +} + +/* + * Move the just written object into its final resting place + */ +static int move_temp_to_file(const char *tmpfile, char *filename) +{ + int ret = link_temp_to_file(tmpfile, filename); + if (ret) { + /* + * Coda hack - coda doesn't like cross-directory links, + * so we fall back to a rename, which will mean that it + * won't be able to check collisions, but that's not a + * big deal. + * + * When this succeeds, we just return 0. We have nothing + * left to unlink. + */ + if (ret == EXDEV && !rename(tmpfile, filename)) + return 0; + } + unlink(tmpfile); + if (ret) { + if (ret != EEXIST) { + fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret)); + return -1; + } + /* FIXME!!! Collision check here ? */ + } + + return 0; +} + int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { int size; @@ -1257,7 +1324,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha char *filename; static char tmpfile[PATH_MAX]; unsigned char hdr[50]; - int fd, hdrlen, ret; + int fd, hdrlen; /* Normally if we have it in the pack then we do not bother writing * it out into .git/objects/??/?{38} file. @@ -1320,32 +1387,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha close(fd); free(compressed); - ret = link(tmpfile, filename); - if (ret < 0) { - ret = errno; - - /* - * Coda hack - coda doesn't like cross-directory links, - * so we fall back to a rename, which will mean that it - * won't be able to check collisions, but that's not a - * big deal. - * - * When this succeeds, we just return 0. We have nothing - * left to unlink. - */ - if (ret == EXDEV && !rename(tmpfile, filename)) - return 0; - } - unlink(tmpfile); - if (ret) { - if (ret != EEXIST) { - fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret)); - return -1; - } - /* FIXME!!! Collision check here ? */ - } - - return 0; + return move_temp_to_file(tmpfile, filename); } int write_sha1_to_fd(int fd, const unsigned char *sha1) @@ -1420,8 +1462,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1) int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, size_t bufsize, size_t *bufposn) { - char *filename = sha1_file_name(sha1); - + char tmpfile[PATH_MAX]; int local; z_stream stream; unsigned char real_sha1[20]; @@ -1429,10 +1470,11 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, int ret; SHA_CTX c; - local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666); + snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory()); + local = mkstemp(tmpfile); if (local < 0) - return error("Couldn't open %s\n", filename); + return error("Couldn't open %s for %s\n", tmpfile, sha1_to_hex(sha1)); memset(&stream, 0, sizeof(stream)); @@ -1462,7 +1504,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, size = read(fd, buffer + *bufposn, bufsize - *bufposn); if (size <= 0) { close(local); - unlink(filename); + unlink(tmpfile); if (!size) return error("Connection closed?"); perror("Reading from connection"); @@ -1475,15 +1517,15 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, close(local); SHA1_Final(real_sha1, &c); if (ret != Z_STREAM_END) { - unlink(filename); + unlink(tmpfile); return error("File %s corrupted", sha1_to_hex(sha1)); } if (memcmp(sha1, real_sha1, 20)) { - unlink(filename); + unlink(tmpfile); return error("File %s has bad hash\n", sha1_to_hex(sha1)); } - - return 0; + + return move_temp_to_file(tmpfile, sha1_file_name(sha1)); } int has_pack_index(const unsigned char *sha1) @@ -1545,3 +1587,42 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con munmap(buf, size); return ret; } + +int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object) +{ + int fd; + char *target; + + switch (st->st_mode & S_IFMT) { + case S_IFREG: + fd = open(path, O_RDONLY); + if (fd < 0) + return error("open(\"%s\"): %s", path, + strerror(errno)); + if (index_fd(sha1, fd, st, write_object, NULL) < 0) + return error("%s: failed to insert into database", + path); + break; + case S_IFLNK: + target = xmalloc(st->st_size+1); + if (readlink(path, target, st->st_size+1) != st->st_size) { + char *errstr = strerror(errno); + free(target); + return error("readlink(\"%s\"): %s", path, + errstr); + } + if (!write_object) { + unsigned char hdr[50]; + int hdrlen; + write_sha1_file_prepare(target, st->st_size, "blob", + sha1, hdr, &hdrlen); + } else if (write_sha1_file(target, st->st_size, "blob", sha1)) + return error("%s: failed to insert into database", + path); + free(target); + break; + default: + return error("%s: unsupported file type", path); + } + return 0; +} diff --git a/t/diff-lib.sh b/t/diff-lib.sh index a912f435aa..745a1b0311 100755 --- a/t/diff-lib.sh +++ b/t/diff-lib.sh @@ -29,7 +29,13 @@ compare_diff_raw_z () { compare_diff_patch () { # When heuristics are improved, the score numbers would change. # Ignore them while comparing. - sed -e '/^[dis]*imilarity index [0-9]*%$/d' <"$1" >.tmp-1 - sed -e '/^[dis]*imilarity index [0-9]*%$/d' <"$2" >.tmp-2 + sed -e ' + /^[dis]*imilarity index [0-9]*%$/d + /^index [0-9a-f]*\.\.[0-9a-f]/d + ' <"$1" >.tmp-1 + sed -e ' + /^[dis]*imilarity index [0-9]*%$/d + /^index [0-9a-f]*\.\.[0-9a-f]/d + ' <"$2" >.tmp-2 diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 } diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index bd940bd09b..5c5f854858 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -28,7 +28,7 @@ test_expect_success \ '.git/objects should be empty after git-init-db in an empty repo.' \ 'cmp -s /dev/null should-be-empty' -# also it should have 258 subdirectories; 256 fan-out, pack, and info. +# also it should have 258 subdirectories; 256 fan-out anymore, pack, and info. # 259 is counting "objects" itself find .git/objects -type d -print >full-of-directories test_expect_success \ diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh index f3b6330a9b..beb6d8f487 100755 --- a/t/t4000-diff-format.sh +++ b/t/t4000-diff-format.sh @@ -7,6 +7,7 @@ test_description='Test built-in diff output engine. ' . ./test-lib.sh +. ../diff-lib.sh echo >path0 'Line 1 Line 2 @@ -48,6 +49,6 @@ EOF test_expect_success \ 'validate git-diff-files -p output.' \ - 'cmp -s current expected' + 'compare_diff_patch current expected' test_done diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index be47485682..2e3c20d6b9 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -7,6 +7,7 @@ test_description='Test rename detection in diff engine. ' . ./test-lib.sh +. ../diff-lib.sh echo >path0 'Line 1 Line 2 @@ -61,6 +62,6 @@ EOF test_expect_success \ 'validate the output.' \ - 'diff -I "similarity.*" >/dev/null current expected' + 'compare_diff_patch current expected' test_done diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh index f59614ae25..a23aaa0a94 100755 --- a/t/t4004-diff-rename-symlink.sh +++ b/t/t4004-diff-rename-symlink.sh @@ -10,6 +10,7 @@ copy of symbolic links, but should not produce rename/copy followed by an edit for them. ' . ./test-lib.sh +. ../diff-lib.sh test_expect_success \ 'prepare reference tree' \ @@ -61,6 +62,6 @@ EOF test_expect_success \ 'validate diff output' \ - 'diff -u current expected' + 'compare_diff_patch current expected' test_done diff --git a/update-index.c b/update-index.c index b825a11d2f..01b4088ad6 100644 --- a/update-index.c +++ b/update-index.c @@ -37,8 +37,6 @@ static int add_file_to_cache(const char *path) int size, namelen, option, status; struct cache_entry *ce; struct stat st; - int fd; - char *target; status = lstat(path, &st); if (status < 0 || S_ISDIR(st.st_mode)) { @@ -77,34 +75,9 @@ static int add_file_to_cache(const char *path) fill_stat_cache_info(ce, &st); ce->ce_mode = create_ce_mode(st.st_mode); ce->ce_flags = htons(namelen); - switch (st.st_mode & S_IFMT) { - case S_IFREG: - fd = open(path, O_RDONLY); - if (fd < 0) - return error("open(\"%s\"): %s", path, strerror(errno)); - if (index_fd(ce->sha1, fd, &st, !info_only, NULL) < 0) - return error("%s: failed to insert into database", path); - break; - case S_IFLNK: - target = xmalloc(st.st_size+1); - if (readlink(path, target, st.st_size+1) != st.st_size) { - char *errstr = strerror(errno); - free(target); - return error("readlink(\"%s\"): %s", path, - errstr); - } - if (info_only) { - unsigned char hdr[50]; - int hdrlen; - write_sha1_file_prepare(target, st.st_size, "blob", - ce->sha1, hdr, &hdrlen); - } else if (write_sha1_file(target, st.st_size, "blob", ce->sha1)) - return error("%s: failed to insert into database", path); - free(target); - break; - default: - return error("%s: unsupported file type", path); - } + + if (index_path(ce->sha1, path, &st, !info_only)) + return -1; option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; if (add_cache_entry(ce, option)) @@ -299,6 +272,54 @@ static void update_one(const char *path, const char *prefix, int prefix_length) die("Unable to process file %s", path); } +static void read_index_info(int line_termination) +{ + struct strbuf buf; + strbuf_init(&buf); + while (1) { + char *ptr; + unsigned char sha1[20]; + unsigned int mode; + + read_line(&buf, stdin, line_termination); + if (buf.eof) + break; + + mode = strtoul(buf.buf, &ptr, 8); + if (ptr == buf.buf || *ptr != ' ' || + get_sha1_hex(ptr + 1, sha1) || + ptr[41] != '\t') + goto bad_line; + + ptr += 42; + if (!verify_path(ptr)) { + fprintf(stderr, "Ignoring path %s\n", ptr); + continue; + } + + if (!mode) { + /* mode == 0 means there is no such path -- remove */ + if (remove_file_from_cache(ptr)) + die("git-update-index: unable to remove %s", + ptr); + } + else { + /* mode ' ' sha1 '\t' name + * ptr[-1] points at tab, + * ptr[-41] is at the beginning of sha1 + */ + ptr[-42] = ptr[-1] = 0; + if (add_cacheinfo(buf.buf, ptr-41, ptr)) + die("git-update-index: unable to update %s", + ptr); + } + continue; + + bad_line: + die("malformed index info %s", buf.buf); + } +} + int main(int argc, const char **argv) { int i, newfd, entries, has_errors = 0, line_termination = '\n'; @@ -373,6 +394,11 @@ int main(int argc, const char **argv) read_from_stdin = 1; break; } + if (!strcmp(path, "--index-info")) { + allow_add = allow_replace = allow_remove = 1; + read_index_info(line_termination); + continue; + } if (!strcmp(path, "--ignore-missing")) { not_new = 1; continue; diff --git a/update-server-info.c b/update-server-info.c index e824f62eaf..b70856373f 100644 --- a/update-server-info.c +++ b/update-server-info.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "object.h" static const char update_server_info_usage[] = "git-update-server-info [--force]"; @@ -7,6 +8,8 @@ int main(int ac, char **av) { int i; int force = 0; + track_object_refs = 0; + for (i = 1; i < ac; i++) { if (av[i][0] == '-') { if (!strcmp("--force", av[i]) || |