diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Documentation/Makefile | 6 | ||||
-rw-r--r-- | Documentation/git-cat-file.txt | 9 | ||||
-rw-r--r-- | Documentation/git-clone.txt | 9 | ||||
-rw-r--r-- | Documentation/tutorial-2.txt | 2 | ||||
-rwxr-xr-x | GIT-VERSION-GEN | 4 | ||||
-rw-r--r-- | Makefile | 89 | ||||
-rw-r--r-- | builtin-add.c | 187 | ||||
-rw-r--r-- | builtin-apply.c (renamed from apply.c) | 3 | ||||
-rw-r--r-- | builtin-cat-file.c (renamed from cat-file.c) | 5 | ||||
-rw-r--r-- | builtin-commit-tree.c (renamed from commit-tree.c) | 5 | ||||
-rw-r--r-- | builtin-diff-files.c (renamed from diff-files.c) | 3 | ||||
-rw-r--r-- | builtin-diff-index.c (renamed from diff-index.c) | 3 | ||||
-rw-r--r-- | builtin-diff-stages.c (renamed from diff-stages.c) | 3 | ||||
-rw-r--r-- | builtin-diff-tree.c (renamed from diff-tree.c) | 3 | ||||
-rw-r--r-- | builtin-diff.c | 2 | ||||
-rw-r--r-- | builtin-log.c | 191 | ||||
-rw-r--r-- | builtin-ls-files.c (renamed from ls-files.c) | 384 | ||||
-rw-r--r-- | builtin-ls-tree.c (renamed from ls-tree.c) | 5 | ||||
-rw-r--r-- | builtin-read-tree.c (renamed from read-tree.c) | 3 | ||||
-rw-r--r-- | builtin-rev-list.c | 5 | ||||
-rw-r--r-- | builtin-rm.c | 150 | ||||
-rw-r--r-- | builtin-show-branch.c (renamed from show-branch.c) | 9 | ||||
-rw-r--r-- | builtin-tar-tree.c (renamed from tar-tree.c) | 62 | ||||
-rw-r--r-- | builtin-upload-tar.c | 74 | ||||
-rw-r--r-- | builtin.h | 16 | ||||
-rw-r--r-- | cache.h | 3 | ||||
-rw-r--r-- | commit.c | 134 | ||||
-rw-r--r-- | commit.h | 3 | ||||
-rw-r--r-- | connect.c | 2 | ||||
-rw-r--r-- | contrib/git-svn/Makefile | 1 | ||||
-rwxr-xr-x | contrib/git-svn/git-svn.perl | 108 | ||||
-rw-r--r-- | contrib/git-svn/t/lib-git-svn.sh | 39 | ||||
-rw-r--r-- | contrib/git-svn/t/t0000-contrib-git-svn.sh | 43 | ||||
-rw-r--r-- | contrib/git-svn/t/t0001-contrib-git-svn-props.sh | 125 | ||||
-rwxr-xr-x | contrib/gitview/gitview | 26 | ||||
-rw-r--r-- | contrib/gitview/gitview.txt | 6 | ||||
-rw-r--r-- | date.c | 29 | ||||
-rw-r--r-- | diff.c | 21 | ||||
-rw-r--r-- | diff.h | 3 | ||||
-rw-r--r-- | dir.c | 401 | ||||
-rw-r--r-- | dir.h | 51 | ||||
-rw-r--r-- | fetch-pack.c | 66 | ||||
-rw-r--r-- | fetch.c | 20 | ||||
-rw-r--r-- | fetch.h | 3 | ||||
-rwxr-xr-x | generate-cmdlist.sh | 1 | ||||
-rwxr-xr-x | git-add.sh | 56 | ||||
-rwxr-xr-x | git-clone.sh | 14 | ||||
-rwxr-xr-x | git-commit.sh | 20 | ||||
-rwxr-xr-x | git-cvsexportcommit.perl | 44 | ||||
-rwxr-xr-x | git-cvsimport.perl | 228 | ||||
-rwxr-xr-x | git-format-patch.sh | 344 | ||||
-rwxr-xr-x | git-ls-remote.sh | 10 | ||||
-rwxr-xr-x | git-rebase.sh | 2 | ||||
-rwxr-xr-x | git-rm.sh | 70 | ||||
-rw-r--r-- | git.c | 19 | ||||
-rw-r--r-- | log-tree.c | 74 | ||||
-rw-r--r-- | mailinfo.c | 145 | ||||
-rw-r--r-- | mktag.c | 45 | ||||
-rw-r--r-- | read-cache.c | 66 | ||||
-rw-r--r-- | repo-config.c | 3 | ||||
-rw-r--r-- | revision.c | 8 | ||||
-rw-r--r-- | revision.h | 2 | ||||
-rw-r--r-- | sha1_file.c | 185 | ||||
-rwxr-xr-x | t/t1002-read-tree-m-u-2way.sh | 20 | ||||
-rw-r--r-- | update-index.c | 64 |
66 files changed, 2342 insertions, 1396 deletions
diff --git a/.gitignore b/.gitignore index b5959d6311..ec4c0dc3df 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ git-prune git-prune-packed git-pull git-push +git-quiltimport git-read-tree git-rebase git-receive-pack @@ -115,6 +116,7 @@ git-update-index git-update-ref git-update-server-info git-upload-pack +git-upload-tar git-var git-verify-pack git-verify-tag diff --git a/Documentation/Makefile b/Documentation/Makefile index 2a08f592d9..2b0efe7921 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -52,9 +52,9 @@ man1: $(DOC_MAN1) man7: $(DOC_MAN7) install: man - $(INSTALL) -d -m755 $(DESTDIR)/$(man1) $(DESTDIR)/$(man7) - $(INSTALL) $(DOC_MAN1) $(DESTDIR)/$(man1) - $(INSTALL) $(DOC_MAN7) $(DESTDIR)/$(man7) + $(INSTALL) -d -m755 $(DESTDIR)$(man1) $(DESTDIR)$(man7) + $(INSTALL) $(DOC_MAN1) $(DESTDIR)$(man1) + $(INSTALL) $(DOC_MAN7) $(DESTDIR)$(man7) # diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 504eb1b16a..5e9cbf875d 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -8,12 +8,12 @@ git-cat-file - Provide content or type information for repository objects SYNOPSIS -------- -'git-cat-file' [-t | -s | -e | <type>] <object> +'git-cat-file' [-t | -s | -e | -p | <type>] <object> DESCRIPTION ----------- Provides content or type of objects in the repository. The type -is required unless '-t' is used to find the object type, +is required unless '-t' or '-p' is used to find the object type, or '-s' is used to find the object size. OPTIONS @@ -33,6 +33,9 @@ OPTIONS Suppress all output; instead exit with zero status if <object> exists and is a valid object. +-p:: + Pretty-print the contents of <object> based on its type. + <type>:: Typically this matches the real type of <object> but asking for a type that can trivially be dereferenced from the given @@ -49,6 +52,8 @@ If '-s' is specified, the size of the <object> in bytes. If '-e' is specified, no output. +If '-p' is specified, the contents of <object> are pretty-printed. + Otherwise the raw (though uncompressed) contents of the <object> will be returned. diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index b333f51045..94d9393372 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -9,8 +9,8 @@ git-clone - Clones a repository SYNOPSIS -------- [verse] -'git-clone' [-l [-s]] [-q] [-n] [--bare] [-o <name>] [-u <upload-pack>] - [--reference <repository>] +'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare] + [-o <name>] [-u <upload-pack>] [--reference <repository>] <repository> [<directory>] DESCRIPTION @@ -89,6 +89,11 @@ OPTIONS the command to specify non-default path for the command run on the other end. +--template=<template_directory>:: + Specify the directory from which templates will be used; + if unset the templates are taken from the installation + defined default, typically `/usr/share/git-core/templates`. + <repository>:: The (possibly remote) repository to clone from. It can be any URL git-fetch supports. diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 7de91aa735..08d3453e5c 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -375,7 +375,7 @@ What next? At this point you should know everything necessary to read the man pages for any of the git commands; one good place to start would be -with the commands mentioned in link:everday.html[Everyday git]. You +with the commands mentioned in link:everyday.html[Everyday git]. You should be able to find any unknown jargon in the link:glossary.html[Glosssay]. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 7fcefcd7c4..a461518cde 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -5,7 +5,7 @@ DEF_VER=v1.3.GIT # First try git-describe, then see if there is a version file # (included in release tarballs), then default -if VN=$(git-describe --abbrev=4 HEAD 2>/dev/null); then +if VN=$(git describe --abbrev=4 HEAD 2>/dev/null); then VN=$(echo "$VN" | sed -e 's/-/./g'); elif test -f version then @@ -16,7 +16,7 @@ fi VN=$(expr "$VN" : v*'\(.*\)') -dirty=$(sh -c 'git-diff-index --name-only HEAD' 2>/dev/null) || dirty= +dirty=$(sh -c 'git diff-index --name-only HEAD' 2>/dev/null) || dirty= case "$dirty" in '') ;; @@ -113,14 +113,14 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ ### --- END CONFIGURATION SECTION --- SCRIPT_SH = \ - git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \ + git-bisect.sh git-branch.sh git-checkout.sh \ git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh \ - git-format-patch.sh git-ls-remote.sh \ + git-ls-remote.sh \ git-merge-one-file.sh git-parse-remote.sh \ git-prune.sh git-pull.sh git-rebase.sh \ git-repack.sh git-request-pull.sh git-reset.sh \ - git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \ + git-resolve.sh git-revert.sh git-sh-setup.sh \ git-tag.sh git-verify-tag.sh \ git-applymbox.sh git-applypatch.sh git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ @@ -149,19 +149,16 @@ SIMPLE_PROGRAMS = \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - 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-checkout-index$X git-clone-pack$X \ + git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ - git-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \ + git-mailinfo$X git-merge-base$X \ git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ - git-peek-remote$X git-prune-packed$X git-read-tree$X \ + git-peek-remote$X git-prune-packed$X \ git-receive-pack$X git-rev-parse$X \ - git-send-pack$X git-show-branch$X git-shell$X \ + git-send-pack$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ - git-ssh-upload$X git-tar-tree$X git-unpack-file$X \ + git-ssh-upload$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 \ @@ -170,8 +167,13 @@ PROGRAMS = \ BUILT_INS = git-log$X git-whatchanged$X git-show$X \ git-count-objects$X git-diff$X git-push$X \ - git-grep$X git-rev-list$X git-check-ref-format$X \ - git-init-db$X + git-grep$X git-add$X git-rm$X git-rev-list$X \ + git-check-ref-format$X \ + git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \ + git-ls-files$X git-ls-tree$X \ + git-read-tree$X git-commit-tree$X \ + git-apply$X git-show-branch$X git-diff-files$X \ + git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -200,7 +202,7 @@ LIB_H = \ blob.h cache.h commit.h csum-file.h delta.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ - tree-walk.h log-tree.h + tree-walk.h log-tree.h dir.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -211,7 +213,7 @@ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o base85.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o \ - quote.o read-cache.o refs.o run-command.o \ + quote.o read-cache.o refs.o run-command.o dir.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ @@ -219,8 +221,14 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-init-db.o + builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \ + builtin-rm.o builtin-init-db.o \ + builtin-tar-tree.o builtin-upload-tar.o \ + builtin-ls-files.o builtin-ls-tree.o \ + builtin-read-tree.o builtin-commit-tree.o \ + builtin-apply.o builtin-show-branch.o builtin-diff-files.o \ + builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ + builtin-cat-file.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -488,37 +496,43 @@ $(BUILT_INS): git$X rm -f $@ && ln git$X $@ common-cmds.h: Documentation/git-*.txt - ./generate-cmdlist.sh > $@ + ./generate-cmdlist.sh > $@+ + mv $@+ $@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh - rm -f $@ + rm -f $@ $@+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ - $@.sh >$@ - chmod +x $@ + $@.sh >$@+ + chmod +x $@+ + mv $@+ $@ $(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl - rm -f $@ + rm -f $@ $@+ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - $@.perl >$@ - chmod +x $@ + $@.perl >$@+ + chmod +x $@+ + mv $@+ $@ $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py - rm -f $@ + rm -f $@ $@+ sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - $@.py >$@ - chmod +x $@ + $@.py >$@+ + chmod +x $@+ + mv $@+ $@ git-cherry-pick: git-revert - cp $< $@ + cp $< $@+ + mv $@+ $@ git-status: git-commit - cp $< $@ + cp $< $@+ + mv $@+ $@ # These can record GIT_VERSION git$X git.spec \ @@ -627,7 +641,14 @@ install: all $(MAKE) -C templates install $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' - $(foreach p,$(BUILT_INS), rm -f '$(DESTDIR_SQ)$(bindir_SQ)/$p' && ln '$(DESTDIR_SQ)$(bindir_SQ)/git$X' '$(DESTDIR_SQ)$(bindir_SQ)/$p' ;) + if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ + then \ + ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ + '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' || \ + cp '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ + '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X'; \ + fi + $(foreach p,$(BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) install-doc: $(MAKE) -C Documentation install @@ -638,7 +659,8 @@ install-doc: ### Maintainer's dist rules git.spec: git.spec.in - sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@ + sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@+ + mv $@+ $@ GIT_TARNAME=git-$(GIT_VERSION) dist: git.spec git-tar-tree @@ -665,7 +687,7 @@ dist-doc: : rm -fr .doc-tmp-dir mkdir .doc-tmp-dir .doc-tmp-dir/man1 .doc-tmp-dir/man7 - $(MAKE) -C Documentation DESTDIR=. \ + $(MAKE) -C Documentation DESTDIR=./ \ man1=../.doc-tmp-dir/man1 \ man7=../.doc-tmp-dir/man7 \ install @@ -709,4 +731,3 @@ check-docs:: *) echo "no link: $$v";; \ esac ; \ done | sort - diff --git a/builtin-add.c b/builtin-add.c new file mode 100644 index 0000000000..6166f66bce --- /dev/null +++ b/builtin-add.c @@ -0,0 +1,187 @@ +/* + * "git add" builtin command + * + * Copyright (C) 2006 Linus Torvalds + */ +#include <fnmatch.h> + +#include "cache.h" +#include "builtin.h" +#include "dir.h" + +static const char builtin_add_usage[] = +"git-add [-n] [-v] <filepattern>..."; + +static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) +{ + char *seen; + int i, specs; + struct dir_entry **src, **dst; + + for (specs = 0; pathspec[specs]; specs++) + /* nothing */; + seen = xmalloc(specs); + memset(seen, 0, specs); + + src = dst = dir->entries; + i = dir->nr; + while (--i >= 0) { + struct dir_entry *entry = *src++; + if (!match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) { + free(entry); + continue; + } + *dst++ = entry; + } + dir->nr = dst - dir->entries; + + for (i = 0; i < specs; i++) { + struct stat st; + const char *match; + if (seen[i]) + continue; + + /* Existing file? We must have ignored it */ + match = pathspec[i]; + if (!match[0] || !lstat(match, &st)) + continue; + die("pathspec '%s' did not match any files", match); + } +} + +static void fill_directory(struct dir_struct *dir, const char **pathspec) +{ + const char *path, *base; + int baselen; + + /* Set up the default git porcelain excludes */ + memset(dir, 0, sizeof(*dir)); + dir->exclude_per_dir = ".gitignore"; + path = git_path("info/exclude"); + if (!access(path, R_OK)) + add_excludes_from_file(dir, path); + + /* + * Calculate common prefix for the pathspec, and + * use that to optimize the directory walk + */ + baselen = common_prefix(pathspec); + path = "."; + base = ""; + if (baselen) { + char *common = xmalloc(baselen + 1); + common = xmalloc(baselen + 1); + memcpy(common, *pathspec, baselen); + common[baselen] = 0; + path = base = common; + } + + /* Read the directory and prune it */ + read_directory(dir, path, base, baselen); + if (pathspec) + prune_directory(dir, pathspec, baselen); +} + +static int add_file_to_index(const char *path, int verbose) +{ + int size, namelen; + struct stat st; + struct cache_entry *ce; + + if (lstat(path, &st)) + die("%s: unable to stat (%s)", path, strerror(errno)); + + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) + die("%s: can only add regular files or symbolic links", path); + + namelen = strlen(path); + size = cache_entry_size(namelen); + ce = xcalloc(1, size); + memcpy(ce->name, path, namelen); + ce->ce_flags = htons(namelen); + fill_stat_cache_info(ce, &st); + + ce->ce_mode = create_ce_mode(st.st_mode); + if (!trust_executable_bit) { + /* If there is an existing entry, pick the mode bits + * from it. + */ + int pos = cache_name_pos(path, namelen); + if (pos >= 0) + ce->ce_mode = active_cache[pos]->ce_mode; + } + + if (index_path(ce->sha1, path, &st, 1)) + die("unable to index file %s", path); + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD)) + die("unable to add %s to index",path); + if (verbose) + printf("add '%s'\n", path); + return 0; +} + +static struct cache_file cache_file; + +int cmd_add(int argc, const char **argv, char **envp) +{ + int i, newfd; + int verbose = 0, show_only = 0; + const char *prefix = setup_git_directory(); + const char **pathspec; + struct dir_struct dir; + + git_config(git_default_config); + + newfd = hold_index_file_for_update(&cache_file, get_index_file()); + if (newfd < 0) + die("unable to create new cachefile"); + + if (read_cache() < 0) + die("index file corrupt"); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-n")) { + show_only = 1; + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } + die(builtin_add_usage); + } + git_config(git_default_config); + pathspec = get_pathspec(prefix, argv + i); + + fill_directory(&dir, pathspec); + + if (show_only) { + const char *sep = "", *eof = ""; + for (i = 0; i < dir.nr; i++) { + printf("%s%s", sep, dir.entries[i]->name); + sep = " "; + eof = "\n"; + } + fputs(eof, stdout); + return 0; + } + + for (i = 0; i < dir.nr; i++) + add_file_to_index(dir.entries[i]->name, verbose); + + if (active_cache_changed) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new index file"); + } + + return 0; +} diff --git a/apply.c b/builtin-apply.c index 0ed9d132e8..4056b9d67b 100644 --- a/apply.c +++ b/builtin-apply.c @@ -11,6 +11,7 @@ #include "quote.h" #include "blob.h" #include "delta.h" +#include "builtin.h" // --check turns on checking that the working tree matches the // files that are being modified, but doesn't apply the patch @@ -2151,7 +2152,7 @@ static int git_apply_config(const char *var, const char *value) } -int main(int argc, char **argv) +int cmd_apply(int argc, const char **argv, char **envp) { int i; int read_stdin = 1; diff --git a/cat-file.c b/builtin-cat-file.c index 7413feed78..4d36817e5f 100644 --- a/cat-file.c +++ b/builtin-cat-file.c @@ -7,6 +7,7 @@ #include "exec_cmd.h" #include "tag.h" #include "tree.h" +#include "builtin.h" static void flush_buffer(const char *buf, unsigned long size) { @@ -93,7 +94,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long return 0; } -int main(int argc, char **argv) +int cmd_cat_file(int argc, const char **argv, char **envp) { unsigned char sha1[20]; char type[20]; @@ -140,7 +141,7 @@ int main(int argc, char **argv) /* custom pretty-print here */ if (!strcmp(type, tree_type)) - return execl_git_cmd("ls-tree", argv[2], NULL); + return cmd_ls_tree(2, argv + 1, NULL); buf = read_sha1_file(sha1, type, &size); if (!buf) diff --git a/commit-tree.c b/builtin-commit-tree.c index 0320036e80..ec082bf754 100644 --- a/commit-tree.c +++ b/builtin-commit-tree.c @@ -6,6 +6,7 @@ #include "cache.h" #include "commit.h" #include "tree.h" +#include "builtin.h" #define BLOCKING (1ul << 14) @@ -76,7 +77,7 @@ static int new_parent(int idx) return 1; } -int main(int argc, char **argv) +int cmd_commit_tree(int argc, const char **argv, char **envp) { int i; int parents = 0; @@ -98,7 +99,7 @@ int main(int argc, char **argv) check_valid(tree_sha1, tree_type); for (i = 2; i < argc; i += 2) { - char *a, *b; + const char *a, *b; a = argv[i]; b = argv[i+1]; if (!b || strcmp(a, "-p")) usage(commit_tree_usage); diff --git a/diff-files.c b/builtin-diff-files.c index b9d193d506..cebda828ee 100644 --- a/diff-files.c +++ b/builtin-diff-files.c @@ -7,12 +7,13 @@ #include "diff.h" #include "commit.h" #include "revision.h" +#include "builtin.h" static const char diff_files_usage[] = "git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]" COMMON_DIFF_OPTIONS_HELP; -int main(int argc, const char **argv) +int cmd_diff_files(int argc, const char **argv, char **envp) { struct rev_info rev; int silent = 0; diff --git a/diff-index.c b/builtin-diff-index.c index 8c9f60173b..1958580d82 100644 --- a/diff-index.c +++ b/builtin-diff-index.c @@ -2,13 +2,14 @@ #include "diff.h" #include "commit.h" #include "revision.h" +#include "builtin.h" static const char diff_cache_usage[] = "git-diff-index [-m] [--cached] " "[<common diff options>] <tree-ish> [<path>...]" COMMON_DIFF_OPTIONS_HELP; -int main(int argc, const char **argv) +int cmd_diff_index(int argc, const char **argv, char **envp) { struct rev_info rev; int cached = 0; diff --git a/diff-stages.c b/builtin-diff-stages.c index dcd20e79e4..7c157ca889 100644 --- a/diff-stages.c +++ b/builtin-diff-stages.c @@ -4,6 +4,7 @@ #include "cache.h" #include "diff.h" +#include "builtin.h" static struct diff_options diff_options; @@ -54,7 +55,7 @@ static void diff_stages(int stage1, int stage2, const char **pathspec) } } -int main(int ac, const char **av) +int cmd_diff_stages(int ac, const char **av, char **envp) { int stage1, stage2; const char *prefix = setup_git_directory(); diff --git a/diff-tree.c b/builtin-diff-tree.c index 69bb74b310..cc53b81ac4 100644 --- a/diff-tree.c +++ b/builtin-diff-tree.c @@ -2,6 +2,7 @@ #include "diff.h" #include "commit.h" #include "log-tree.h" +#include "builtin.h" static struct rev_info log_tree_opt; @@ -58,7 +59,7 @@ static const char diff_tree_usage[] = " --root include the initial commit as diff against /dev/null\n" COMMON_DIFF_OPTIONS_HELP; -int main(int argc, const char **argv) +int cmd_diff_tree(int argc, const char **argv, char **envp) { int nr_sha1; char line[1000]; diff --git a/builtin-diff.c b/builtin-diff.c index de81b05e32..27451d5613 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -233,7 +233,7 @@ static int builtin_diff_combined(struct rev_info *revs, return 0; } -static void add_head(struct rev_info *revs) +void add_head(struct rev_info *revs) { unsigned char sha1[20]; struct object *obj; diff --git a/builtin-log.c b/builtin-log.c index c4ceee0f98..db1912a05b 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -9,6 +9,10 @@ #include "diff.h" #include "revision.h" #include "log-tree.h" +#include "builtin.h" + +/* this is in builtin-diff.c */ +void add_head(struct rev_info *revs); static int cmd_log_wc(int argc, const char **argv, char **envp, struct rev_info *rev) @@ -74,3 +78,190 @@ int cmd_log(int argc, const char **argv, char **envp) rev.diffopt.recursive = 1; return cmd_log_wc(argc, argv, envp, &rev); } + +static int istitlechar(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '.' || c == '_'; +} + +static FILE *realstdout = NULL; +static char *output_directory = NULL; + +static void reopen_stdout(struct commit *commit, int nr, int keep_subject) +{ + char filename[1024]; + char *sol; + int len = 0; + + if (output_directory) { + strncpy(filename, output_directory, 1010); + len = strlen(filename); + if (filename[len - 1] != '/') + filename[len++] = '/'; + } + + sprintf(filename + len, "%04d", nr); + len = strlen(filename); + + sol = strstr(commit->buffer, "\n\n"); + if (sol) { + int j, space = 1; + + sol += 2; + /* strip [PATCH] or [PATCH blabla] */ + if (!keep_subject && !strncmp(sol, "[PATCH", 6)) { + char *eos = strchr(sol + 6, ']'); + if (eos) { + while (isspace(*eos)) + eos++; + sol = eos; + } + } + + for (j = 0; len < 1024 - 6 && sol[j] && sol[j] != '\n'; j++) { + if (istitlechar(sol[j])) { + if (space) { + filename[len++] = '-'; + space = 0; + } + filename[len++] = sol[j]; + if (sol[j] == '.') + while (sol[j + 1] == '.') + j++; + } else + space = 1; + } + while (filename[len - 1] == '.' || filename[len - 1] == '-') + len--; + } + strcpy(filename + len, ".txt"); + fprintf(realstdout, "%s\n", filename); + freopen(filename, "w", stdout); +} + +int cmd_format_patch(int argc, const char **argv, char **envp) +{ + struct commit *commit; + struct commit **list = NULL; + struct rev_info rev; + int nr = 0, total, i, j; + int use_stdout = 0; + int numbered = 0; + int start_number = -1; + int keep_subject = 0; + + init_revisions(&rev); + rev.commit_format = CMIT_FMT_EMAIL; + rev.verbose_header = 1; + rev.diff = 1; + rev.diffopt.with_raw = 0; + rev.diffopt.with_stat = 1; + rev.combine_merges = 0; + rev.ignore_merges = 1; + rev.diffopt.output_format = DIFF_FORMAT_PATCH; + + /* + * Parse the arguments before setup_revisions(), or something + * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is + * possibly a valid SHA1. + */ + for (i = 1, j = 1; i < argc; i++) { + if (!strcmp(argv[i], "--stdout")) + use_stdout = 1; + else if (!strcmp(argv[i], "-n") || + !strcmp(argv[i], "--numbered")) + numbered = 1; + else if (!strncmp(argv[i], "--start-number=", 15)) + start_number = strtol(argv[i] + 15, NULL, 10); + else if (!strcmp(argv[i], "--start-number")) { + i++; + if (i == argc) + die("Need a number for --start-number"); + start_number = strtol(argv[i], NULL, 10); + } else if (!strcmp(argv[i], "-k") || + !strcmp(argv[i], "--keep-subject")) { + keep_subject = 1; + rev.total = -1; + } else if (!strcmp(argv[i], "-o")) { + if (argc < 3) + die ("Which directory?"); + if (mkdir(argv[i + 1], 0777) < 0 && errno != EEXIST) + die("Could not create directory %s", + argv[i + 1]); + output_directory = strdup(argv[i + 1]); + i++; + } + else if (!strcmp(argv[i], "--attach")) + rev.mime_boundary = git_version_string; + else if (!strncmp(argv[i], "--attach=", 9)) + rev.mime_boundary = argv[i] + 9; + else + argv[j++] = argv[i]; + } + argc = j; + + if (start_number < 0) + start_number = 1; + if (numbered && keep_subject) + die ("-n and -k are mutually exclusive."); + + argc = setup_revisions(argc, argv, &rev, "HEAD"); + if (argc > 1) + die ("unrecognized argument: %s", argv[1]); + + if (rev.pending_objects && rev.pending_objects->next == NULL) { + rev.pending_objects->item->flags |= UNINTERESTING; + add_head(&rev); + } + + if (!use_stdout) + realstdout = fdopen(dup(1), "w"); + + prepare_revision_walk(&rev); + while ((commit = get_revision(&rev)) != NULL) { + /* ignore merges */ + if (commit->parents && commit->parents->next) + continue; + nr++; + list = realloc(list, nr * sizeof(list[0])); + list[nr - 1] = commit; + } + total = nr; + if (numbered) + rev.total = total + start_number - 1; + while (0 <= --nr) { + int shown; + commit = list[nr]; + rev.nr = total - nr + (start_number - 1); + if (!use_stdout) + reopen_stdout(commit, rev.nr, keep_subject); + shown = log_tree_commit(&rev, commit); + free(commit->buffer); + commit->buffer = NULL; + + /* We put one extra blank line between formatted + * patches and this flag is used by log-tree code + * to see if it needs to emit a LF before showing + * the log; when using one file per patch, we do + * not want the extra blank line. + */ + if (!use_stdout) + rev.shown_one = 0; + if (shown) { + if (rev.mime_boundary) + printf("\n--%s%s--\n\n\n", + mime_boundary_leader, + rev.mime_boundary); + else + printf("-- \n%s\n\n", git_version_string); + } + if (!use_stdout) + fclose(stdout); + } + if (output_directory) + free(output_directory); + free(list); + return 0; +} + diff --git a/ls-files.c b/builtin-ls-files.c index 4a4af1ca3b..8dae9f70e2 100644 --- a/ls-files.c +++ b/builtin-ls-files.c @@ -5,23 +5,21 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#include <dirent.h> #include <fnmatch.h> #include "cache.h" #include "quote.h" +#include "dir.h" +#include "builtin.h" static int abbrev = 0; static int show_deleted = 0; static int show_cached = 0; static int show_others = 0; -static int show_ignored = 0; static int show_stage = 0; static int show_unmerged = 0; static int show_modified = 0; static int show_killed = 0; -static int show_other_directories = 0; -static int hide_empty_directories = 0; static int show_valid_bit = 0; static int line_terminator = '\n'; @@ -38,309 +36,6 @@ static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; -static const char *exclude_per_dir = NULL; - -/* We maintain three exclude pattern lists: - * EXC_CMDL lists patterns explicitly given on the command line. - * EXC_DIRS lists patterns obtained from per-directory ignore files. - * EXC_FILE lists patterns from fallback ignore files. - */ -#define EXC_CMDL 0 -#define EXC_DIRS 1 -#define EXC_FILE 2 -static struct exclude_list { - int nr; - int alloc; - struct exclude { - const char *pattern; - const char *base; - int baselen; - } **excludes; -} exclude_list[3]; - -static void add_exclude(const char *string, const char *base, - int baselen, struct exclude_list *which) -{ - struct exclude *x = xmalloc(sizeof (*x)); - - x->pattern = string; - x->base = base; - x->baselen = baselen; - if (which->nr == which->alloc) { - which->alloc = alloc_nr(which->alloc); - which->excludes = realloc(which->excludes, - which->alloc * sizeof(x)); - } - which->excludes[which->nr++] = x; -} - -static int add_excludes_from_file_1(const char *fname, - const char *base, - int baselen, - struct exclude_list *which) -{ - int fd, i; - long size; - char *buf, *entry; - - fd = open(fname, O_RDONLY); - if (fd < 0) - goto err; - size = lseek(fd, 0, SEEK_END); - if (size < 0) - goto err; - lseek(fd, 0, SEEK_SET); - if (size == 0) { - close(fd); - return 0; - } - buf = xmalloc(size+1); - if (read(fd, buf, size) != size) - goto err; - close(fd); - - buf[size++] = '\n'; - entry = buf; - for (i = 0; i < size; i++) { - if (buf[i] == '\n') { - if (entry != buf + i && entry[0] != '#') { - buf[i - (i && buf[i-1] == '\r')] = 0; - add_exclude(entry, base, baselen, which); - } - entry = buf + i + 1; - } - } - return 0; - - err: - if (0 <= fd) - close(fd); - return -1; -} - -static void add_excludes_from_file(const char *fname) -{ - if (add_excludes_from_file_1(fname, "", 0, - &exclude_list[EXC_FILE]) < 0) - die("cannot use %s as an exclude file", fname); -} - -static int push_exclude_per_directory(const char *base, int baselen) -{ - char exclude_file[PATH_MAX]; - struct exclude_list *el = &exclude_list[EXC_DIRS]; - int current_nr = el->nr; - - if (exclude_per_dir) { - memcpy(exclude_file, base, baselen); - strcpy(exclude_file + baselen, exclude_per_dir); - add_excludes_from_file_1(exclude_file, base, baselen, el); - } - return current_nr; -} - -static void pop_exclude_per_directory(int stk) -{ - struct exclude_list *el = &exclude_list[EXC_DIRS]; - - while (stk < el->nr) - free(el->excludes[--el->nr]); -} - -/* Scan the list and let the last match determines the fate. - * Return 1 for exclude, 0 for include and -1 for undecided. - */ -static int excluded_1(const char *pathname, - int pathlen, - struct exclude_list *el) -{ - int i; - - if (el->nr) { - for (i = el->nr - 1; 0 <= i; i--) { - struct exclude *x = el->excludes[i]; - const char *exclude = x->pattern; - int to_exclude = 1; - - if (*exclude == '!') { - to_exclude = 0; - exclude++; - } - - if (!strchr(exclude, '/')) { - /* match basename */ - const char *basename = strrchr(pathname, '/'); - basename = (basename) ? basename+1 : pathname; - if (fnmatch(exclude, basename, 0) == 0) - return to_exclude; - } - else { - /* match with FNM_PATHNAME: - * exclude has base (baselen long) implicitly - * in front of it. - */ - int baselen = x->baselen; - if (*exclude == '/') - exclude++; - - if (pathlen < baselen || - (baselen && pathname[baselen-1] != '/') || - strncmp(pathname, x->base, baselen)) - continue; - - if (fnmatch(exclude, pathname+baselen, - FNM_PATHNAME) == 0) - return to_exclude; - } - } - } - return -1; /* undecided */ -} - -static int excluded(const char *pathname) -{ - int pathlen = strlen(pathname); - int st; - - for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, &exclude_list[st])) { - case 0: - return 0; - case 1: - return 1; - } - } - return 0; -} - -struct nond_on_fs { - int len; - char name[FLEX_ARRAY]; /* more */ -}; - -static struct nond_on_fs **dir; -static int nr_dir; -static int dir_alloc; - -static void add_name(const char *pathname, int len) -{ - struct nond_on_fs *ent; - - if (cache_name_pos(pathname, len) >= 0) - return; - - if (nr_dir == dir_alloc) { - dir_alloc = alloc_nr(dir_alloc); - dir = xrealloc(dir, dir_alloc*sizeof(ent)); - } - ent = xmalloc(sizeof(*ent) + len + 1); - ent->len = len; - memcpy(ent->name, pathname, len); - ent->name[len] = 0; - dir[nr_dir++] = ent; -} - -static int dir_exists(const char *dirname, int len) -{ - int pos = cache_name_pos(dirname, len); - if (pos >= 0) - return 1; - pos = -pos-1; - if (pos >= active_nr) /* can't */ - return 0; - return !strncmp(active_cache[pos]->name, dirname, len); -} - -/* - * Read a directory tree. We currently ignore anything but - * directories, regular files and symlinks. That's because git - * doesn't handle them at all yet. Maybe that will change some - * day. - * - * Also, we ignore the name ".git" (even if it is not a directory). - * That likely will not change. - */ -static int read_directory(const char *path, const char *base, int baselen) -{ - DIR *fdir = opendir(path); - int contents = 0; - - if (fdir) { - int exclude_stk; - struct dirent *de; - char fullname[MAXPATHLEN + 1]; - memcpy(fullname, base, baselen); - - exclude_stk = push_exclude_per_directory(base, baselen); - - while ((de = readdir(fdir)) != NULL) { - int len; - - if ((de->d_name[0] == '.') && - (de->d_name[1] == 0 || - !strcmp(de->d_name + 1, ".") || - !strcmp(de->d_name + 1, "git"))) - continue; - len = strlen(de->d_name); - memcpy(fullname + baselen, de->d_name, len+1); - if (excluded(fullname) != show_ignored) { - if (!show_ignored || DTYPE(de) != DT_DIR) { - continue; - } - } - - switch (DTYPE(de)) { - struct stat st; - int subdir, rewind_base; - default: - continue; - case DT_UNKNOWN: - if (lstat(fullname, &st)) - continue; - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) - break; - if (!S_ISDIR(st.st_mode)) - continue; - /* fallthrough */ - case DT_DIR: - memcpy(fullname + baselen + len, "/", 2); - len++; - rewind_base = nr_dir; - subdir = read_directory(fullname, fullname, - baselen + len); - if (show_other_directories && - (subdir || !hide_empty_directories) && - !dir_exists(fullname, baselen + len)) { - // Rewind the read subdirectory - while (nr_dir > rewind_base) - free(dir[--nr_dir]); - break; - } - contents += subdir; - continue; - case DT_REG: - case DT_LNK: - break; - } - add_name(fullname, baselen + len); - contents++; - } - closedir(fdir); - - pop_exclude_per_directory(exclude_stk); - } - - return contents; -} - -static int cmp_name(const void *p1, const void *p2) -{ - const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1; - const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2; - - return cache_name_compare(e1->name, e1->len, - e2->name, e2->len); -} /* * Match a pathspec against a filename. The first "len" characters @@ -377,7 +72,7 @@ static int match(const char **spec, char *ps_matched, return 0; } -static void show_dir_entry(const char *tag, struct nond_on_fs *ent) +static void show_dir_entry(const char *tag, struct dir_entry *ent) { int len = prefix_len; int offset = prefix_offset; @@ -393,14 +88,14 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent) putchar(line_terminator); } -static void show_other_files(void) +static void show_other_files(struct dir_struct *dir) { int i; - for (i = 0; i < nr_dir; i++) { + for (i = 0; i < dir->nr; i++) { /* We should not have a matching entry, but we * may have an unmerged entry for this path. */ - struct nond_on_fs *ent = dir[i]; + struct dir_entry *ent = dir->entries[i]; int pos = cache_name_pos(ent->name, ent->len); struct cache_entry *ce; if (0 <= pos) @@ -416,11 +111,11 @@ static void show_other_files(void) } } -static void show_killed_files(void) +static void show_killed_files(struct dir_struct *dir) { int i; - for (i = 0; i < nr_dir; i++) { - struct nond_on_fs *ent = dir[i]; + for (i = 0; i < dir->nr; i++) { + struct dir_entry *ent = dir->entries[i]; char *cp, *sp; int pos, len, killed = 0; @@ -461,7 +156,7 @@ static void show_killed_files(void) } } if (killed) - show_dir_entry(tag_killed, dir[i]); + show_dir_entry(tag_killed, dir->entries[i]); } } @@ -512,7 +207,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) } } -static void show_files(void) +static void show_files(struct dir_struct *dir) { int i; @@ -521,39 +216,18 @@ static void show_files(void) const char *path = ".", *base = ""; int baselen = prefix_len; - if (baselen) { + if (baselen) path = base = prefix; - if (exclude_per_dir) { - char *p, *pp = xmalloc(baselen+1); - memcpy(pp, prefix, baselen+1); - p = pp; - while (1) { - char save = *p; - *p = 0; - push_exclude_per_directory(pp, p-pp); - *p++ = save; - if (!save) - break; - p = strchr(p, '/'); - if (p) - p++; - else - p = pp + baselen; - } - free(pp); - } - } - read_directory(path, base, baselen); - qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name); + read_directory(dir, path, base, baselen); if (show_others) - show_other_files(); + show_other_files(dir); if (show_killed) - show_killed_files(); + show_killed_files(dir); } if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - if (excluded(ce->name) != show_ignored) + if (excluded(dir, ce->name) != dir->show_ignored) continue; if (show_unmerged && !ce_stage(ce)) continue; @@ -565,7 +239,7 @@ static void show_files(void) struct cache_entry *ce = active_cache[i]; struct stat st; int err; - if (excluded(ce->name) != show_ignored) + if (excluded(dir, ce->name) != dir->show_ignored) continue; err = lstat(ce->name, &st); if (show_deleted && err) @@ -648,11 +322,13 @@ static const char ls_files_usage[] = "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] " "[--] [<file>]*"; -int main(int argc, const char **argv) +int cmd_ls_files(int argc, const char **argv, char** envp) { int i; int exc_given = 0; + struct dir_struct dir; + memset(&dir, 0, sizeof(dir)); prefix = setup_git_directory(); if (prefix) prefix_offset = strlen(prefix); @@ -697,7 +373,7 @@ int main(int argc, const char **argv) continue; } if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { - show_ignored = 1; + dir.show_ignored = 1; continue; } if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { @@ -709,11 +385,11 @@ int main(int argc, const char **argv) continue; } if (!strcmp(arg, "--directory")) { - show_other_directories = 1; + dir.show_other_directories = 1; continue; } if (!strcmp(arg, "--no-empty-directory")) { - hide_empty_directories = 1; + dir.hide_empty_directories = 1; continue; } if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) { @@ -726,27 +402,27 @@ int main(int argc, const char **argv) } if (!strcmp(arg, "-x") && i+1 < argc) { exc_given = 1; - add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]); + add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]); continue; } if (!strncmp(arg, "--exclude=", 10)) { exc_given = 1; - add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]); + add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]); continue; } if (!strcmp(arg, "-X") && i+1 < argc) { exc_given = 1; - add_excludes_from_file(argv[++i]); + add_excludes_from_file(&dir, argv[++i]); continue; } if (!strncmp(arg, "--exclude-from=", 15)) { exc_given = 1; - add_excludes_from_file(arg+15); + add_excludes_from_file(&dir, arg+15); continue; } if (!strncmp(arg, "--exclude-per-directory=", 24)) { exc_given = 1; - exclude_per_dir = arg + 24; + dir.exclude_per_dir = arg + 24; continue; } if (!strcmp(arg, "--full-name")) { @@ -788,7 +464,7 @@ int main(int argc, const char **argv) ps_matched = xcalloc(1, num); } - if (show_ignored && !exc_given) { + if (dir.show_ignored && !exc_given) { fprintf(stderr, "%s: --ignored needs some exclude pattern\n", argv[0]); exit(1); @@ -802,7 +478,7 @@ int main(int argc, const char **argv) read_cache(); if (prefix) prune_cache(); - show_files(); + show_files(&dir); if (ps_matched) { /* We need to make sure all pathspec matched otherwise diff --git a/ls-tree.c b/builtin-ls-tree.c index f2b3bc1231..48385d59f6 100644 --- a/ls-tree.c +++ b/builtin-ls-tree.c @@ -7,6 +7,7 @@ #include "blob.h" #include "tree.h" #include "quote.h" +#include "builtin.h" static int line_termination = '\n'; #define LS_RECURSIVE 1 @@ -15,7 +16,7 @@ static int line_termination = '\n'; #define LS_NAME_ONLY 8 static int abbrev = 0; static int ls_options = 0; -const char **pathspec; +static const char **pathspec; static int chomp_prefix = 0; static const char *prefix; @@ -84,7 +85,7 @@ static int show_tree(unsigned char *sha1, const char *base, int baselen, return retval; } -int main(int argc, const char **argv) +int cmd_ls_tree(int argc, const char **argv, char **envp) { unsigned char sha1[20]; struct tree *tree; diff --git a/read-tree.c b/builtin-read-tree.c index 82e2a9a4d3..ec40d013c4 100644 --- a/read-tree.c +++ b/builtin-read-tree.c @@ -11,6 +11,7 @@ #include "tree.h" #include <sys/time.h> #include <signal.h> +#include "builtin.h" static int reset = 0; static int merge = 0; @@ -763,7 +764,7 @@ static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] static struct cache_file cache_file; -int main(int argc, char **argv) +int cmd_read_tree(int argc, const char **argv, char **envp) { int i, newfd, stage = 0; unsigned char sha1[20]; diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 446802d377..5277d3cf12 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -85,7 +85,7 @@ static void show_commit(struct commit *commit) static char pretty_header[16384]; pretty_print_commit(revs.commit_format, commit, ~0, pretty_header, sizeof(pretty_header), - revs.abbrev); + revs.abbrev, NULL, NULL); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); @@ -103,6 +103,7 @@ static struct object_list **process_blob(struct blob *blob, if (obj->flags & (UNINTERESTING | SEEN)) return p; obj->flags |= SEEN; + name = strdup(name); return add_object(obj, p, path, name); } @@ -122,6 +123,7 @@ static struct object_list **process_tree(struct tree *tree, if (parse_tree(tree) < 0) die("bad tree object %s", sha1_to_hex(obj->sha1)); obj->flags |= SEEN; + name = strdup(name); p = add_object(obj, p, path, name); me.up = path; me.elem = name; @@ -134,6 +136,7 @@ static struct object_list **process_tree(struct tree *tree, p = process_tree(entry->item.tree, p, &me, entry->name); else p = process_blob(entry->item.blob, p, &me, entry->name); + free(entry->name); free(entry); entry = next; } diff --git a/builtin-rm.c b/builtin-rm.c new file mode 100644 index 0000000000..9014c61556 --- /dev/null +++ b/builtin-rm.c @@ -0,0 +1,150 @@ +/* + * "git rm" builtin command + * + * Copyright (C) Linus Torvalds 2006 + */ +#include "cache.h" +#include "builtin.h" +#include "dir.h" + +static const char builtin_rm_usage[] = +"git-rm [-n] [-v] [-f] <filepattern>..."; + +static struct { + int nr, alloc; + const char **name; +} list; + +static void add_list(const char *name) +{ + if (list.nr >= list.alloc) { + list.alloc = alloc_nr(list.alloc); + list.name = xrealloc(list.name, list.alloc * sizeof(const char *)); + } + list.name[list.nr++] = name; +} + +static int remove_file(const char *name) +{ + int ret; + char *slash; + + ret = unlink(name); + if (!ret && (slash = strrchr(name, '/'))) { + char *n = strdup(name); + do { + n[slash - name] = 0; + name = n; + } while (!rmdir(name) && (slash = strrchr(name, '/'))); + } + return ret; +} + +static struct cache_file cache_file; + +int cmd_rm(int argc, const char **argv, char **envp) +{ + int i, newfd; + int verbose = 0, show_only = 0, force = 0; + const char *prefix = setup_git_directory(); + const char **pathspec; + char *seen; + + git_config(git_default_config); + + newfd = hold_index_file_for_update(&cache_file, get_index_file()); + if (newfd < 0) + die("unable to create new index file"); + + if (read_cache() < 0) + die("index file corrupt"); + + for (i = 1 ; i < argc ; i++) { + const char *arg = argv[i]; + + if (*arg != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-n")) { + show_only = 1; + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } + if (!strcmp(arg, "-f")) { + force = 1; + continue; + } + die(builtin_rm_usage); + } + pathspec = get_pathspec(prefix, argv + i); + + seen = NULL; + if (pathspec) { + for (i = 0; pathspec[i] ; i++) + /* nothing */; + seen = xmalloc(i); + memset(seen, 0, i); + } + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) + continue; + add_list(ce->name); + } + + if (pathspec) { + const char *match; + for (i = 0; (match = pathspec[i]) != NULL ; i++) { + if (*match && !seen[i]) + die("pathspec '%s' did not match any files", match); + } + } + + /* + * First remove the names from the index: we won't commit + * the index unless all of them succeed + */ + for (i = 0; i < list.nr; i++) { + const char *path = list.name[i]; + printf("rm '%s'\n", path); + + if (remove_file_from_cache(path)) + die("git rm: unable to remove %s", path); + } + + /* + * Then, if we used "-f", remove the filenames from the + * workspace. If we fail to remove the first one, we + * abort the "git rm" (but once we've successfully removed + * any file at all, we'll go ahead and commit to it all: + * by then we've already committed ourself and can't fail + * in the middle) + */ + if (force) { + int removed = 0; + for (i = 0; i < list.nr; i++) { + const char *path = list.name[i]; + if (!remove_file(path)) { + removed = 1; + continue; + } + if (!removed) + die("git rm: %s: %s", path, strerror(errno)); + } + } + + if (active_cache_changed) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new index file"); + } + + return 0; +} diff --git a/show-branch.c b/builtin-show-branch.c index 268c57b180..2895140915 100644 --- a/show-branch.c +++ b/builtin-show-branch.c @@ -3,13 +3,14 @@ #include "cache.h" #include "commit.h" #include "refs.h" +#include "builtin.h" static const char show_branch_usage[] = "git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; static int default_num = 0; static int default_alloc = 0; -static char **default_arg = NULL; +static const char **default_arg = NULL; #define UNINTERESTING 01 @@ -259,7 +260,7 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->object.util; if (commit->object.parsed) pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - pretty, sizeof(pretty), 0); + pretty, sizeof(pretty), 0, NULL, NULL); else strcpy(pretty, "(unavailable)"); if (!strncmp(pretty, "[PATCH] ", 8)) @@ -548,7 +549,7 @@ static int omit_in_dense(struct commit *commit, struct commit **rev, int n) return 0; } -int main(int ac, char **av) +int cmd_show_branch(int ac, const char **av, char **envp) { struct commit *rev[MAX_REVS], *commit; struct commit_list *list = NULL, *seen = NULL; @@ -581,7 +582,7 @@ int main(int ac, char **av) } while (1 < ac && av[1][0] == '-') { - char *arg = av[1]; + const char *arg = av[1]; if (!strcmp(arg, "--")) { ac--; av++; break; diff --git a/tar-tree.c b/builtin-tar-tree.c index 33087366c3..2d5e06fb69 100644 --- a/tar-tree.c +++ b/builtin-tar-tree.c @@ -7,11 +7,14 @@ #include "commit.h" #include "strbuf.h" #include "tar.h" +#include "builtin.h" +#include "pkt-line.h" #define RECORDSIZE (512) #define BLOCKSIZE (RECORDSIZE * 20) -static const char tar_tree_usage[] = "git-tar-tree <key> [basedir]"; +static const char tar_tree_usage[] = +"git-tar-tree [--remote=<repo>] <ent> [basedir]"; static char block[BLOCKSIZE]; static unsigned long offset; @@ -301,7 +304,7 @@ static void traverse_tree(struct tree_desc *tree, struct strbuf *path) } } -int main(int argc, char **argv) +static int generate_tar(int argc, const char **argv, char** envp) { unsigned char sha1[20], tree_sha1[20]; struct commit *commit; @@ -348,3 +351,58 @@ int main(int argc, char **argv) free(current_path.buf); return 0; } + +static const char *exec = "git-upload-tar"; + +static int remote_tar(int argc, const char **argv) +{ + int fd[2], ret, len; + pid_t pid; + char buf[1024]; + char *url; + + if (argc < 3 || 4 < argc) + usage(tar_tree_usage); + + /* --remote=<repo> */ + url = strdup(argv[1]+9); + pid = git_connect(fd, url, exec); + if (pid < 0) + return 1; + + packet_write(fd[1], "want %s\n", argv[2]); + if (argv[3]) + packet_write(fd[1], "base %s\n", argv[3]); + packet_flush(fd[1]); + + len = packet_read_line(fd[0], buf, sizeof(buf)); + if (!len) + die("git-tar-tree: expected ACK/NAK, got EOF"); + if (buf[len-1] == '\n') + buf[--len] = 0; + if (strcmp(buf, "ACK")) { + if (5 < len && !strncmp(buf, "NACK ", 5)) + die("git-tar-tree: NACK %s", buf + 5); + die("git-tar-tree: protocol error"); + } + /* expect a flush */ + len = packet_read_line(fd[0], buf, sizeof(buf)); + if (len) + die("git-tar-tree: expected a flush"); + + /* Now, start reading from fd[0] and spit it out to stdout */ + ret = copy_fd(fd[0], 1); + close(fd[0]); + + ret |= finish_connect(pid); + return !!ret; +} + +int cmd_tar_tree(int argc, const char **argv, char **envp) +{ + if (argc < 2) + usage(tar_tree_usage); + if (!strncmp("--remote=", argv[1], 9)) + return remote_tar(argc, argv); + return generate_tar(argc, argv, envp); +} diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c new file mode 100644 index 0000000000..d4fa7b56c3 --- /dev/null +++ b/builtin-upload-tar.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2006 Junio C Hamano + */ +#include "cache.h" +#include "pkt-line.h" +#include "exec_cmd.h" +#include "builtin.h" + +static const char upload_tar_usage[] = "git-upload-tar <repo>"; + +static int nak(const char *reason) +{ + packet_write(1, "NACK %s\n", reason); + packet_flush(1); + return 1; +} + +int cmd_upload_tar(int argc, const char **argv, char **envp) +{ + int len; + const char *dir = argv[1]; + char buf[8192]; + unsigned char sha1[20]; + char *base = NULL; + char hex[41]; + int ac; + const char *av[4]; + + if (argc != 2) + usage(upload_tar_usage); + if (strlen(dir) < sizeof(buf)-1) + strcpy(buf, dir); /* enter-repo smudges its argument */ + else + packet_write(1, "NACK insanely long repository name %s\n", dir); + if (!enter_repo(buf, 0)) { + packet_write(1, "NACK not a git archive %s\n", dir); + packet_flush(1); + return 1; + } + + len = packet_read_line(0, buf, sizeof(buf)); + if (len < 5 || strncmp("want ", buf, 5)) + return nak("expected want"); + if (buf[len-1] == '\n') + buf[--len] = 0; + if (get_sha1(buf + 5, sha1)) + return nak("expected sha1"); + strcpy(hex, sha1_to_hex(sha1)); + + len = packet_read_line(0, buf, sizeof(buf)); + if (len) { + if (len < 5 || strncmp("base ", buf, 5)) + return nak("expected (optional) base"); + if (buf[len-1] == '\n') + buf[--len] = 0; + base = strdup(buf + 5); + len = packet_read_line(0, buf, sizeof(buf)); + } + if (len) + return nak("expected flush"); + + packet_write(1, "ACK\n"); + packet_flush(1); + + ac = 0; + av[ac++] = "tar-tree"; + av[ac++] = hex; + if (base) + av[ac++] = base; + av[ac++] = NULL; + execv_git_cmd(av); + /* should it return that is an error */ + return 1; +} @@ -20,12 +20,28 @@ extern int cmd_whatchanged(int argc, const char **argv, char **envp); extern int cmd_show(int argc, const char **argv, char **envp); extern int cmd_log(int argc, const char **argv, char **envp); extern int cmd_diff(int argc, const char **argv, char **envp); +extern int cmd_format_patch(int argc, const char **argv, char **envp); extern int cmd_count_objects(int argc, const char **argv, char **envp); extern int cmd_push(int argc, const char **argv, char **envp); extern int cmd_grep(int argc, const char **argv, char **envp); +extern int cmd_rm(int argc, const char **argv, char **envp); +extern int cmd_add(int argc, const char **argv, char **envp); extern int cmd_rev_list(int argc, const char **argv, char **envp); extern int cmd_check_ref_format(int argc, const char **argv, char **envp); extern int cmd_init_db(int argc, const char **argv, char **envp); +extern int cmd_tar_tree(int argc, const char **argv, char **envp); +extern int cmd_upload_tar(int argc, const char **argv, char **envp); +extern int cmd_ls_files(int argc, const char **argv, char **envp); +extern int cmd_ls_tree(int argc, const char **argv, char **envp); +extern int cmd_read_tree(int argc, const char **argv, char **envp); +extern int cmd_commit_tree(int argc, const char **argv, char **envp); +extern int cmd_apply(int argc, const char **argv, char **envp); +extern int cmd_show_branch(int argc, const char **argv, char **envp); +extern int cmd_diff_files(int argc, const char **argv, char **envp); +extern int cmd_diff_index(int argc, const char **argv, char **envp); +extern int cmd_diff_stages(int argc, const char **argv, char **envp); +extern int cmd_diff_tree(int argc, const char **argv, char **envp); +extern int cmd_cat_file(int argc, const char **argv, char **envp); #endif @@ -142,6 +142,7 @@ extern void verify_non_filename(const char *prefix, const char *name); /* Initialize and use the cache information */ extern int read_cache(void); extern int write_cache(int newfd, struct cache_entry **cache, int entries); +extern int verify_path(const char *path); extern int cache_name_pos(const char *name, int namelen); #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ @@ -154,6 +155,7 @@ extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int); extern int ce_modified(struct cache_entry *ce, struct stat *st, int); 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 read_pipe(int fd, char** return_buf, unsigned long* return_size); extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object); 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); @@ -257,6 +259,7 @@ extern void *read_object_with_reference(const unsigned char *sha1, unsigned char *sha1_ret); const char *show_date(unsigned long time, int timezone); +const char *show_rfc2822_date(unsigned long time, int timezone); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); @@ -30,6 +30,7 @@ struct cmt_fmt_map { { "raw", 1, CMIT_FMT_RAW }, { "medium", 1, CMIT_FMT_MEDIUM }, { "short", 1, CMIT_FMT_SHORT }, + { "email", 1, CMIT_FMT_EMAIL }, { "full", 5, CMIT_FMT_FULL }, { "fuller", 5, CMIT_FMT_FULLER }, { "oneline", 1, CMIT_FMT_ONELINE }, @@ -421,6 +422,46 @@ static int get_one_line(const char *msg, unsigned long len) return ret; } +static int is_rfc2047_special(char ch) +{ + return ((ch & 0x80) || (ch == '=') || (ch == '?') || (ch == '_')); +} + +static int add_rfc2047(char *buf, const char *line, int len) +{ + char *bp = buf; + int i, needquote; + static const char q_utf8[] = "=?utf-8?q?"; + + for (i = needquote = 0; !needquote && i < len; i++) { + unsigned ch = line[i]; + if (ch & 0x80) + needquote++; + if ((i + 1 < len) && + (ch == '=' && line[i+1] == '?')) + needquote++; + } + if (!needquote) + return sprintf(buf, "%.*s", len, line); + + memcpy(bp, q_utf8, sizeof(q_utf8)-1); + bp += sizeof(q_utf8)-1; + for (i = 0; i < len; i++) { + unsigned ch = line[i]; + if (is_rfc2047_special(ch)) { + sprintf(bp, "=%02X", ch); + bp += 3; + } + else if (ch == ' ') + *bp++ = '_'; + else + *bp++ = ch; + } + memcpy(bp, "?=", 2); + bp += 2; + return bp - buf; +} + static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line) { char *date; @@ -438,13 +479,35 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c time = strtoul(date, &date, 10); tz = strtol(date, NULL, 10); - ret = sprintf(buf, "%s: %.*s%.*s\n", what, - (fmt == CMIT_FMT_FULLER) ? 4 : 0, - filler, namelen, line); + if (fmt == CMIT_FMT_EMAIL) { + char *name_tail = strchr(line, '<'); + int display_name_length; + if (!name_tail) + return 0; + while (line < name_tail && isspace(name_tail[-1])) + name_tail--; + display_name_length = name_tail - line; + filler = ""; + strcpy(buf, "From: "); + ret = strlen(buf); + ret += add_rfc2047(buf + ret, line, display_name_length); + memcpy(buf + ret, name_tail, namelen - display_name_length); + ret += namelen - display_name_length; + buf[ret++] = '\n'; + } + else { + ret = sprintf(buf, "%s: %.*s%.*s\n", what, + (fmt == CMIT_FMT_FULLER) ? 4 : 0, + filler, namelen, line); + } switch (fmt) { case CMIT_FMT_MEDIUM: ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); break; + case CMIT_FMT_EMAIL: + ret += sprintf(buf + ret, "Date: %s\n", + show_rfc2822_date(time, tz)); + break; case CMIT_FMT_FULLER: ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); break; @@ -455,10 +518,12 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c return ret; } -static int is_empty_line(const char *line, int len) +static int is_empty_line(const char *line, int *len_p) { + int len = *len_p; while (len && isspace(line[len-1])) len--; + *len_p = len; return !len; } @@ -467,7 +532,8 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com struct commit_list *parent = commit->parents; int offset; - if ((fmt == CMIT_FMT_ONELINE) || !parent || !parent->next) + if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) || + !parent || !parent->next) return 0; offset = sprintf(buf, "Merge:"); @@ -486,13 +552,30 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev) +unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject) { int hdr = 1, body = 0; unsigned long offset = 0; - int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4; + int indent = 4; int parents_shown = 0; const char *msg = commit->buffer; + int plain_non_ascii = 0; + + if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) + indent = 0; + + /* After-subject is used to pass in Content-Type: multipart + * MIME header; in that case we do not have to do the + * plaintext content type even if the commit message has + * non 7-bit ASCII character. Otherwise, check if we need + * to say this is not a 7-bit ASCII. + */ + if (fmt == CMIT_FMT_EMAIL && !after_subject) { + int i; + for (i = 0; !plain_non_ascii && msg[i] && i < len; i++) + if (msg[i] & 0x80) + plain_non_ascii = 1; + } for (;;) { const char *line = msg; @@ -516,7 +599,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (hdr) { if (linelen == 1) { hdr = 0; - if (fmt != CMIT_FMT_ONELINE) + if ((fmt != CMIT_FMT_ONELINE) && !subject) buf[offset++] = '\n'; continue; } @@ -554,20 +637,47 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit continue; } - if (is_empty_line(line, linelen)) { + if (is_empty_line(line, &linelen)) { if (!body) continue; + if (subject) + continue; if (fmt == CMIT_FMT_SHORT) break; } else { body = 1; } - memset(buf + offset, ' ', indent); - memcpy(buf + offset + indent, line, linelen); - offset += linelen + indent; + if (subject) { + int slen = strlen(subject); + memcpy(buf + offset, subject, slen); + offset += slen; + offset += add_rfc2047(buf + offset, line, linelen); + } + else { + memset(buf + offset, ' ', indent); + memcpy(buf + offset + indent, line, linelen); + offset += linelen + indent; + } + buf[offset++] = '\n'; if (fmt == CMIT_FMT_ONELINE) break; + if (subject && plain_non_ascii) { + static const char header[] = + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n"; + memcpy(buf + offset, header, sizeof(header)-1); + offset += sizeof(header)-1; + } + if (after_subject) { + int slen = strlen(after_subject); + if (slen > space - offset - 1) + slen = space - offset - 1; + memcpy(buf + offset, after_subject, slen); + offset += slen; + after_subject = NULL; + } + subject = NULL; } while (offset && isspace(buf[offset-1])) offset--; @@ -45,12 +45,13 @@ enum cmit_fmt { CMIT_FMT_FULL, CMIT_FMT_FULLER, CMIT_FMT_ONELINE, + CMIT_FMT_EMAIL, CMIT_FMT_UNSPECIFIED, }; extern enum cmit_fmt get_commit_format(const char *arg); -extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev); +extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject); /** Removes the first commit from a list sorted by date, and adds all * of its parents. @@ -100,7 +100,7 @@ int path_match(const char *path, int nr, char **match) if (pathlen > len && path[pathlen - len - 1] != '/') continue; *s = 0; - return 1; + return (i + 1); } return 0; } diff --git a/contrib/git-svn/Makefile b/contrib/git-svn/Makefile index acedf7305e..48f60b3a0d 100644 --- a/contrib/git-svn/Makefile +++ b/contrib/git-svn/Makefile @@ -30,6 +30,7 @@ git-svn.html : git-svn.txt -f ../../Documentation/asciidoc.conf $< test: git-svn cd t && $(SHELL) ./t0000-contrib-git-svn.sh + cd t && $(SHELL) ./t0001-contrib-git-svn-props.sh clean: rm -f git-svn *.xml *.html *.1 diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index de13a96b8a..b3e0684c44 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -8,7 +8,7 @@ use vars qw/ $AUTHOR $VERSION $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $REV_DIR/; $AUTHOR = 'Eric Wong <normalperson@yhbt.net>'; -$VERSION = '1.0.0'; +$VERSION = '1.1.0-pre'; use Cwd qw/abs_path/; $GIT_DIR = abs_path($ENV{GIT_DIR} || '.git'); @@ -39,6 +39,10 @@ my $_svn_co_url_revs; my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'branch|b=s' => \@_branch_from, 'authors-file|A=s' => \$_authors ); + +# yes, 'native' sets "\n". Patches to fix this for non-*nix systems welcome: +my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" ); + my %cmd = ( fetch => [ \&fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -207,7 +211,7 @@ sub rebuild { push @svn_up, '--ignore-externals' unless $_no_ignore_ext; sys(@svn_up,"-r$newest_rev"); $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX; - git_addremove(); + index_changes(); exec('git-write-tree'); } waitpid $pid, 0; @@ -249,7 +253,7 @@ sub fetch { chdir $SVN_WC or croak $!; read_uuid(); $last_commit = git_commit($base, @parents); - assert_svn_wc_clean($base->{revision}, $last_commit); + assert_tree($last_commit); } else { chdir $SVN_WC or croak $!; read_uuid(); @@ -259,16 +263,20 @@ sub fetch { push @svn_up, '--ignore-externals' unless $_no_ignore_ext; my $last = $base; while (my $log_msg = next_log_entry($svn_log)) { - assert_svn_wc_clean($last->{revision}, $last_commit); + assert_tree($last_commit); if ($last->{revision} >= $log_msg->{revision}) { croak "Out of order: last >= current: ", "$last->{revision} >= $log_msg->{revision}\n"; } + # Revert is needed for cases like: + # https://svn.musicpd.org/Jamming/trunk (r166:167), but + # I can't seem to reproduce something like that on a test... + sys(qw/svn revert -R ./); + assert_svn_wc_clean($last->{revision}); sys(@svn_up,"-r$log_msg->{revision}"); $last_commit = git_commit($log_msg, $last_commit, @parents); $last = $log_msg; } - assert_svn_wc_clean($last->{revision}, $last_commit); unless (-e "$GIT_DIR/refs/heads/master") { sys(qw(git-update-ref refs/heads/master),$last_commit); } @@ -314,7 +322,6 @@ sub commit { $svn_current_rev = svn_commit_tree($svn_current_rev, $c); } print "Done committing ",scalar @revs," revisions to SVN\n"; - } sub show_ignore { @@ -367,13 +374,11 @@ sub setup_git_svn { } sub assert_svn_wc_clean { - my ($svn_rev, $treeish) = @_; + my ($svn_rev) = @_; croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/); - croak "$treeish is not a sha1!\n" unless ($treeish =~ /^$sha1$/o); my $lcr = svn_info('.')->{'Last Changed Rev'}; if ($svn_rev != $lcr) { print STDERR "Checking for copy-tree ... "; - # use my @diff = grep(/^Index: /,(safe_qx(qw(svn diff), "-r$lcr:$svn_rev"))); if (@diff) { @@ -389,7 +394,6 @@ sub assert_svn_wc_clean { print STDERR $_ foreach @status; croak; } - assert_tree($treeish); } sub assert_tree { @@ -416,7 +420,7 @@ sub assert_tree { unlink $tmpindex or croak $!; } $ENV{GIT_INDEX_FILE} = $tmpindex; - git_addremove(); + index_changes(1); chomp(my $tree = `git-write-tree`); if ($old_index) { $ENV{GIT_INDEX_FILE} = $old_index; @@ -426,6 +430,7 @@ sub assert_tree { if ($tree ne $expected) { croak "Tree mismatch, Got: $tree, Expected: $expected\n"; } + unlink $tmpindex; } sub parse_diff_tree { @@ -562,7 +567,8 @@ sub precommit_check { sub svn_checkout_tree { my ($svn_rev, $treeish) = @_; my $from = file_to_s("$REV_DIR/$svn_rev"); - assert_svn_wc_clean($svn_rev,$from); + assert_svn_wc_clean($svn_rev); + assert_tree($from); print "diff-tree $from $treeish\n"; my $pid = open my $diff_fh, '-|'; defined $pid or croak $!; @@ -852,13 +858,75 @@ sub svn_info { sub sys { system(@_) == 0 or croak $? } -sub git_addremove { - system( "git-diff-files --name-only -z ". - " | git-update-index --remove -z --stdin && ". - "git-ls-files -z --others ". - "'--exclude-from=$GIT_DIR/$GIT_SVN/info/exclude'". - " | git-update-index --add -z --stdin" - ) == 0 or croak $? +sub eol_cp { + my ($from, $to) = @_; + my $es = safe_qx(qw/svn propget svn:eol-style/, $to); + open my $rfd, '<', $from or croak $!; + binmode $rfd or croak $!; + open my $wfd, '>', $to or croak $!; + binmode $wfd or croak $!; + + my $eol = $EOL{$es} or undef; + if ($eol) { + print "$eol: $from => $to\n"; + } + my $buf; + while (1) { + my ($r, $w, $t); + defined($r = sysread($rfd, $buf, 4096)) or croak $!; + return unless $r; + $buf =~ s/(?:\015|\012|\015\012)/$eol/gs if $eol; + for ($w = 0; $w < $r; $w += $t) { + $t = syswrite($wfd, $buf, $r - $w, $w) or croak $!; + } + } +} + +sub do_update_index { + my ($z_cmd, $cmd, $no_text_base) = @_; + + my $z = open my $p, '-|'; + defined $z or croak $!; + unless ($z) { exec @$z_cmd or croak $! } + + my $pid = open my $ui, '|-'; + defined $pid or croak $!; + unless ($pid) { + exec('git-update-index',"--$cmd",'-z','--stdin') or croak $!; + } + local $/ = "\0"; + while (my $x = <$p>) { + chomp $x; + if (!$no_text_base && lstat $x && ! -l _ && + safe_qx(qw/svn propget svn:keywords/,$x)) { + my $mode = -x _ ? 0755 : 0644; + my ($v,$d,$f) = File::Spec->splitpath($x); + my $tb = File::Spec->catfile($d, '.svn', 'tmp', + 'text-base',"$f.svn-base"); + $tb =~ s#^/##; + unless (-f $tb) { + $tb = File::Spec->catfile($d, '.svn', + 'text-base',"$f.svn-base"); + $tb =~ s#^/##; + } + unlink $x or croak $!; + eol_cp($tb, $x); + chmod(($mode &~ umask), $x) or croak $!; + } + print $ui $x,"\0"; + } + close $ui or croak $!; +} + +sub index_changes { + my $no_text_base = shift; + do_update_index([qw/git-diff-files --name-only -z/], + 'remove', + $no_text_base); + do_update_index([qw/git-ls-files -z --others/, + "--exclude-from=$GIT_DIR/$GIT_SVN/info/exclude"], + 'add', + $no_text_base); } sub s_to_file { @@ -936,7 +1004,7 @@ sub git_commit { defined $pid or croak $!; if ($pid == 0) { $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX; - git_addremove(); + index_changes(); chomp(my $tree = `git-write-tree`); croak if $?; if (exists $tree_map{$tree}) { diff --git a/contrib/git-svn/t/lib-git-svn.sh b/contrib/git-svn/t/lib-git-svn.sh new file mode 100644 index 0000000000..a98e9d164d --- /dev/null +++ b/contrib/git-svn/t/lib-git-svn.sh @@ -0,0 +1,39 @@ +PATH=$PWD/../:$PATH +if test -d ../../../t +then + cd ../../../t +else + echo "Must be run in contrib/git-svn/t" >&2 + exit 1 +fi + +. ./test-lib.sh + +GIT_DIR=$PWD/.git +GIT_SVN_DIR=$GIT_DIR/git-svn +SVN_TREE=$GIT_SVN_DIR/tree + +svnadmin >/dev/null 2>&1 +if test $? != 1 +then + test_expect_success 'skipping contrib/git-svn test' : + test_done + exit +fi + +svn >/dev/null 2>&1 +if test $? != 1 +then + test_expect_success 'skipping contrib/git-svn test' : + test_done + exit +fi + +svnrepo=$PWD/svnrepo + +set -e + +svnadmin create $svnrepo +svnrepo="file://$svnrepo/test-git-svn" + + diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh index 80ad3573db..8b3a0d9029 100644 --- a/contrib/git-svn/t/t0000-contrib-git-svn.sh +++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh @@ -3,48 +3,10 @@ # Copyright (c) 2006 Eric Wong # - -PATH=$PWD/../:$PATH test_description='git-svn tests' -if test -d ../../../t -then - cd ../../../t -else - echo "Must be run in contrib/git-svn/t" >&2 - exit 1 -fi - -. ./test-lib.sh - -GIT_DIR=$PWD/.git -GIT_SVN_DIR=$GIT_DIR/git-svn -SVN_TREE=$GIT_SVN_DIR/tree - -svnadmin >/dev/null 2>&1 -if test $? != 1 -then - test_expect_success 'skipping contrib/git-svn test' : - test_done - exit -fi - -svn >/dev/null 2>&1 -if test $? != 1 -then - test_expect_success 'skipping contrib/git-svn test' : - test_done - exit -fi - -svnrepo=$PWD/svnrepo - -set -e - -svnadmin create $svnrepo -svnrepo="file://$svnrepo/test-git-svn" +. ./lib-git-svn.sh mkdir import - cd import echo foo > foo @@ -55,10 +17,9 @@ mkdir -p bar echo 'zzz' > bar/zzz echo '#!/bin/sh' > exec.sh chmod +x exec.sh -svn import -m 'import for git-svn' . $svnrepo >/dev/null +svn import -m 'import for git-svn' . "$svnrepo" >/dev/null cd .. - rm -rf import test_expect_success \ diff --git a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh new file mode 100644 index 0000000000..6fa7889e9a --- /dev/null +++ b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh @@ -0,0 +1,125 @@ +#!/bin/sh +# +# Copyright (c) 2006 Eric Wong +# + +test_description='git-svn property tests' +. ./lib-git-svn.sh + +mkdir import + +a_crlf= +a_lf= +a_cr= +a_ne_crlf= +a_ne_lf= +a_ne_cr= +a_empty= +a_empty_lf= +a_empty_cr= +a_empty_crlf= + +cd import + cat >> kw.c <<'' +/* Make it look like somebody copied a file from CVS into SVN: */ +/* $Id: kw.c,v 1.1.1.1 1994/03/06 00:00:00 eric Exp $ */ + + printf "Hello\r\nWorld\r\n" > crlf + a_crlf=`git-hash-object -w crlf` + printf "Hello\rWorld\r" > cr + a_cr=`git-hash-object -w cr` + printf "Hello\nWorld\n" > lf + a_lf=`git-hash-object -w lf` + + printf "Hello\r\nWorld" > ne_crlf + a_ne_crlf=`git-hash-object -w ne_crlf` + printf "Hello\nWorld" > ne_lf + a_ne_lf=`git-hash-object -w ne_lf` + printf "Hello\rWorld" > ne_cr + a_ne_cr=`git-hash-object -w ne_cr` + + touch empty + a_empty=`git-hash-object -w empty` + printf "\n" > empty_lf + a_empty_lf=`git-hash-object -w empty_lf` + printf "\r" > empty_cr + a_empty_cr=`git-hash-object -w empty_cr` + printf "\r\n" > empty_crlf + a_empty_crlf=`git-hash-object -w empty_crlf` + + svn import -m 'import for git-svn' . "$svnrepo" >/dev/null +cd .. + +rm -rf import +svn co "$svnrepo" test_wc + +cd test_wc + echo 'Greetings' >> kw.c + svn commit -m 'Not yet an $Id$' + svn up + + echo 'Hello world' >> kw.c + svn commit -m 'Modified file, but still not yet an $Id$' + svn up + + svn propset svn:keywords Id kw.c + svn commit -m 'Propset $Id$' + svn up +cd .. + +git-svn init "$svnrepo" +git-svn fetch + +git checkout -b mybranch remotes/git-svn +echo 'Hi again' >> kw.c +name='test svn:keywords ignoring' + +git commit -a -m "$name" +git-svn commit remotes/git-svn..mybranch +git pull . remotes/git-svn + +expect='/* $Id$ */' +got="`sed -ne 2p kw.c`" +test_expect_success 'raw $Id$ found in kw.c' "test '$expect' = '$got'" + +cd test_wc + svn propset svn:eol-style CR empty + svn propset svn:eol-style CR crlf + svn propset svn:eol-style CR ne_crlf + svn commit -m 'propset CR on crlf files' + svn up +cd .. + +git-svn fetch +git pull . remotes/git-svn + +svn co "$svnrepo" new_wc +for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf +do + test_expect_success "Comparing $i" "cmp $i new_wc/$i" +done + + +cd test_wc + printf '$Id$\rHello\rWorld\r' > cr + printf '$Id$\rHello\rWorld' > ne_cr + a_cr=`printf '$Id$\r\nHello\r\nWorld\r\n' | git-hash-object --stdin` + a_ne_cr=`printf '$Id$\r\nHello\r\nWorld' | git-hash-object --stdin` + svn propset svn:eol-style CRLF cr + svn propset svn:eol-style CRLF ne_cr + svn propset svn:keywords Id cr + svn propset svn:keywords Id ne_cr + svn commit -m 'propset CRLF on cr files' + svn up +cd .. + +git-svn fetch +git pull . remotes/git-svn + +b_cr="`git-hash-object cr`" +b_ne_cr="`git-hash-object ne_cr`" + +test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'" +test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'" + +test_done diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview index 781badbc5b..b836047cf3 100755 --- a/contrib/gitview/gitview +++ b/contrib/gitview/gitview @@ -425,7 +425,7 @@ class DiffWindow: class GitView: """ This is the main class """ - version = "0.7" + version = "0.8" def __init__(self, with_diff=0): self.with_diff = with_diff @@ -449,8 +449,17 @@ class GitView: self.accel_group = gtk.AccelGroup() self.window.add_accel_group(self.accel_group) + self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh); - self.construct() + self.window.add(self.construct()) + + def refresh(self, widget, event=None, *arguments, **keywords): + self.get_encoding() + self.get_bt_sha1() + Commit.children_sha1 = {} + self.set_branch(sys.argv[without_diff:]) + self.window.show() + return True def get_bt_sha1(self): """ Update the bt_sha1 dictionary with the @@ -500,9 +509,9 @@ class GitView: menu_bar.show() vbox.pack_start(menu_bar, expand=False, fill=True) vbox.pack_start(paned, expand=True, fill=True) - self.window.add(vbox) paned.show() vbox.show() + return vbox def construct_top(self): @@ -974,10 +983,15 @@ class GitView: try: self.treeview.set_cursor(self.index[revid]) except KeyError: - print "Revision %s not present in the list" % revid + dialog = gtk.MessageDialog(parent=None, flags=0, + type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE, + message_format=None) + dialog.set_markup("Revision <b>%s</b> not present in the list" % revid) # revid == 0 is the parent of the first commit if (revid != 0 ): - print "Try running gitview without any options" + dialog.format_secondary_text("Try running gitview without any options") + dialog.run() + dialog.destroy() self.treeview.grab_focus() @@ -987,8 +1001,8 @@ class GitView: window.set_diff(commit_sha1, parent_sha1, encoding) self.treeview.grab_focus() +without_diff = 0 if __name__ == "__main__": - without_diff = 0 if (len(sys.argv) > 1 ): if (sys.argv[1] == "--without-diff"): diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt index fcf759c307..e3bc4f46c2 100644 --- a/contrib/gitview/gitview.txt +++ b/contrib/gitview/gitview.txt @@ -25,6 +25,9 @@ OPTIONS <args> All the valid option for git-rev-list(1) + Key Bindings: + F5: + To reread references. EXAMPLES ------ @@ -33,6 +36,5 @@ EXAMPLES or drivers/scsi subdirectories gitview --since=2.weeks.ago - Show the changes during the last two weeks + Show the changes during the last two weeks - @@ -42,18 +42,24 @@ static const char *weekday_names[] = { * thing, which means that tz -0100 is passed in as the integer -100, * even though it means "sixty minutes off" */ -const char *show_date(unsigned long time, int tz) +static struct tm *time_to_tm(unsigned long time, int tz) { - struct tm *tm; time_t t; - static char timebuf[200]; int minutes; minutes = tz < 0 ? -tz : tz; minutes = (minutes / 100)*60 + (minutes % 100); minutes = tz < 0 ? -minutes : minutes; t = time + minutes * 60; - tm = gmtime(&t); + return gmtime(&t); +} + +const char *show_date(unsigned long time, int tz) +{ + struct tm *tm; + static char timebuf[200]; + + tm = time_to_tm(time, tz); if (!tm) return NULL; sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d", @@ -65,6 +71,21 @@ const char *show_date(unsigned long time, int tz) return timebuf; } +const char *show_rfc2822_date(unsigned long time, int tz) +{ + struct tm *tm; + static char timebuf[200]; + + tm = time_to_tm(time, tz); + if (!tm) + return NULL; + sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d", + weekday_names[tm->tm_wday], tm->tm_mday, + month_names[tm->tm_mon], tm->tm_year + 1900, + tm->tm_hour, tm->tm_min, tm->tm_sec, tz); + return timebuf; +} + /* * Check these. And note how it doesn't do the summer-time conversion. * @@ -237,7 +237,7 @@ static char *pprint_rename(const char *a, const char *b) if (a_midlen < 0) a_midlen = 0; if (b_midlen < 0) b_midlen = 0; - name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7); + name = xmalloc(pfx_length + a_midlen + b_midlen + sfx_length + 7); sprintf(name, "%.*s{%.*s => %.*s}%s", pfx_length, a, a_midlen, a + pfx_length, @@ -299,6 +299,7 @@ static void diffstat_consume(void *priv, char *line, unsigned long len) static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; static const char minuses[]= "----------------------------------------------------------------------"; +const char mime_boundary_leader[] = "------------"; static void show_stats(struct diffstat_t* data) { @@ -1303,6 +1304,14 @@ int diff_setup_done(struct diff_options *options) (options->output_format == DIFF_FORMAT_CHECKDIFF)) options->recursive = 1; + /* + * These combinations do not make sense. + */ + if (options->output_format == DIFF_FORMAT_RAW) + options->with_raw = 0; + if (options->output_format == DIFF_FORMAT_DIFFSTAT) + options->with_stat = 0; + if (options->detect_rename && options->rename_limit < 0) options->rename_limit = diff_rename_limit_default; if (options->setup & DIFF_SETUP_USE_CACHE) { @@ -1980,7 +1989,13 @@ void diff_flush(struct diff_options *options) show_stats(diffstat); free(diffstat); diffstat = NULL; - putchar(options->line_termination); + if (options->summary) + for (i = 0; i < q->nr; i++) + diff_summary(q->queue[i]); + if (options->stat_sep) + fputs(options->stat_sep, stdout); + else + putchar(options->line_termination); } for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -1993,7 +2008,7 @@ void diff_flush(struct diff_options *options) } for (i = 0; i < q->nr; i++) { - if (options->summary) + if (diffstat && options->summary) diff_summary(q->queue[i]); diff_free_filepair(q->queue[i]); } @@ -44,6 +44,7 @@ struct diff_options { int rename_limit; int setup; int abbrev; + const char *stat_sep; int nr_paths; const char **paths; @@ -52,6 +53,8 @@ struct diff_options { add_remove_fn_t add_remove; }; +extern const char mime_boundary_leader[]; + extern void diff_tree_setup_paths(const char **paths, struct diff_options *); extern void diff_tree_release_paths(struct diff_options *); extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2, @@ -0,0 +1,401 @@ +/* + * This handles recursive filename detection with exclude + * files, index knowledge etc.. + * + * Copyright (C) Linus Torvalds, 2005-2006 + * Junio Hamano, 2005-2006 + */ +#include <dirent.h> +#include <fnmatch.h> + +#include "cache.h" +#include "dir.h" + +int common_prefix(const char **pathspec) +{ + const char *path, *slash, *next; + int prefix; + + if (!pathspec) + return 0; + + path = *pathspec; + slash = strrchr(path, '/'); + if (!slash) + return 0; + + prefix = slash - path + 1; + while ((next = *++pathspec) != NULL) { + int len = strlen(next); + if (len >= prefix && !memcmp(path, next, len)) + continue; + for (;;) { + if (!len) + return 0; + if (next[--len] != '/') + continue; + if (memcmp(path, next, len+1)) + continue; + prefix = len + 1; + break; + } + } + return prefix; +} + +static int match_one(const char *match, const char *name, int namelen) +{ + int matchlen; + + /* If the match was just the prefix, we matched */ + matchlen = strlen(match); + if (!matchlen) + return 1; + + /* + * If we don't match the matchstring exactly, + * we need to match by fnmatch + */ + if (strncmp(match, name, matchlen)) + return !fnmatch(match, name, 0); + + /* + * If we did match the string exactly, we still + * need to make sure that it happened on a path + * component boundary (ie either the last character + * of the match was '/', or the next character of + * the name was '/' or the terminating NUL. + */ + return match[matchlen-1] == '/' || + name[matchlen] == '/' || + !name[matchlen]; +} + +int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen) +{ + int retval; + const char *match; + + name += prefix; + namelen -= prefix; + + for (retval = 0; (match = *pathspec++) != NULL; seen++) { + if (retval & *seen) + continue; + match += prefix; + if (match_one(match, name, namelen)) { + retval = 1; + *seen = 1; + } + } + return retval; +} + +void add_exclude(const char *string, const char *base, + int baselen, struct exclude_list *which) +{ + struct exclude *x = xmalloc(sizeof (*x)); + + x->pattern = string; + x->base = base; + x->baselen = baselen; + if (which->nr == which->alloc) { + which->alloc = alloc_nr(which->alloc); + which->excludes = realloc(which->excludes, + which->alloc * sizeof(x)); + } + which->excludes[which->nr++] = x; +} + +static int add_excludes_from_file_1(const char *fname, + const char *base, + int baselen, + struct exclude_list *which) +{ + int fd, i; + long size; + char *buf, *entry; + + fd = open(fname, O_RDONLY); + if (fd < 0) + goto err; + size = lseek(fd, 0, SEEK_END); + if (size < 0) + goto err; + lseek(fd, 0, SEEK_SET); + if (size == 0) { + close(fd); + return 0; + } + buf = xmalloc(size+1); + if (read(fd, buf, size) != size) + goto err; + close(fd); + + buf[size++] = '\n'; + entry = buf; + for (i = 0; i < size; i++) { + if (buf[i] == '\n') { + if (entry != buf + i && entry[0] != '#') { + buf[i - (i && buf[i-1] == '\r')] = 0; + add_exclude(entry, base, baselen, which); + } + entry = buf + i + 1; + } + } + return 0; + + err: + if (0 <= fd) + close(fd); + return -1; +} + +void add_excludes_from_file(struct dir_struct *dir, const char *fname) +{ + if (add_excludes_from_file_1(fname, "", 0, + &dir->exclude_list[EXC_FILE]) < 0) + die("cannot use %s as an exclude file", fname); +} + +static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) +{ + char exclude_file[PATH_MAX]; + struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; + int current_nr = el->nr; + + if (dir->exclude_per_dir) { + memcpy(exclude_file, base, baselen); + strcpy(exclude_file + baselen, dir->exclude_per_dir); + add_excludes_from_file_1(exclude_file, base, baselen, el); + } + return current_nr; +} + +static void pop_exclude_per_directory(struct dir_struct *dir, int stk) +{ + struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; + + while (stk < el->nr) + free(el->excludes[--el->nr]); +} + +/* Scan the list and let the last match determines the fate. + * Return 1 for exclude, 0 for include and -1 for undecided. + */ +static int excluded_1(const char *pathname, + int pathlen, + struct exclude_list *el) +{ + int i; + + if (el->nr) { + for (i = el->nr - 1; 0 <= i; i--) { + struct exclude *x = el->excludes[i]; + const char *exclude = x->pattern; + int to_exclude = 1; + + if (*exclude == '!') { + to_exclude = 0; + exclude++; + } + + if (!strchr(exclude, '/')) { + /* match basename */ + const char *basename = strrchr(pathname, '/'); + basename = (basename) ? basename+1 : pathname; + if (fnmatch(exclude, basename, 0) == 0) + return to_exclude; + } + else { + /* match with FNM_PATHNAME: + * exclude has base (baselen long) implicitly + * in front of it. + */ + int baselen = x->baselen; + if (*exclude == '/') + exclude++; + + if (pathlen < baselen || + (baselen && pathname[baselen-1] != '/') || + strncmp(pathname, x->base, baselen)) + continue; + + if (fnmatch(exclude, pathname+baselen, + FNM_PATHNAME) == 0) + return to_exclude; + } + } + } + return -1; /* undecided */ +} + +int excluded(struct dir_struct *dir, const char *pathname) +{ + int pathlen = strlen(pathname); + int st; + + for (st = EXC_CMDL; st <= EXC_FILE; st++) { + switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) { + case 0: + return 0; + case 1: + return 1; + } + } + return 0; +} + +static void add_name(struct dir_struct *dir, const char *pathname, int len) +{ + struct dir_entry *ent; + + if (cache_name_pos(pathname, len) >= 0) + return; + + if (dir->nr == dir->alloc) { + int alloc = alloc_nr(dir->alloc); + dir->alloc = alloc; + dir->entries = xrealloc(dir->entries, alloc*sizeof(ent)); + } + ent = xmalloc(sizeof(*ent) + len + 1); + ent->len = len; + memcpy(ent->name, pathname, len); + ent->name[len] = 0; + dir->entries[dir->nr++] = ent; +} + +static int dir_exists(const char *dirname, int len) +{ + int pos = cache_name_pos(dirname, len); + if (pos >= 0) + return 1; + pos = -pos-1; + if (pos >= active_nr) /* can't */ + return 0; + return !strncmp(active_cache[pos]->name, dirname, len); +} + +/* + * Read a directory tree. We currently ignore anything but + * directories, regular files and symlinks. That's because git + * doesn't handle them at all yet. Maybe that will change some + * day. + * + * Also, we ignore the name ".git" (even if it is not a directory). + * That likely will not change. + */ +static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen) +{ + DIR *fdir = opendir(path); + int contents = 0; + + if (fdir) { + int exclude_stk; + struct dirent *de; + char fullname[MAXPATHLEN + 1]; + memcpy(fullname, base, baselen); + + exclude_stk = push_exclude_per_directory(dir, base, baselen); + + while ((de = readdir(fdir)) != NULL) { + int len; + + if ((de->d_name[0] == '.') && + (de->d_name[1] == 0 || + !strcmp(de->d_name + 1, ".") || + !strcmp(de->d_name + 1, "git"))) + continue; + len = strlen(de->d_name); + memcpy(fullname + baselen, de->d_name, len+1); + if (excluded(dir, fullname) != dir->show_ignored) { + if (!dir->show_ignored || DTYPE(de) != DT_DIR) { + continue; + } + } + + switch (DTYPE(de)) { + struct stat st; + int subdir, rewind_base; + default: + continue; + case DT_UNKNOWN: + if (lstat(fullname, &st)) + continue; + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + break; + if (!S_ISDIR(st.st_mode)) + continue; + /* fallthrough */ + case DT_DIR: + memcpy(fullname + baselen + len, "/", 2); + len++; + rewind_base = dir->nr; + subdir = read_directory_recursive(dir, fullname, fullname, + baselen + len); + if (dir->show_other_directories && + (subdir || !dir->hide_empty_directories) && + !dir_exists(fullname, baselen + len)) { + // Rewind the read subdirectory + while (dir->nr > rewind_base) + free(dir->entries[--dir->nr]); + break; + } + contents += subdir; + continue; + case DT_REG: + case DT_LNK: + break; + } + add_name(dir, fullname, baselen + len); + contents++; + } + closedir(fdir); + + pop_exclude_per_directory(dir, exclude_stk); + } + + return contents; +} + +static int cmp_name(const void *p1, const void *p2) +{ + const struct dir_entry *e1 = *(const struct dir_entry **)p1; + const struct dir_entry *e2 = *(const struct dir_entry **)p2; + + return cache_name_compare(e1->name, e1->len, + e2->name, e2->len); +} + +int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen) +{ + /* + * Make sure to do the per-directory exclude for all the + * directories leading up to our base. + */ + if (baselen) { + if (dir->exclude_per_dir) { + char *p, *pp = xmalloc(baselen+1); + memcpy(pp, base, baselen+1); + p = pp; + while (1) { + char save = *p; + *p = 0; + push_exclude_per_directory(dir, pp, p-pp); + *p++ = save; + if (!save) + break; + p = strchr(p, '/'); + if (p) + p++; + else + p = pp + baselen; + } + free(pp); + } + } + + read_directory_recursive(dir, path, base, baselen); + qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); + return dir->nr; +} @@ -0,0 +1,51 @@ +#ifndef DIR_H +#define DIR_H + +/* + * We maintain three exclude pattern lists: + * EXC_CMDL lists patterns explicitly given on the command line. + * EXC_DIRS lists patterns obtained from per-directory ignore files. + * EXC_FILE lists patterns from fallback ignore files. + */ +#define EXC_CMDL 0 +#define EXC_DIRS 1 +#define EXC_FILE 2 + + +struct dir_entry { + int len; + char name[FLEX_ARRAY]; /* more */ +}; + +struct exclude_list { + int nr; + int alloc; + struct exclude { + const char *pattern; + const char *base; + int baselen; + } **excludes; +}; + +struct dir_struct { + int nr, alloc; + unsigned int show_ignored:1, + show_other_directories:1, + hide_empty_directories:1; + struct dir_entry **entries; + + /* Exclude info */ + const char *exclude_per_dir; + struct exclude_list exclude_list[3]; +}; + +extern int common_prefix(const char **pathspec); +extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); + +extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen); +extern int excluded(struct dir_struct *, const char *); +extern void add_excludes_from_file(struct dir_struct *, const char *fname); +extern void add_exclude(const char *string, const char *base, + int baselen, struct exclude_list *which); + +#endif diff --git a/fetch-pack.c b/fetch-pack.c index a3bcad016f..8daa93d024 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -262,22 +262,58 @@ static void mark_recent_complete_commits(unsigned long cutoff) static void filter_refs(struct ref **refs, int nr_match, char **match) { - struct ref *prev, *current, *next; - - for (prev = NULL, current = *refs; current; current = next) { - next = current->next; - if ((!memcmp(current->name, "refs/", 5) && - check_ref_format(current->name + 5)) || - (!fetch_all && - !path_match(current->name, nr_match, match))) { - if (prev == NULL) - *refs = next; - else - prev->next = next; - free(current); - } else - prev = current; + struct ref **return_refs; + struct ref *newlist = NULL; + struct ref **newtail = &newlist; + struct ref *ref, *next; + struct ref *fastarray[32]; + + if (nr_match && !fetch_all) { + if (ARRAY_SIZE(fastarray) < nr_match) + return_refs = xcalloc(nr_match, sizeof(struct ref *)); + else { + return_refs = fastarray; + memset(return_refs, 0, sizeof(struct ref *) * nr_match); + } + } + else + return_refs = NULL; + + for (ref = *refs; ref; ref = next) { + next = ref->next; + if (!memcmp(ref->name, "refs/", 5) && + check_ref_format(ref->name + 5)) + ; /* trash */ + else if (fetch_all) { + *newtail = ref; + ref->next = NULL; + newtail = &ref->next; + continue; + } + else { + int order = path_match(ref->name, nr_match, match); + if (order) { + return_refs[order-1] = ref; + continue; /* we will link it later */ + } + } + free(ref); + } + + if (!fetch_all) { + int i; + for (i = 0; i < nr_match; i++) { + ref = return_refs[i]; + if (ref) { + *newtail = ref; + ref->next = NULL; + newtail = &ref->next; + } + } + if (return_refs != fastarray) + free(return_refs); } + *refs = newlist; } static int everything_local(struct ref **refs, int nr_match, char **match) @@ -9,8 +9,6 @@ const char *write_ref = NULL; -const unsigned char *current_ref = NULL; - int get_tree = 0; int get_history = 0; int get_all = 0; @@ -205,19 +203,12 @@ static int mark_complete(const char *path, const unsigned char *sha1) int pull(char *target) { unsigned char sha1[20]; - int fd = -1; save_commit_buffer = 0; track_object_refs = 0; - if (write_ref && current_ref) { - fd = lock_ref_sha1(write_ref, current_ref); - if (fd < 0) - return -1; - } - if (!get_recover) { + if (!get_recover) for_each_ref(mark_complete); - } if (interpret_target(target, sha1)) return error("Could not interpret %s as something to pull", @@ -227,12 +218,7 @@ int pull(char *target) if (loop()) return -1; - if (write_ref) { - if (current_ref) { - write_ref_sha1(write_ref, fd, sha1); - } else { - write_ref_sha1_unlocked(write_ref, sha1); - } - } + if (write_ref) + write_ref_sha1_unlocked(write_ref, sha1); return 0; } @@ -25,9 +25,6 @@ extern int fetch_ref(char *ref, unsigned char *sha1); /* If set, the ref filename to write the target value to. */ extern const char *write_ref; -/* If set, the hash that the current value of write_ref must be. */ -extern const unsigned char *current_ref; - /* Set to fetch the target tree. */ extern int get_tree; diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index 6c59dbd68f..ec1eda20de 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -37,7 +37,6 @@ show-branch status tag verify-tag -whatchanged EOF while read cmd do diff --git a/git-add.sh b/git-add.sh deleted file mode 100755 index d6a4bc7d09..0000000000 --- a/git-add.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -USAGE='[-n] [-v] <file>...' -SUBDIRECTORY_OK='Yes' -. git-sh-setup - -show_only= -verbose= -while : ; do - case "$1" in - -n) - show_only=true - ;; - -v) - verbose=--verbose - ;; - --) - shift - break - ;; - -*) - usage - ;; - *) - break - ;; - esac - shift -done - -# Check misspelled pathspec -case "$#" in -0) ;; -*) - git-ls-files --error-unmatch --others --cached -- "$@" >/dev/null || { - echo >&2 "Maybe you misspelled it?" - exit 1 - } - ;; -esac - -if test -f "$GIT_DIR/info/exclude" -then - git-ls-files -z \ - --exclude-from="$GIT_DIR/info/exclude" \ - --others --exclude-per-directory=.gitignore -- "$@" -else - git-ls-files -z \ - --others --exclude-per-directory=.gitignore -- "$@" -fi | -case "$show_only" in -true) - xargs -0 echo ;; -*) - git-update-index --add $verbose -z --stdin ;; -esac diff --git a/git-clone.sh b/git-clone.sh index 227245c865..de59904d56 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -9,7 +9,7 @@ unset CDPATH usage() { - echo >&2 "Usage: $0 [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]" + echo >&2 "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]" exit 1 } @@ -102,6 +102,7 @@ quiet= local=no use_local=no local_shared=no +unset template no_checkout= upload_pack= bare= @@ -120,6 +121,11 @@ while *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;; *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared) local_shared=yes; use_local=yes ;; + 1,--template) usage ;; + *,--template) + shift; template="--template=$1" ;; + *,--template=*) + template="$1" ;; *,-q|*,--quiet) quiet=-q ;; *,--use-separate-remote) use_separate_remote=t ;; @@ -199,11 +205,11 @@ dir="$2" [ -e "$dir" ] && echo "$dir already exists." && usage mkdir -p "$dir" && D=$(cd "$dir" && pwd) && -trap 'err=$?; cd ..; rm -r "$D"; exit $err' exit +trap 'err=$?; cd ..; rm -r "$D"; exit $err' 0 case "$bare" in yes) GIT_DIR="$D" ;; *) GIT_DIR="$D/.git" ;; -esac && export GIT_DIR && git-init-db || usage +esac && export GIT_DIR && git-init-db ${template+"$template"} || usage case "$bare" in yes) GIT_DIR="$D" ;; @@ -407,5 +413,5 @@ Pull: refs/heads/$head_points_at:$origin_track" && fi rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD" -trap - exit +trap - 0 diff --git a/git-commit.sh b/git-commit.sh index 6ef1a9dedc..1983d45828 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Linus Torvalds # Copyright (c) 2006 Junio C Hamano -USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>) [--amend] [-e] [--author <author>] [[-i | -o] <path>...]' +USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-u] [--amend] [-e] [--author <author>] [[-i | -o] <path>...]' SUBDIRECTORY_OK=Yes . git-sh-setup @@ -134,13 +134,17 @@ run_status () { report "Changed but not updated" \ "use git-update-index to mark for commit" + option="" + if test -z "$untracked_files"; then + option="--directory --no-empty-directory" + fi if test -f "$GIT_DIR/info/exclude" then - git-ls-files -z --others --directory \ + git-ls-files -z --others $option \ --exclude-from="$GIT_DIR/info/exclude" \ --exclude-per-directory=.gitignore else - git-ls-files -z --others --directory \ + git-ls-files -z --others $option \ --exclude-per-directory=.gitignore fi | perl -e '$/ = "\0"; @@ -203,6 +207,7 @@ verbose= signoff= force_author= only_include_assumed= +untracked_files= while case "$#" in 0) break;; esac do case "$1" in @@ -340,6 +345,12 @@ do verbose=t shift ;; + -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|--untracked|\ + --untracked-|--untracked-f|--untracked-fi|--untracked-fil|--untracked-file|\ + --untracked-files) + untracked_files=t + shift + ;; --) shift break @@ -615,6 +626,9 @@ fi if test -z "$no_edit" then { + echo "" + echo "# Please enter the commit message for your changes." + echo "# (Comment lines starting with '#' will not be included)" test -z "$only_include_assumed" || echo "$only_include_assumed" run_status } >>"$GIT_DIR"/COMMIT_EDITMSG diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index f994443c6f..57088c3f0b 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -1,10 +1,16 @@ #!/usr/bin/perl -w +# Known limitations: +# - cannot add or remove binary files +# - does not propagate permissions +# - tells "ready for commit" even when things could not be completed +# (eg addition of a binary file) + use strict; use Getopt::Std; use File::Temp qw(tempdir); use Data::Dumper; -use File::Basename qw(basename); +use File::Basename qw(basename dirname); unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ die "GIT_DIR is not defined or is unreadable"; @@ -84,7 +90,7 @@ close MSG; `git-cat-file commit $commit | sed -e '1,/^\$/d' >> .msg`; $? && die "Error extracting the commit message"; -my (@afiles, @dfiles, @mfiles); +my (@afiles, @dfiles, @mfiles, @dirs); my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit); #print @files; $? && die "Error in git-diff-tree"; @@ -92,7 +98,14 @@ foreach my $f (@files) { chomp $f; my @fields = split(m!\s+!, $f); if ($fields[4] eq 'A') { - push @afiles, $fields[5]; + my $path = $fields[5]; + push @afiles, $path; + # add any needed parent directories + $path = dirname $path; + while (!-d $path and ! grep { $_ eq $path } @dirs) { + unshift @dirs, $path; + $path = dirname $path; + } } if ($fields[4] eq 'M') { push @mfiles, $fields[5]; @@ -107,13 +120,21 @@ undef @files; # don't need it anymore # check that the files are clean and up to date according to cvs my $dirty; +foreach my $d (@dirs) { + if (-e $d) { + $dirty = 1; + warn "$d exists and is not a directory!\n"; + } +} foreach my $f (@afiles) { # This should return only one value my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; - unless ($status[0] =~ m/Status: Unknown$/) { + if (-d dirname $f and $status[0] !~ m/Status: Unknown$/ + and $status[0] !~ m/^File: no file /) { $dirty = 1; warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n"; + warn "Status was: $status\n"; } } foreach my $f (@mfiles, @dfiles) { @@ -139,6 +160,19 @@ if ($dirty) { ### +print "Creating new directories\n"; +foreach my $d (@dirs) { + unless (mkdir $d) { + warn "Could not mkdir $d: $!"; + $dirty = 1; + } + `cvs add $d`; + if ($?) { + $dirty = 1; + warn "Failed to cvs add directory $d -- you may need to do it manually"; + } +} + print "'Patching' binary files\n"; my @bfiles = grep(m/^Binary/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit)); @@ -151,7 +185,7 @@ foreach my $f (@bfiles) { my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; chomp $blob; `git-cat-file blob $blob > $tmpdir/blob`; - if (system('cmp', '-q', $f, "$tmpdir/blob")) { + if (system('cmp', '-s', $f, "$tmpdir/blob")) { warn "Binary file $f in CVS does not match parent.\n"; $dirty = 1; next; diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 8c707f2c66..76f6246a31 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -23,13 +23,13 @@ use File::Basename qw(basename dirname); use Time::Local; use IO::Socket; use IO::Pipe; -use POSIX qw(strftime dup2); +use POSIX qw(strftime dup2 ENOENT); use IPC::Open2; $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; -our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S); +our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L); my (%conv_author_name, %conv_author_email); sub usage() { @@ -85,7 +85,7 @@ sub write_author_info($) { close ($f); } -getopts("hivmkuo:d:p:C:z:s:M:P:A:S:") or usage(); +getopts("hivmkuo:d:p:C:z:s:M:P:A:S:L:") or usage(); usage if $opt_h; @ARGV <= 1 or usage(); @@ -315,15 +315,7 @@ sub _line { chomp $cnt; die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/; $line=""; - $res=0; - while($cnt) { - my $buf; - my $num = $self->{'socketi'}->read($buf,$cnt); - die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0; - print $fh $buf; - $res += $num; - $cnt -= $num; - } + $res = $self->_fetchfile($fh, $cnt); } elsif($line =~ s/^ //) { print $fh $line; $res += length($line); @@ -335,14 +327,7 @@ sub _line { chomp $cnt; die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1; $line=""; - while($cnt) { - my $buf; - my $num = $self->{'socketi'}->read($buf,$cnt); - die "S: Mbinary $cnt: $num: $!\n" if not defined $num or $num<=0; - print $fh $buf; - $res += $num; - $cnt -= $num; - } + $res += $self->_fetchfile($fh, $cnt); } else { chomp $line; if($line eq "ok") { @@ -384,6 +369,23 @@ sub file { return ($name, $res); } +sub _fetchfile { + my ($self, $fh, $cnt) = @_; + my $res = 0; + my $bufsize = 1024 * 1024; + while($cnt) { + if ($bufsize > $cnt) { + $bufsize = $cnt; + } + my $buf; + my $num = $self->{'socketi'}->read($buf,$bufsize); + die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0; + print $fh $buf; + $res += $num; + $cnt -= $num; + } + return $res; +} package main; @@ -429,22 +431,25 @@ sub getwd() { return $pwd; } +sub is_sha1 { + my $s = shift; + return $s =~ /^[a-f0-9]{40}$/; +} -sub get_headref($$) { +sub get_headref ($$) { my $name = shift; my $git_dir = shift; - my $sha; - if (open(C,"$git_dir/refs/heads/$name")) { - chomp($sha = <C>); - close(C); - length($sha) == 40 - or die "Cannot get head id for $name ($sha): $!\n"; + my $f = "$git_dir/refs/heads/$name"; + if(open(my $fh, $f)) { + chomp(my $r = <$fh>); + is_sha1($r) or die "Cannot get head id for $name ($r): $!"; + return $r; } - return $sha; + die "unable to open $f: $!" unless $! == POSIX::ENOENT; + return undef; } - -d $git_tree or mkdir($git_tree,0777) or die "Could not create $git_tree: $!"; @@ -561,98 +566,66 @@ unless($pid) { my $state = 0; -my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); -my(@old,@new,@skipped); -my $commit = sub { - my $pid; - while(@old) { - my @o2; - if(@old > 55) { - @o2 = splice(@old,0,50); - } else { - @o2 = @old; - @old = (); - } - system("git-update-index","--force-remove","--",@o2); - die "Cannot remove files: $?\n" if $?; - } - while(@new) { - my @n2; - if(@new > 12) { - @n2 = splice(@new,0,10); - } else { - @n2 = @new; - @new = (); - } - system("git-update-index","--add", - (map { ('--cacheinfo', @$_) } @n2)); - die "Cannot add files: $?\n" if $?; - } +sub update_index (\@\@) { + my $old = shift; + my $new = shift; + open(my $fh, '|-', qw(git-update-index -z --index-info)) + or die "unable to open git-update-index: $!"; + print $fh + (map { "0 0000000000000000000000000000000000000000\t$_\0" } + @$old), + (map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" } + @$new) + or die "unable to write to git-update-index: $!"; + close $fh + or die "unable to write to git-update-index: $!"; + $? and die "git-update-index reported error: $?"; +} - $pid = open(C,"-|"); - die "Cannot fork: $!" unless defined $pid; - unless($pid) { - exec("git-write-tree"); - die "Cannot exec git-write-tree: $!\n"; - } - chomp(my $tree = <C>); - length($tree) == 40 - or die "Cannot get tree id ($tree): $!\n"; - close(C) +sub write_tree () { + open(my $fh, '-|', qw(git-write-tree)) + or die "unable to open git-write-tree: $!"; + chomp(my $tree = <$fh>); + is_sha1($tree) + or die "Cannot get tree id ($tree): $!"; + close($fh) or die "Error running git-write-tree: $?\n"; print "Tree ID $tree\n" if $opt_v; + return $tree; +} - my $parent = ""; - if(open(C,"$git_dir/refs/heads/$last_branch")) { - chomp($parent = <C>); - close(C); - length($parent) == 40 - or die "Cannot get parent id ($parent): $!\n"; - print "Parent ID $parent\n" if $opt_v; - } - - my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n"; - my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n"; - $pid = fork(); - die "Fork: $!\n" unless defined $pid; - unless($pid) { - $pr->writer(); - $pw->reader(); - open(OUT,">&STDOUT"); - dup2($pw->fileno(),0); - dup2($pr->fileno(),1); - $pr->close(); - $pw->close(); - - my @par = (); - @par = ("-p",$parent) if $parent; - - # loose detection of merges - # based on the commit msg - foreach my $rx (@mergerx) { - if ($logmsg =~ $rx) { - my $mparent = $1; - if ($mparent eq 'HEAD') { $mparent = $opt_o }; - if ( -e "$git_dir/refs/heads/$mparent") { - $mparent = get_headref($mparent, $git_dir); - push @par, '-p', $mparent; - print OUT "Merge parent branch: $mparent\n" if $opt_v; - } - } +my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); +my(@old,@new,@skipped); +sub commit { + update_index(@old, @new); + @old = @new = (); + my $tree = write_tree(); + my $parent = get_headref($last_branch, $git_dir); + print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v; + + my @commit_args; + push @commit_args, ("-p", $parent) if $parent; + + # loose detection of merges + # based on the commit msg + foreach my $rx (@mergerx) { + next unless $logmsg =~ $rx && $1; + my $mparent = $1 eq 'HEAD' ? $opt_o : $1; + if(my $sha1 = get_headref($mparent, $git_dir)) { + push @commit_args, '-p', $mparent; + print "Merge parent branch: $mparent\n" if $opt_v; } - - exec("env", - "GIT_AUTHOR_NAME=$author_name", - "GIT_AUTHOR_EMAIL=$author_email", - "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "GIT_COMMITTER_NAME=$author_name", - "GIT_COMMITTER_EMAIL=$author_email", - "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "git-commit-tree", $tree,@par); - die "Cannot exec git-commit-tree: $!\n"; } - $pw->writer(); - $pr->reader(); + + my $commit_date = strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)); + $ENV{GIT_AUTHOR_NAME} = $author_name; + $ENV{GIT_AUTHOR_EMAIL} = $author_email; + $ENV{GIT_AUTHOR_DATE} = $commit_date; + $ENV{GIT_COMMITTER_NAME} = $author_name; + $ENV{GIT_COMMITTER_EMAIL} = $author_email; + $ENV{GIT_COMMITTER_DATE} = $commit_date; + my $pid = open2(my $commit_read, my $commit_write, + 'git-commit-tree', $tree, @commit_args); # compatibility with git2cvs substr($logmsg,32767) = "" if length($logmsg) > 32767; @@ -661,18 +634,17 @@ my $commit = sub { if (@skipped) { $logmsg .= "\n\n\nSKIPPED:\n\t"; $logmsg .= join("\n\t", @skipped) . "\n"; + @skipped = (); } - print $pw "$logmsg\n" + print($commit_write "$logmsg\n") && close($commit_write) or die "Error writing to git-commit-tree: $!\n"; - $pw->close(); - print "Committed patch $patchset ($branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v; - chomp(my $cid = <$pr>); - length($cid) == 40 - or die "Cannot get commit id ($cid): $!\n"; + print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v; + chomp(my $cid = <$commit_read>); + is_sha1($cid) or die "Cannot get commit id ($cid): $!\n"; print "Commit ID $cid\n" if $opt_v; - $pr->close(); + close($commit_read); waitpid($pid,0); die "Error running git-commit-tree: $?\n" if $?; @@ -716,6 +688,7 @@ my $commit = sub { } }; +my $commitcount = 1; while(<CVS>) { chomp; if($state == 0 and /^-+$/) { @@ -849,7 +822,14 @@ while(<CVS>) { } elsif($state == 9 and /^\s*$/) { $state = 10; } elsif(($state == 9 or $state == 10) and /^-+$/) { - &$commit(); + $commitcount++; + if ($opt_L && $commitcount > $opt_L) { + last; + } + commit(); + if (($commitcount & 1023) == 0) { + system("git repack -a -d"); + } $state = 1; } elsif($state == 11 and /^-+$/) { $state = 1; @@ -859,7 +839,7 @@ while(<CVS>) { print "* UNKNOWN LINE * $_\n"; } } -&$commit() if $branch and $state != 11; +commit() if $branch and $state != 11; unlink($git_index); diff --git a/git-format-patch.sh b/git-format-patch.sh deleted file mode 100755 index 8a16eadfbd..0000000000 --- a/git-format-patch.sh +++ /dev/null @@ -1,344 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano -# - -USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--diff-options] [--attach] <his> [<mine>]' -LONG_USAGE='Prepare each commit with its patch since <mine> head forked from -<his> head, one file per patch formatted to resemble UNIX mailbox -format, for e-mail submission or use with git-am. - -Each output file is numbered sequentially from 1, and uses the -first line of the commit message (massaged for pathname safety) -as the filename. - -When -o is specified, output files are created in <dir>; otherwise -they are created in the current working directory. This option -is ignored if --stdout is specified. - -When -n is specified, instead of "[PATCH] Subject", the first -line is formatted as "[PATCH N/M] Subject", unless you have only -one patch. - -When --attach is specified, patches are attached, not inlined.' - -. git-sh-setup - -# Force diff to run in C locale. -LANG=C LC_ALL=C -export LANG LC_ALL - -diff_opts= -LF=' -' - -outdir=./ -while case "$#" in 0) break;; esac -do - case "$1" in - -c|--c|--ch|--che|--chec|--check) - check=t ;; - -a|--a|--au|--aut|--auth|--autho|--author|\ - -d|--d|--da|--dat|--date|\ - -m|--m|--mb|--mbo|--mbox) # now noop - ;; - --at|--att|--atta|--attac|--attach) - attach=t ;; - -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\ - --keep-subj|--keep-subje|--keep-subjec|--keep-subject) - keep_subject=t ;; - -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered) - numbered=t ;; - -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) - signoff=t ;; - --st|--std|--stdo|--stdou|--stdout) - stdout=t ;; - -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\ - --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\ - --output-direc=*|--output-direct=*|--output-directo=*|\ - --output-director=*|--output-directory=*) - outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;; - -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\ - --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\ - --output-directo|--output-director|--output-directory) - case "$#" in 1) usage ;; esac; shift - outdir="$1" ;; - -h|--h|--he|--hel|--help) - usage - ;; - -*' '* | -*"$LF"* | -*' '*) - # Ignore diff option that has whitespace for now. - ;; - -*) diff_opts="$diff_opts$1 " ;; - *) break ;; - esac - shift -done - -case "$keep_subject$numbered" in -tt) - die '--keep-subject and --numbered are incompatible.' ;; -esac - -tmp=.tmp-series$$ -trap 'rm -f $tmp-*' 0 1 2 3 15 - -series=$tmp-series -commsg=$tmp-commsg -filelist=$tmp-files - -# Backward compatible argument parsing hack. -# -# Historically, we supported: -# 1. "rev1" is equivalent to "rev1..HEAD" -# 2. "rev1..rev2" -# 3. "rev1" "rev2 is equivalent to "rev1..rev2" -# -# We want to take a sequence of "rev1..rev2" in general. -# Also, "rev1.." should mean "rev1..HEAD"; git-diff users are -# familiar with that syntax. - -case "$#,$1$2" in -1,?*..?*) - # single "rev1..rev2" - ;; -1,?*..) - # single "rev1.." should mean "rev1..HEAD" - set x "$1"HEAD - shift - ;; -1,*) - # single rev1 - set x "$1..HEAD" - shift - ;; -2,?*..?*) - # not traditional "rev1" "rev2" - ;; -2,*) - set x "$1..$2" - shift - ;; -esac - -# Now we have what we want in $@ -for revpair -do - case "$revpair" in - ?*..?*) - rev1=`expr "z$revpair" : 'z\(.*\)\.\.'` - rev2=`expr "z$revpair" : 'z.*\.\.\(.*\)'` - ;; - *) - rev1="$revpair^" - rev2="$revpair" - ;; - esac - git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 || - die "Not a valid rev $rev1 ($revpair)" - git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 || - die "Not a valid rev $rev2 ($revpair)" - git-cherry -v "$rev1" "$rev2" | - while read sign rev comment - do - case "$sign" in - '-') - echo >&2 "Merged already: $comment" - ;; - *) - echo $rev - ;; - esac - done -done >$series - -me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'` -headers=`git-repo-config --get format.headers` -case "$attach" in -"") ;; -*) - mimemagic="050802040500080604070107" -esac - -case "$outdir" in -*/) ;; -*) outdir="$outdir/" ;; -esac -test -d "$outdir" || mkdir -p "$outdir" || exit - -titleScript=' - /./d - /^$/n - s/^\[PATCH[^]]*\] *// - s/[^-a-z.A-Z_0-9]/-/g - s/\.\.\.*/\./g - s/\.*$// - s/--*/-/g - s/^-// - s/-$// - s/$/./ - p - q -' - -process_one () { - perl -w -e ' -my ($keep_subject, $num, $signoff, $headers, $mimemagic, $commsg) = @ARGV; -my ($signoff_pattern, $done_header, $done_subject, $done_separator, $signoff_seen, - $last_was_signoff); - -if ($signoff) { - $signoff = "Signed-off-by: " . `git-var GIT_COMMITTER_IDENT`; - $signoff =~ s/>.*/>/; - $signoff_pattern = quotemeta($signoff); -} - -my @weekday_names = qw(Sun Mon Tue Wed Thu Fri Sat); -my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); - -sub show_date { - my ($time, $tz) = @_; - my $minutes = abs($tz); - $minutes = int($minutes / 100) * 60 + ($minutes % 100); - if ($tz < 0) { - $minutes = -$minutes; - } - my $t = $time + $minutes * 60; - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t); - return sprintf("%s, %d %s %d %02d:%02d:%02d %+05d", - $weekday_names[$wday], $mday, - $month_names[$mon], $year+1900, - $hour, $min, $sec, $tz); -} - -print "From nobody Mon Sep 17 00:00:00 2001\n"; -open FH, "git stripspace <$commsg |" or die "open $commsg pipe"; -while (<FH>) { - unless ($done_header) { - if (/^$/) { - $done_header = 1; - } - elsif (/^author (.*>) (.*)$/) { - my ($author_ident, $author_date) = ($1, $2); - my ($utc, $off) = ($author_date =~ /^(\d+) ([-+]?\d+)$/); - $author_date = show_date($utc, $off); - - print "From: $author_ident\n"; - print "Date: $author_date\n"; - } - next; - } - unless ($done_subject) { - unless ($keep_subject) { - s/^\[PATCH[^]]*\]\s*//; - s/^/[PATCH$num] /; - } - if ($headers) { - print "$headers\n"; - } - print "Subject: $_"; - if ($mimemagic) { - print "MIME-Version: 1.0\n"; - print "Content-Type: multipart/mixed;\n"; - print " boundary=\"------------$mimemagic\"\n"; - print "\n"; - print "This is a multi-part message in MIME format.\n"; - print "--------------$mimemagic\n"; - print "Content-Type: text/plain; charset=UTF-8; format=fixed\n"; - print "Content-Transfer-Encoding: 8bit\n"; - } - $done_subject = 1; - next; - } - unless ($done_separator) { - print "\n"; - $done_separator = 1; - next if (/^$/); - } - - $last_was_signoff = 0; - if (/Signed-off-by:/i) { - if ($signoff ne "" && /Signed-off-by:\s*$signoff_pattern$/i) { - $signoff_seen = 1; - } - } - print $_; -} -if (!$signoff_seen && $signoff ne "") { - if (!$last_was_signoff) { - print "\n"; - } - print "$signoff\n"; -} -print "\n---\n\n"; -close FH or die "close $commsg pipe"; -' "$keep_subject" "$num" "$signoff" "$headers" "$mimemagic" $commsg - - git-diff-tree -p --stat --summary $diff_opts "$commit" - echo - case "$mimemagic" in - '');; - *) - echo "--------------$mimemagic" - echo "Content-Type: text/x-patch;" - echo " name=\"$commit.diff\"" - echo "Content-Transfer-Encoding: 8bit" - echo "Content-Disposition: inline;" - echo " filename=\"$commit.diff\"" - echo - esac - git-diff-tree -p $diff_opts "$commit" - case "$mimemagic" in - '') - echo "-- " - echo "@@GIT_VERSION@@" - ;; - *) - echo - echo "--------------$mimemagic--" - echo - ;; - esac - echo -} - -total=`wc -l <$series | tr -dc "[0-9]"` -case "$total,$numbered" in -1,*) - numfmt='' ;; -*,t) - numfmt=`echo "$total" | wc -c` - numfmt=$(($numfmt-1)) - numfmt=" %0${numfmt}d/$total" -esac - -i=1 -while read commit -do - git-cat-file commit "$commit" | git-stripspace >$commsg - title=`sed -ne "$titleScript" <$commsg` - case "$numbered" in - '') num= ;; - *) - num=`printf "$numfmt" $i` ;; - 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 b6882a90c1..2fdcaf7886 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -58,11 +58,19 @@ http://* | https://* ) ;; rsync://* ) - mkdir $tmpdir + mkdir $tmpdir && + rsync -rlq "$peek_repo/HEAD" $tmpdir && rsync -rq "$peek_repo/refs" $tmpdir || { echo "failed slurping" exit } + head=$(cat "$tmpdir/HEAD") && + case "$head" in + ref:' '*) + head=$(expr "z$head" : 'zref: \(.*\)') && + head=$(cat "$tmpdir/$head") || exit + esac && + echo "$head HEAD" (cd $tmpdir && find refs -type f) | while read path do diff --git a/git-rebase.sh b/git-rebase.sh index 6ff6088d18..e6b57b8ab9 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -152,6 +152,6 @@ then exit 0 fi -git-format-patch -k --stdout --full-index "$upstream" ORIG_HEAD | +git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD | git am --binary -3 -k --resolvemsg="$RESOLVEMSG" diff --git a/git-rm.sh b/git-rm.sh deleted file mode 100755 index fda4541c76..0000000000 --- a/git-rm.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh - -USAGE='[-f] [-n] [-v] [--] <file>...' -SUBDIRECTORY_OK='Yes' -. git-sh-setup - -remove_files= -show_only= -verbose= -while : ; do - case "$1" in - -f) - remove_files=true - ;; - -n) - show_only=true - ;; - -v) - verbose=--verbose - ;; - --) - shift; break - ;; - -*) - usage - ;; - *) - break - ;; - esac - shift -done - -# This is typo-proofing. If some paths match and some do not, we want -# to do nothing. -case "$#" in -0) ;; -*) - git-ls-files --error-unmatch -- "$@" >/dev/null || { - echo >&2 "Maybe you misspelled it?" - exit 1 - } - ;; -esac - -if test -f "$GIT_DIR/info/exclude" -then - git-ls-files -z \ - --exclude-from="$GIT_DIR/info/exclude" \ - --exclude-per-directory=.gitignore -- "$@" -else - git-ls-files -z \ - --exclude-per-directory=.gitignore -- "$@" -fi | -case "$show_only,$remove_files" in -true,*) - xargs -0 echo - ;; -*,true) - xargs -0 sh -c " - while [ \$# -gt 0 ]; do - file=\$1; shift - rm -- \"\$file\" && git-update-index --remove $verbose \"\$file\" - done - " inline - ;; -*) - git-update-index --force-remove $verbose -z --stdin - ;; -esac @@ -47,12 +47,29 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "whatchanged", cmd_whatchanged }, { "show", cmd_show }, { "push", cmd_push }, + { "format-patch", cmd_format_patch }, { "count-objects", cmd_count_objects }, { "diff", cmd_diff }, { "grep", cmd_grep }, + { "rm", cmd_rm }, + { "add", cmd_add }, { "rev-list", cmd_rev_list }, { "init-db", cmd_init_db }, - { "check-ref-format", cmd_check_ref_format } + { "tar-tree", cmd_tar_tree }, + { "upload-tar", cmd_upload_tar }, + { "check-ref-format", cmd_check_ref_format }, + { "ls-files", cmd_ls_files }, + { "ls-tree", cmd_ls_tree }, + { "tar-tree", cmd_tar_tree }, + { "read-tree", cmd_read_tree }, + { "commit-tree", cmd_commit_tree }, + { "apply", cmd_apply }, + { "show-branch", cmd_show_branch }, + { "diff-files", cmd_diff_files }, + { "diff-index", cmd_diff_index }, + { "diff-stages", cmd_diff_stages }, + { "diff-tree", cmd_diff_tree }, + { "cat-file", cmd_cat_file } }; int i; diff --git a/log-tree.c b/log-tree.c index b90ba6762a..58b016378c 100644 --- a/log-tree.c +++ b/log-tree.c @@ -20,6 +20,7 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep) int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40; const char *extra; int len; + char *subject = NULL, *after_subject = NULL; opt->loginfo = NULL; if (!opt->verbose_header) { @@ -49,19 +50,67 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep) /* * Print header line of header.. */ - printf("%s%s", - opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ", - diff_unique_abbrev(commit->object.sha1, abbrev_commit)); - if (opt->parents) - show_parents(commit, abbrev_commit); - if (parent) - printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit)); - putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); + + if (opt->commit_format == CMIT_FMT_EMAIL) { + char *sha1 = sha1_to_hex(commit->object.sha1); + if (opt->total > 0) { + static char buffer[64]; + snprintf(buffer, sizeof(buffer), + "Subject: [PATCH %d/%d] ", + opt->nr, opt->total); + subject = buffer; + } else if (opt->total == 0) + subject = "Subject: [PATCH] "; + else + subject = "Subject: "; + + printf("From %s Mon Sep 17 00:00:00 2001\n", sha1); + if (opt->mime_boundary) { + static char subject_buffer[1024]; + static char buffer[1024]; + snprintf(subject_buffer, sizeof(subject_buffer) - 1, + "MIME-Version: 1.0\n" + "Content-Type: multipart/mixed;\n" + " boundary=\"%s%s\"\n" + "\n" + "This is a multi-part message in MIME " + "format.\n" + "--%s%s\n" + "Content-Type: text/plain; " + "charset=UTF-8; format=fixed\n" + "Content-Transfer-Encoding: 8bit\n\n", + mime_boundary_leader, opt->mime_boundary, + mime_boundary_leader, opt->mime_boundary); + after_subject = subject_buffer; + + snprintf(buffer, sizeof(buffer) - 1, + "--%s%s\n" + "Content-Type: text/x-patch;\n" + " name=\"%s.diff\"\n" + "Content-Transfer-Encoding: 8bit\n" + "Content-Disposition: inline;\n" + " filename=\"%s.diff\"\n\n", + mime_boundary_leader, opt->mime_boundary, + sha1, sha1); + opt->diffopt.stat_sep = buffer; + } + } else { + printf("%s%s", + opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ", + diff_unique_abbrev(commit->object.sha1, abbrev_commit)); + if (opt->parents) + show_parents(commit, abbrev_commit); + if (parent) + printf(" (from %s)", + diff_unique_abbrev(parent->object.sha1, + abbrev_commit)); + putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); + } /* * And then the pretty-printed message itself */ - len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev); + len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, after_subject); printf("%s%s%s", this_header, extra, sep); } @@ -166,15 +215,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log int log_tree_commit(struct rev_info *opt, struct commit *commit) { struct log_info log; + int shown; log.commit = commit; log.parent = NULL; opt->loginfo = &log; - if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) { + shown = log_tree_diff(opt, commit, &log); + if (!shown && opt->loginfo && opt->always_show_header) { log.parent = NULL; show_log(opt, opt->loginfo, ""); + shown = 1; } opt->loginfo = NULL; - return 0; + return shown; } diff --git a/mailinfo.c b/mailinfo.c index a133e6d08a..5b6c2157ed 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -72,11 +72,14 @@ static int bogus_from(char *line) return 1; } -static int handle_from(char *line) +static int handle_from(char *in_line) { - char *at = strchr(line, '@'); + char line[1000]; + char *at; char *dst; + strcpy(line, in_line); + at = strchr(line, '@'); if (!at) return bogus_from(line); @@ -238,44 +241,45 @@ static int eatspace(char *line) #define SEEN_DATE 02 #define SEEN_SUBJECT 04 #define SEEN_BOGUS_UNIX_FROM 010 +#define SEEN_PREFIX 020 /* First lines of body can have From:, Date:, and Subject: */ -static int handle_inbody_header(int *seen, char *line) +static void handle_inbody_header(int *seen, char *line) { if (!memcmp(">From", line, 5) && isspace(line[5])) { if (!(*seen & SEEN_BOGUS_UNIX_FROM)) { *seen |= SEEN_BOGUS_UNIX_FROM; - return 1; + return; } } if (!memcmp("From:", line, 5) && isspace(line[5])) { if (!(*seen & SEEN_FROM) && handle_from(line+6)) { *seen |= SEEN_FROM; - return 1; + return; } } if (!memcmp("Date:", line, 5) && isspace(line[5])) { if (!(*seen & SEEN_DATE)) { handle_date(line+6); *seen |= SEEN_DATE; - return 1; + return; } } if (!memcmp("Subject:", line, 8) && isspace(line[8])) { if (!(*seen & SEEN_SUBJECT)) { handle_subject(line+9); *seen |= SEEN_SUBJECT; - return 1; + return; } } if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { if (!(*seen & SEEN_SUBJECT)) { handle_subject(line); *seen |= SEEN_SUBJECT; - return 1; + return; } } - return 0; + *seen |= SEEN_PREFIX; } static char *cleanup_subject(char *subject) @@ -331,6 +335,7 @@ static void cleanup_space(char *buf) } } +static void decode_header_bq(char *it); typedef int (*header_fn_t)(char *); struct header_def { const char *name; @@ -338,7 +343,7 @@ struct header_def { int namelen; }; -static void check_header(char *line, int len, struct header_def *header) +static void check_header(char *line, struct header_def *header) { int i; @@ -350,13 +355,17 @@ static void check_header(char *line, int len, struct header_def *header) int len = header[i].namelen; if (!strncasecmp(line, header[i].name, len) && line[len] == ':' && isspace(line[len + 1])) { + /* Unwrap inline B and Q encoding, and optionally + * normalize the meta information to utf8. + */ + decode_header_bq(line + len + 2); header[i].func(line + len + 2); break; } } } -static void check_subheader_line(char *line, int len) +static void check_subheader_line(char *line) { static struct header_def header[] = { { "Content-Type", handle_subcontent_type }, @@ -364,9 +373,9 @@ static void check_subheader_line(char *line, int len) handle_content_transfer_encoding }, { NULL }, }; - check_header(line, len, header); + check_header(line, header); } -static void check_header_line(char *line, int len) +static void check_header_line(char *line) { static struct header_def header[] = { { "From", handle_from }, @@ -377,7 +386,30 @@ static void check_header_line(char *line, int len) handle_content_transfer_encoding }, { NULL }, }; - check_header(line, len, header); + check_header(line, header); +} + +static int is_rfc2822_header(char *line) +{ + /* + * The section that defines the loosest possible + * field name is "3.6.8 Optional fields". + * + * optional-field = field-name ":" unstructured CRLF + * field-name = 1*ftext + * ftext = %d33-57 / %59-126 + */ + int ch; + char *cp = line; + while ((ch = *cp++)) { + if (ch == ':') + return cp != line; + if ((33 <= ch && ch <= 57) || + (59 <= ch && ch <= 126)) + continue; + break; + } + return 0; } static int read_one_header_line(char *line, int sz, FILE *in) @@ -386,18 +418,25 @@ static int read_one_header_line(char *line, int sz, FILE *in) while (ofs < sz) { int peek, len; if (fgets(line + ofs, sz - ofs, in) == NULL) - return ofs; + break; len = eatspace(line + ofs); if (len == 0) - return ofs; - peek = fgetc(in); ungetc(peek, in); - if (peek == ' ' || peek == '\t') { - /* Yuck, 2822 header "folding" */ - ofs += len; - continue; + break; + if (!is_rfc2822_header(line)) { + /* Re-add the newline */ + line[ofs + len] = '\n'; + line[ofs + len + 1] = '\0'; + break; } - return ofs + len; + ofs += len; + /* Yuck, 2822 header "folding" */ + peek = fgetc(in); ungetc(peek, in); + if (peek != ' ' && peek != '\t') + break; } + /* Count mbox From headers as headers */ + if (!ofs && !memcmp(line, "From ", 5)) + ofs = 1; return ofs; } @@ -592,25 +631,13 @@ static void decode_transfer_encoding(char *line) static void handle_info(void) { char *sub; - static int done_info = 0; - - if (done_info) - return; - done_info = 1; sub = cleanup_subject(subject); cleanup_space(name); cleanup_space(date); cleanup_space(email); cleanup_space(sub); - /* Unwrap inline B and Q encoding, and optionally - * normalize the meta information to utf8. - */ - decode_header_bq(name); - decode_header_bq(date); - decode_header_bq(email); - decode_header_bq(sub); printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", name, email, sub, date); } @@ -618,7 +645,7 @@ static void handle_info(void) /* We are inside message body and have read line[] already. * Spit out the commit log. */ -static int handle_commit_msg(void) +static int handle_commit_msg(int *seen) { if (!cmitmsg) return 0; @@ -642,6 +669,11 @@ static int handle_commit_msg(void) decode_transfer_encoding(line); if (metainfo_charset) convert_to_utf8(line, charset); + + handle_inbody_header(seen, line); + if (!(*seen & SEEN_PREFIX)) + continue; + fputs(line, cmitmsg); } while (fgets(line, sizeof(line), stdin) != NULL); fclose(cmitmsg); @@ -673,26 +705,16 @@ static void handle_patch(void) * that the first part to contain commit message and a patch, and * handle other parts as pure patches. */ -static int handle_multipart_one_part(void) +static int handle_multipart_one_part(int *seen) { - int seen = 0; int n = 0; - int len; while (fgets(line, sizeof(line), stdin) != NULL) { again: - len = eatspace(line); n++; - if (!len) - continue; if (is_multipart_boundary(line)) break; - if (0 <= seen && handle_inbody_header(&seen, line)) - continue; - seen = -1; /* no more inbody headers */ - line[len] = '\n'; - handle_info(); - if (handle_commit_msg()) + if (handle_commit_msg(seen)) goto again; handle_patch(); break; @@ -704,6 +726,7 @@ static int handle_multipart_one_part(void) static void handle_multipart_body(void) { + int seen = 0; int part_num = 0; /* Skip up to the first boundary */ @@ -716,16 +739,16 @@ static void handle_multipart_body(void) return; /* We are on boundary line. Start slurping the subhead. */ while (1) { - int len = read_one_header_line(line, sizeof(line), stdin); - if (!len) { - if (handle_multipart_one_part() < 0) + int hdr = read_one_header_line(line, sizeof(line), stdin); + if (!hdr) { + if (handle_multipart_one_part(&seen) < 0) return; /* Reset per part headers */ transfer_encoding = TE_DONTCARE; charset[0] = 0; } else - check_subheader_line(line, len); + check_subheader_line(line); } fclose(patchfile); if (!patch_lines) { @@ -739,18 +762,9 @@ static void handle_body(void) { int seen = 0; - while (fgets(line, sizeof(line), stdin) != NULL) { - int len = eatspace(line); - if (!len) - continue; - if (0 <= seen && handle_inbody_header(&seen, line)) - continue; - seen = -1; /* no more inbody headers */ - line[len] = '\n'; - handle_info(); - handle_commit_msg(); + if (line[0] || fgets(line, sizeof(line), stdin) != NULL) { + handle_commit_msg(&seen); handle_patch(); - break; } fclose(patchfile); if (!patch_lines) { @@ -794,15 +808,16 @@ int main(int argc, char **argv) exit(1); } while (1) { - int len = read_one_header_line(line, sizeof(line), stdin); - if (!len) { + int hdr = read_one_header_line(line, sizeof(line), stdin); + if (!hdr) { if (multipart_boundary[0]) handle_multipart_body(); else handle_body(); + handle_info(); break; } - check_header_line(line, len); + check_header_line(line); } return 0; } @@ -45,42 +45,46 @@ static int verify_tag(char *buffer, unsigned long size) unsigned char sha1[20]; const char *object, *type_line, *tag_line, *tagger_line; - if (size < 64 || size > MAXSIZE-1) - return -1; + if (size < 64) + return error("wanna fool me ? you obviously got the size wrong !\n"); + buffer[size] = 0; /* Verify object line */ object = buffer; if (memcmp(object, "object ", 7)) - return -1; + return error("char%d: does not start with \"object \"\n", 0); + if (get_sha1_hex(object + 7, sha1)) - return -1; + return error("char%d: could not get SHA1 hash\n", 7); /* Verify type line */ type_line = object + 48; if (memcmp(type_line - 1, "\ntype ", 6)) - return -1; + return error("char%d: could not find \"\\ntype \"\n", 47); /* Verify tag-line */ tag_line = strchr(type_line, '\n'); if (!tag_line) - return -1; + return error("char%td: could not find next \"\\n\"\n", type_line - buffer); tag_line++; if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return -1; + return error("char%td: no \"tag \" found\n", tag_line - buffer); /* Get the actual type */ typelen = tag_line - type_line - strlen("type \n"); if (typelen >= sizeof(type)) - return -1; + return error("char%td: type too long\n", type_line+5 - buffer); + memcpy(type, type_line+5, typelen); type[typelen] = 0; /* Verify that the object matches */ if (get_sha1_hex(object + 7, sha1)) - return -1; + return error("char%d: could not get SHA1 hash but this is really odd since i got it before !\n", 7); + if (verify_object(sha1, type)) - return -1; + return error("char%d: could not verify object %s\n", 7, sha1); /* Verify the tag-name: we don't allow control characters or spaces in it */ tag_line += 4; @@ -90,14 +94,14 @@ static int verify_tag(char *buffer, unsigned long size) break; if (c > ' ') continue; - return -1; + return error("char%td: could not verify tag name\n", tag_line - buffer); } /* Verify the tagger line */ tagger_line = tag_line; if (memcmp(tagger_line, "tagger", 6) || (tagger_line[6] == '\n')) - return -1; + return error("char%td: could not find \"tagger\"\n", tagger_line - buffer); /* The actual stuff afterwards we don't care about.. */ return 0; @@ -105,8 +109,8 @@ static int verify_tag(char *buffer, unsigned long size) int main(int argc, char **argv) { - unsigned long size; - char buffer[MAXSIZE]; + unsigned long size = 4096; + char *buffer = malloc(size); unsigned char result_sha1[20]; if (argc != 1) @@ -114,13 +118,9 @@ int main(int argc, char **argv) setup_git_directory(); - // Read the signature - size = 0; - for (;;) { - int ret = xread(0, buffer + size, MAXSIZE - size); - if (ret <= 0) - break; - size += ret; + if (read_pipe(0, &buffer, &size)) { + free(buffer); + die("could not read from stdin"); } // Verify it for some basic sanity: it needs to start with "object <sha1>\ntype\ntagger " @@ -129,6 +129,9 @@ int main(int argc, char **argv) if (write_sha1_file(buffer, size, tag_type, result_sha1) < 0) die("unable to write tag file"); + + free(buffer); + printf("%s\n", sha1_to_hex(result_sha1)); return 0; } diff --git a/read-cache.c b/read-cache.c index b95edcc14c..32ba917923 100644 --- a/read-cache.c +++ b/read-cache.c @@ -332,6 +332,70 @@ int ce_path_match(const struct cache_entry *ce, const char **pathspec) } /* + * We fundamentally don't like some paths: we don't want + * dot or dot-dot anywhere, and for obvious reasons don't + * want to recurse into ".git" either. + * + * Also, we don't want double slashes or slashes at the + * end that can make pathnames ambiguous. + */ +static int verify_dotfile(const char *rest) +{ + /* + * The first character was '.', but that + * has already been discarded, we now test + * the rest. + */ + switch (*rest) { + /* "." is not allowed */ + case '\0': case '/': + return 0; + + /* + * ".git" followed by NUL or slash is bad. This + * shares the path end test with the ".." case. + */ + case 'g': + if (rest[1] != 'i') + break; + if (rest[2] != 't') + break; + rest += 2; + /* fallthrough */ + case '.': + if (rest[1] == '\0' || rest[1] == '/') + return 0; + } + return 1; +} + +int verify_path(const char *path) +{ + char c; + + goto inside; + for (;;) { + if (!c) + return 1; + if (c == '/') { +inside: + c = *path++; + switch (c) { + default: + continue; + case '/': case '\0': + break; + case '.': + if (verify_dotfile(path)) + continue; + } + return 0; + } + c = *path++; + } +} + +/* * Do we have another file that has the beginning components being a * proper superset of the name we're trying to add? */ @@ -472,6 +536,8 @@ int add_cache_entry(struct cache_entry *ce, int option) if (!ok_to_add) return -1; + if (!verify_path(ce->name)) + return -1; if (!skip_df_check && check_file_directory_conflict(ce, pos, ok_to_replace)) { diff --git a/repo-config.c b/repo-config.c index 127afd784c..08fc4cc57d 100644 --- a/repo-config.c +++ b/repo-config.c @@ -108,7 +108,8 @@ static int get_value(const char* key_, const char* regex_) int main(int argc, const char **argv) { - setup_git_directory(); + int nongit = 0; + setup_git_directory_gently(&nongit); while (1 < argc) { if (!strcmp(argv[1], "--int")) diff --git a/revision.c b/revision.c index 2294b16ea2..42c077a4cb 100644 --- a/revision.c +++ b/revision.c @@ -733,6 +733,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->abbrev = DEFAULT_ABBREV; continue; } + if (!strncmp(arg, "--abbrev=", 9)) { + revs->abbrev = strtoul(arg + 9, NULL, 10); + if (revs->abbrev < MINIMUM_ABBREV) + revs->abbrev = MINIMUM_ABBREV; + else if (revs->abbrev > 40) + revs->abbrev = 40; + continue; + } if (!strcmp(arg, "--abbrev-commit")) { revs->abbrev_commit = 1; continue; diff --git a/revision.h b/revision.h index 48d7b4ca94..bdbdd235d8 100644 --- a/revision.h +++ b/revision.h @@ -58,6 +58,8 @@ struct rev_info { unsigned int abbrev; enum cmit_fmt commit_format; struct log_info *loginfo; + int nr, total; + const char *mime_boundary; /* special limits */ int max_count; diff --git a/sha1_file.c b/sha1_file.c index 223001033c..f77c18934a 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1399,6 +1399,25 @@ int move_temp_to_file(const char *tmpfile, char *filename) return 0; } +static int write_buffer(int fd, const void *buf, size_t len) +{ + while (len) { + ssize_t size; + + size = write(fd, buf, len); + if (!size) + return error("file write: disk full"); + if (size < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return error("file write error (%s)", strerror(errno)); + } + len -= size; + buf += size; + } + return 0; +} + int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { int size; @@ -1465,8 +1484,8 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha deflateEnd(&stream); size = stream.total_out; - if (write(fd, compressed, size) != size) - die("unable to write file"); + if (write_buffer(fd, compressed, size) < 0) + die("unable to write sha1 file"); fchmod(fd, 0444); close(fd); free(compressed); @@ -1474,73 +1493,70 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha return move_temp_to_file(tmpfile, filename); } -int write_sha1_to_fd(int fd, const unsigned char *sha1) +/* + * We need to unpack and recompress the object for writing + * it out to a different file. + */ +static void *repack_object(const unsigned char *sha1, unsigned long *objsize) { - ssize_t size; - unsigned long objsize; - int posn = 0; - void *map = map_sha1_file_internal(sha1, &objsize); - void *buf = map; - void *temp_obj = NULL; + size_t size; z_stream stream; + unsigned char *unpacked; + unsigned long len; + char type[20]; + char hdr[50]; + int hdrlen; + void *buf; - if (!buf) { - unsigned char *unpacked; - unsigned long len; - char type[20]; - char hdr[50]; - int hdrlen; - // need to unpack and recompress it by itself - unpacked = read_packed_sha1(sha1, type, &len); + // need to unpack and recompress it by itself + unpacked = read_packed_sha1(sha1, type, &len); - hdrlen = sprintf(hdr, "%s %lu", type, len) + 1; + hdrlen = sprintf(hdr, "%s %lu", type, len) + 1; - /* Set it up */ - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, Z_BEST_COMPRESSION); - size = deflateBound(&stream, len + hdrlen); - temp_obj = buf = xmalloc(size); - - /* Compress it */ - stream.next_out = buf; - stream.avail_out = size; - - /* First header.. */ - stream.next_in = (void *)hdr; - stream.avail_in = hdrlen; - while (deflate(&stream, 0) == Z_OK) - /* nothing */; + /* Set it up */ + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, Z_BEST_COMPRESSION); + size = deflateBound(&stream, len + hdrlen); + buf = xmalloc(size); - /* Then the data itself.. */ - stream.next_in = unpacked; - stream.avail_in = len; - while (deflate(&stream, Z_FINISH) == Z_OK) - /* nothing */; - deflateEnd(&stream); - free(unpacked); - - objsize = stream.total_out; - } + /* Compress it */ + stream.next_out = buf; + stream.avail_out = size; - do { - size = write(fd, buf + posn, objsize - posn); - if (size <= 0) { - if (!size) { - fprintf(stderr, "write closed\n"); - } else { - perror("write "); - } - return -1; - } - posn += size; - } while (posn < objsize); + /* First header.. */ + stream.next_in = (void *)hdr; + stream.avail_in = hdrlen; + while (deflate(&stream, 0) == Z_OK) + /* nothing */; - if (map) - munmap(map, objsize); - if (temp_obj) - free(temp_obj); + /* Then the data itself.. */ + stream.next_in = unpacked; + stream.avail_in = len; + while (deflate(&stream, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&stream); + free(unpacked); - return 0; + *objsize = stream.total_out; + return buf; +} + +int write_sha1_to_fd(int fd, const unsigned char *sha1) +{ + int retval; + unsigned long objsize; + void *buf = map_sha1_file_internal(sha1, &objsize); + + if (buf) { + retval = write_buffer(fd, buf, objsize); + munmap(buf, objsize); + return retval; + } + + buf = repack_object(sha1, &objsize); + retval = write_buffer(fd, buf, objsize); + free(buf); + return retval; } int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, @@ -1579,7 +1595,8 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, SHA1_Update(&c, discard, sizeof(discard) - stream.avail_out); } while (stream.avail_in && ret == Z_OK); - write(local, buffer, *bufposn - stream.avail_in); + if (write_buffer(local, buffer, *bufposn - stream.avail_in) < 0) + die("unable to write sha1 file"); memmove(buffer, buffer + *bufposn - stream.avail_in, stream.avail_in); *bufposn = stream.avail_in; @@ -1645,16 +1662,24 @@ int has_sha1_file(const unsigned char *sha1) return find_sha1_file(sha1, &st) ? 1 : 0; } -int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) +/* + * reads from fd as long as possible into a supplied buffer of size bytes. + * If neccessary the buffer's size is increased using realloc() + * + * returns 0 if anything went fine and -1 otherwise + * + * NOTE: both buf and size may change, but even when -1 is returned + * you still have to free() it yourself. + */ +int read_pipe(int fd, char** return_buf, unsigned long* return_size) { - unsigned long size = 4096; - char *buf = malloc(size); - int iret, ret; + char* buf = *return_buf; + unsigned long size = *return_size; + int iret; unsigned long off = 0; - unsigned char hdr[50]; - int hdrlen; + do { - iret = read(fd, buf + off, size - off); + iret = xread(fd, buf + off, size - off); if (iret > 0) { off += iret; if (off == size) { @@ -1663,16 +1688,34 @@ int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) } } } while (iret > 0); - if (iret < 0) { + + *return_buf = buf; + *return_size = off; + + if (iret < 0) + return -1; + return 0; +} + +int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) +{ + unsigned long size = 4096; + char *buf = malloc(size); + int ret; + unsigned char hdr[50]; + int hdrlen; + + if (read_pipe(fd, &buf, &size)) { free(buf); return -1; } + if (!type) type = blob_type; if (write_object) - ret = write_sha1_file(buf, off, type, sha1); + ret = write_sha1_file(buf, size, type, sha1); else { - write_sha1_file_prepare(buf, off, type, sha1, hdr, &hdrlen); + write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen); ret = 0; } free(buf); diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index 8335a63e2e..da3c81357b 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -75,7 +75,7 @@ test_expect_success \ git-update-index --add yomin && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >4.out || return 1 - diff --unified=0 M.out 4.out >4diff.out + diff -U0 M.out 4.out >4diff.out compare_change 4diff.out expected && check_cache_at yomin clean && sum bozbar frotz nitfol >actual4.sum && @@ -94,7 +94,7 @@ test_expect_success \ echo yomin yomin >yomin && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >5.out || return 1 - diff --unified=0 M.out 5.out >5diff.out + diff -U0 M.out 5.out >5diff.out compare_change 5diff.out expected && check_cache_at yomin dirty && sum bozbar frotz nitfol >actual5.sum && @@ -112,7 +112,7 @@ test_expect_success \ git-update-index --add frotz && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >6.out && - diff --unified=0 M.out 6.out && + diff -U0 M.out 6.out && check_cache_at frotz clean && sum bozbar frotz nitfol >actual3.sum && cmp M.sum actual3.sum && @@ -129,7 +129,7 @@ test_expect_success \ echo frotz frotz >frotz && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >7.out && - diff --unified=0 M.out 7.out && + diff -U0 M.out 7.out && check_cache_at frotz dirty && sum bozbar frotz nitfol >actual7.sum && if cmp M.sum actual7.sum; then false; else :; fi && @@ -206,7 +206,7 @@ test_expect_success \ git-update-index --add nitfol && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >14.out || return 1 - diff --unified=0 M.out 14.out >14diff.out + diff -U0 M.out 14.out >14diff.out compare_change 14diff.out expected && sum bozbar frotz >actual14.sum && grep -v nitfol M.sum > expected14.sum && @@ -227,7 +227,7 @@ test_expect_success \ echo nitfol nitfol nitfol >nitfol && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >15.out || return 1 - diff --unified=0 M.out 15.out >15diff.out + diff -U0 M.out 15.out >15diff.out compare_change 15diff.out expected && check_cache_at nitfol dirty && sum bozbar frotz >actual15.sum && @@ -264,7 +264,7 @@ test_expect_success \ git-update-index --add bozbar && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >18.out && - diff --unified=0 M.out 18.out && + diff -U0 M.out 18.out && check_cache_at bozbar clean && sum bozbar frotz nitfol >actual18.sum && cmp M.sum actual18.sum' @@ -278,7 +278,7 @@ test_expect_success \ echo gnusto gnusto >bozbar && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >19.out && - diff --unified=0 M.out 19.out && + diff -U0 M.out 19.out && check_cache_at bozbar dirty && sum frotz nitfol >actual19.sum && grep -v bozbar M.sum > expected19.sum && @@ -297,7 +297,7 @@ test_expect_success \ git-update-index --add bozbar && git-read-tree -m -u $treeH $treeM && git-ls-files --stage >20.out && - diff --unified=0 M.out 20.out && + diff -U0 M.out 20.out && check_cache_at bozbar clean && sum bozbar frotz nitfol >actual20.sum && cmp M.sum actual20.sum' @@ -338,7 +338,7 @@ test_expect_success \ git-update-index --add DF && git-read-tree -m -u $treeDF $treeDFDF && git-ls-files --stage >DFDFcheck.out && - diff --unified=0 DFDF.out DFDFcheck.out && + diff -U0 DFDF.out DFDFcheck.out && check_cache_at DF/DF clean' test_done diff --git a/update-index.c b/update-index.c index 7d6de821e2..5854d11fd9 100644 --- a/update-index.c +++ b/update-index.c @@ -120,70 +120,6 @@ static int add_file_to_cache(const char *path) return 0; } -/* - * We fundamentally don't like some paths: we don't want - * dot or dot-dot anywhere, and for obvious reasons don't - * want to recurse into ".git" either. - * - * Also, we don't want double slashes or slashes at the - * end that can make pathnames ambiguous. - */ -static int verify_dotfile(const char *rest) -{ - /* - * The first character was '.', but that - * has already been discarded, we now test - * the rest. - */ - switch (*rest) { - /* "." is not allowed */ - case '\0': case '/': - return 0; - - /* - * ".git" followed by NUL or slash is bad. This - * shares the path end test with the ".." case. - */ - case 'g': - if (rest[1] != 'i') - break; - if (rest[2] != 't') - break; - rest += 2; - /* fallthrough */ - case '.': - if (rest[1] == '\0' || rest[1] == '/') - return 0; - } - return 1; -} - -static int verify_path(const char *path) -{ - char c; - - goto inside; - for (;;) { - if (!c) - return 1; - if (c == '/') { -inside: - c = *path++; - switch (c) { - default: - continue; - case '/': case '\0': - break; - case '.': - if (verify_dotfile(path)) - continue; - } - return 0; - } - c = *path++; - } -} - static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, const char *path, int stage) { |