From: Junio C Hamano Date: Thu, 28 Sep 2006 04:46:07 +0000 (-0700) Subject: Merge branch 'jc/repack' X-Git-Tag: v1.4.3-rc1~23 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/1ad7a06?hp=d0b353b1a7a2f5168a8e2c992403f2a7d1e323f8 Merge branch 'jc/repack' * jc/repack: git-repack: allow git-repack to run in subdirectory repack: use only pack-objects, not rev-list. --- diff --git a/.gitignore b/.gitignore index a3d9c7a11d..25eb4637a6 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ git-merge-one-file git-merge-ours git-merge-recur git-merge-recursive +git-merge-recursive-old git-merge-resolve git-merge-stupid git-mktag @@ -122,13 +123,11 @@ git-update-ref git-update-server-info git-upload-archive git-upload-pack -git-upload-tar git-var git-verify-pack git-verify-tag git-whatchanged git-write-tree -git-zip-tree git-core-*/?* gitweb/gitweb.cgi test-date diff --git a/Documentation/config.txt b/Documentation/config.txt index 844cae4cf0..98c1f3e2e3 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -119,6 +119,13 @@ apply.whitespace:: Tells `git-apply` how to handle whitespaces, in the same way as the '--whitespace' option. See gitlink:git-apply[1]. +branch..remote:: + When in branch , it tells `git fetch` which remote to fetch. + +branch..merge:: + When in branch , it tells `git fetch` the default remote branch + to be merged. + pager.color:: A boolean to enable/disable colored output when the pager is in use (default is true). @@ -267,3 +274,10 @@ whatchanged.difftree:: imap:: The configuration variables in the 'imap' section are described in gitlink:git-imap-send[1]. + +receive.denyNonFastforwads:: + If set to true, git-receive-pack will deny a ref update which is + not a fast forward. Use this to prevent such an update via a push, + even if that push is forced. This configuration variable is + set when initializing a shared repository. + diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 913528d373..031fcd5190 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -40,6 +40,7 @@ OPTIONS :: This can be any options that the archiver backend understand. + See next section. --remote=:: Instead of making a tar archive from local repository, @@ -52,6 +53,18 @@ path:: If one or more paths are specified, include only these in the archive, otherwise include all files and subdirectories. +BACKEND EXTRA OPTIONS +--------------------- + +zip +~~~ +-0:: + Store the files instead of deflating them. +-9:: + Highest and slowest compression level. You can specify any + number from 1 to 9 to adjust compression speed and ratio. + + CONFIGURATION ------------- By default, file and directories modes are set to 0666 or 0777 in tar diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 741f2c69bd..51d7c94d7d 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] + [--interpolated-path=pathtemplate] [--enable=service] [--disable=service] [--allow-override=service] [--forbid-override=service] [--reuseaddr] [--detach] [--pid-file=file] @@ -50,6 +51,12 @@ OPTIONS 'git://example.com/hello.git', `git-daemon` will interpret the path as '/srv/git/hello.git'. +--interpolated-path=pathtemplate:: + To support virtual hosting, an interpolated path template can be + used to dynamically construct alternate paths. The template + supports %H for the target hostname as supplied by the client, + and %D for the absolute path of the named repository. + --export-all:: Allow pulling from all directories that look like GIT repositories (have the 'objects' and 'refs' subdirectories), even if they @@ -135,6 +142,46 @@ upload-pack:: disable it by setting `daemon.uploadpack` configuration item to `false`. +EXAMPLES +-------- +git-daemon as inetd server:: + To set up `git-daemon` as an inetd service that handles any + repository under the whitelisted set of directories, /pub/foo + and /pub/bar, place an entry like the following into + /etc/inetd all on one line: ++ +------------------------------------------------ + git stream tcp nowait nobody /usr/bin/git-daemon + git-daemon --inetd --verbose + --syslog --export-all + /pub/foo /pub/bar +------------------------------------------------ + + +git-daemon as inetd server for virtual hosts:: + To set up `git-daemon` as an inetd service that handles + repositories for different virtual hosts, `www.example.com` + and `www.example.org`, place an entry like the following into + `/etc/inetd` all on one line: ++ +------------------------------------------------ + git stream tcp nowait nobody /usr/bin/git-daemon + git-daemon --inetd --verbose + --syslog --export-all + --interpolated-path=/pub/%H%D + /pub/www.example.org/software + /pub/www.example.com/software + /software +------------------------------------------------ ++ +In this example, the root-level directory `/pub` will contain +a subdirectory for each virtual host name supported. +Further, both hosts advertise repositories simply as +`git://www.example.com/software/repo.git`. For pre-1.4.0 +clients, a symlink from `/software` into the appropriate +default repository could be made as well. + + Author ------ Written by Linus Torvalds , YOSHIFUJI Hideaki diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 63cd5dab3f..ca7d09dc0a 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -48,6 +48,10 @@ is given: - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository readable by all users. +By default, the configuration flag receive.denyNonFastforward is enabled +in shared repositories, so that you cannot force a non fast-forwarding push +into it. + -- diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index f9457d45ed..0dfadc2a32 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -73,6 +73,8 @@ packed and is served via a dumb transport. There are other real-world examples of using update and post-update hooks found in the Documentation/howto directory. +git-receive-pack honours the receive.denyNonFastforwards flag, which +tells it if updates to a ref should be denied if they are not fast-forwards. OPTIONS ------- diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 28966adbbc..00a95e249f 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -20,6 +20,7 @@ SYNOPSIS [ \--stdin ] [ \--topo-order ] [ \--parents ] + [ \--(author|committer|grep)= ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] [ \--bisect ] @@ -154,6 +155,16 @@ limiting may be applied. Limit the commits output to specified time range. +--author='pattern', --committer='pattern':: + + Limit the commits output to ones with author/committer + header lines that match the specified pattern. + +--grep='pattern':: + + Limit the commits output to ones with log message that + matches the specified pattern. + --remove-empty:: Stop when a given path disappears from the tree. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index b7b63f7136..1cfa3e342c 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -244,6 +244,18 @@ doing. repo-config key: svn.noignoreexternals +--ignore-nodate:: +Only used with the 'fetch' command. + +By default git-svn will crash if it tries to import a revision +from SVN which has '(no date)' listed as the date of the revision. +This is repository corruption on SVN's part, plain and simple. +But sometimes you really need those revisions anyway. + +If supplied git-svn will convert '(no date)' entries to the UNIX +epoch (midnight on Jan. 1, 1970). Yes, that's probably very wrong. +SVN was very wrong. + -- Basic Examples diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt index 1e1c7fa856..74a6fddd9a 100644 --- a/Documentation/git-tar-tree.txt +++ b/Documentation/git-tar-tree.txt @@ -12,6 +12,9 @@ SYNOPSIS DESCRIPTION ----------- +THIS COMMAND IS DEPRECATED. Use `git-archive` with `--format=tar` +option instead. + Creates a tar archive containing the tree structure for the named tree. When is specified it is added as a leading path to the files in the generated tar archive. diff --git a/Documentation/git-upload-tar.txt b/Documentation/git-upload-tar.txt deleted file mode 100644 index 394af62015..0000000000 --- a/Documentation/git-upload-tar.txt +++ /dev/null @@ -1,39 +0,0 @@ -git-upload-tar(1) -================= - -NAME ----- -git-upload-tar - Send tar archive - - -SYNOPSIS --------- -'git-upload-tar' - -DESCRIPTION ------------ -Invoked by 'git-tar-tree --remote' and sends a generated tar archive -to the other end over the git protocol. - -This command is usually not invoked directly by the end user. -The UI for the protocol is on the 'git-tar-tree' side, and the -program pair is meant to be used to get a tar archive from a -remote repository. - - -OPTIONS -------- -:: - The repository to get a tar archive from. - -Author ------- -Written by Junio C Hamano - -Documentation --------------- -Documentation by Junio C Hamano. - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git-zip-tree.txt b/Documentation/git-zip-tree.txt deleted file mode 100644 index 2e9d981247..0000000000 --- a/Documentation/git-zip-tree.txt +++ /dev/null @@ -1,67 +0,0 @@ -git-zip-tree(1) -=============== - -NAME ----- -git-zip-tree - Creates a ZIP archive of the files in the named tree - - -SYNOPSIS --------- -'git-zip-tree' [-0|...|-9] [ ] - -DESCRIPTION ------------ -Creates a ZIP archive containing the tree structure for the named tree. -When is specified it is added as a leading path to the files in the -generated ZIP archive. - -git-zip-tree behaves differently when given a tree ID versus when given -a commit ID or tag ID. In the first case the current time is used as -modification time of each file in the archive. In the latter case the -commit time as recorded in the referenced commit object is used instead. -Additionally the commit ID is stored as an archive comment. - -Currently git-zip-tree can handle only files and directories, symbolic -links are not supported. - -OPTIONS -------- - --0:: - Store the files instead of deflating them. - --9:: - Highest and slowest compression level. You can specify any - number from 1 to 9 to adjust compression speed and ratio. - -:: - The tree or commit to produce ZIP archive for. If it is - the object name of a commit object. - -:: - Leading path to the files in the resulting ZIP archive. - -EXAMPLES --------- -git zip-tree v1.4.0 git-1.4.0 >git-1.4.0.zip:: - - Create a ZIP file for v1.4.0 release. - -git zip-tree HEAD:Documentation/ git-docs >docs.zip:: - - Put everything in the current head's Documentation/ directory - into 'docs.zip', with the prefix 'git-docs/'. - -Author ------- -Written by Rene Scharfe. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list . - -GIT ---- -Part of the gitlink:git[7] suite - diff --git a/Documentation/git.txt b/Documentation/git.txt index 744c38dee3..2135b65516 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -247,10 +247,6 @@ gitlink:git-upload-pack[1]:: Invoked by 'git-fetch-pack' to push what are asked for. -gitlink:git-upload-tar[1]:: - Invoked by 'git-tar-tree --remote' to return the tar - archive the other end asked for. - High-level commands (porcelain) ------------------------------- @@ -270,6 +266,9 @@ gitlink:git-am[1]:: gitlink:git-applymbox[1]:: Apply patches from a mailbox, original version by Linus. +gitlink:git-archive[1]:: + Creates an archive of files from a named tree. + gitlink:git-bisect[1]:: Find the change that introduced a bug by binary search. diff --git a/Makefile b/Makefile index 8467447da9..c888c810bf 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,6 @@ all: # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. -# # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you # sufficient guarantee that no collisions between objects will ever happen. @@ -174,7 +172,7 @@ SCRIPT_PERL = \ git-send-email.perl git-svn.perl SCRIPT_PYTHON = \ - git-merge-recursive.py + git-merge-recursive-old.py SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ @@ -199,7 +197,7 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X \ git-pack-redundant$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ - git-merge-recur$X \ + git-merge-recursive$X \ $(EXTRA_PROGRAMS) # Empty... @@ -234,7 +232,7 @@ LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a LIB_H = \ - archive.h blob.h cache.h commit.h csum-file.h delta.h \ + archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h @@ -246,15 +244,17 @@ DIFF_OBJS = \ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ - date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \ + date.o diff-delta.o entry.o exec_cmd.o ident.o \ + interpolate.o \ + lockfile.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.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 \ - write_or_die.o trace.o list-objects.o \ + write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ - color.o wt-status.o + color.o wt-status.o archive-zip.o archive-tar.o BUILTIN_OBJS = \ builtin-add.o \ @@ -298,10 +298,8 @@ BUILTIN_OBJS = \ builtin-update-index.o \ builtin-update-ref.o \ builtin-upload-archive.o \ - builtin-upload-tar.o \ builtin-verify-pack.o \ - builtin-write-tree.o \ - builtin-zip-tree.o + builtin-write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -570,7 +568,8 @@ LIB_OBJS += $(COMPAT_OBJS) export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi +all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \ + git-merge-recur$X all: $(MAKE) -C templates @@ -585,6 +584,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS help.o: common-cmds.h +git-merge-recur$X: git-merge-recursive$X + rm -f $@ && ln git-merge-recursive$X $@ + $(BUILT_INS): git$X rm -f $@ && ln git$X $@ @@ -722,11 +724,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -merge-recursive.o path-list.o: path-list.h -git-merge-recur$X: merge-recursive.o path-list.o $(GITLIBS) - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) - $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) $(DIFF_OBJS): diffcore.h @@ -887,6 +884,7 @@ check-docs:: case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ git-merge-resolve | git-merge-stupid | git-merge-recur | \ + git-merge-recursive-old | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/archive-tar.c b/archive-tar.c new file mode 100644 index 0000000000..ff0f6e2929 --- /dev/null +++ b/archive-tar.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2005, 2006 Rene Scharfe + */ +#include +#include "cache.h" +#include "commit.h" +#include "strbuf.h" +#include "tar.h" +#include "builtin.h" +#include "archive.h" + +#define RECORDSIZE (512) +#define BLOCKSIZE (RECORDSIZE * 20) + +static char block[BLOCKSIZE]; +static unsigned long offset; + +static time_t archive_time; +static int tar_umask; +static int verbose; + +/* writes out the whole block, but only if it is full */ +static void write_if_needed(void) +{ + if (offset == BLOCKSIZE) { + write_or_die(1, block, BLOCKSIZE); + offset = 0; + } +} + +/* + * queues up writes, so that all our write(2) calls write exactly one + * full block; pads writes to RECORDSIZE + */ +static void write_blocked(const void *data, unsigned long size) +{ + const char *buf = data; + unsigned long tail; + + if (offset) { + unsigned long chunk = BLOCKSIZE - offset; + if (size < chunk) + chunk = size; + memcpy(block + offset, buf, chunk); + size -= chunk; + offset += chunk; + buf += chunk; + write_if_needed(); + } + while (size >= BLOCKSIZE) { + write_or_die(1, buf, BLOCKSIZE); + size -= BLOCKSIZE; + buf += BLOCKSIZE; + } + if (size) { + memcpy(block + offset, buf, size); + offset += size; + } + tail = offset % RECORDSIZE; + if (tail) { + memset(block + offset, 0, RECORDSIZE - tail); + offset += RECORDSIZE - tail; + } + write_if_needed(); +} + +/* + * The end of tar archives is marked by 2*512 nul bytes and after that + * follows the rest of the block (if any). + */ +static void write_trailer(void) +{ + int tail = BLOCKSIZE - offset; + memset(block + offset, 0, tail); + write_or_die(1, block, BLOCKSIZE); + if (tail < 2 * RECORDSIZE) { + memset(block, 0, offset); + write_or_die(1, block, BLOCKSIZE); + } +} + +static void strbuf_append_string(struct strbuf *sb, const char *s) +{ + int slen = strlen(s); + int total = sb->len + slen; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + memcpy(sb->buf + sb->len, s, slen); + sb->len = total; +} + +/* + * pax extended header records have the format "%u %s=%s\n". %u contains + * the size of the whole string (including the %u), the first %s is the + * keyword, the second one is the value. This function constructs such a + * string and appends it to a struct strbuf. + */ +static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, + const char *value, unsigned int valuelen) +{ + char *p; + int len, total, tmp; + + /* "%u %s=%s\n" */ + len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; + for (tmp = len; tmp > 9; tmp /= 10) + len++; + + total = sb->len + len; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + + p = sb->buf; + p += sprintf(p, "%u %s=", len, keyword); + memcpy(p, value, valuelen); + p += valuelen; + *p = '\n'; + sb->len = total; +} + +static unsigned int ustar_header_chksum(const struct ustar_header *header) +{ + char *p = (char *)header; + unsigned int chksum = 0; + while (p < header->chksum) + chksum += *p++; + chksum += sizeof(header->chksum) * ' '; + p += sizeof(header->chksum); + while (p < (char *)header + sizeof(struct ustar_header)) + chksum += *p++; + return chksum; +} + +static int get_path_prefix(const struct strbuf *path, int maxlen) +{ + int i = path->len; + if (i > maxlen) + i = maxlen; + do { + i--; + } while (i > 0 && path->buf[i] != '/'); + return i; +} + +static void write_entry(const unsigned char *sha1, struct strbuf *path, + unsigned int mode, void *buffer, unsigned long size) +{ + struct ustar_header header; + struct strbuf ext_header; + + memset(&header, 0, sizeof(header)); + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + + if (!sha1) { + *header.typeflag = TYPEFLAG_GLOBAL_HEADER; + mode = 0100666; + strcpy(header.name, "pax_global_header"); + } else if (!path) { + *header.typeflag = TYPEFLAG_EXT_HEADER; + mode = 0100666; + sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + } else { + if (verbose) + fprintf(stderr, "%.*s\n", path->len, path->buf); + if (S_ISDIR(mode)) { + *header.typeflag = TYPEFLAG_DIR; + mode = (mode | 0777) & ~tar_umask; + } else if (S_ISLNK(mode)) { + *header.typeflag = TYPEFLAG_LNK; + mode |= 0777; + } else if (S_ISREG(mode)) { + *header.typeflag = TYPEFLAG_REG; + mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", + mode, sha1_to_hex(sha1)); + return; + } + if (path->len > sizeof(header.name)) { + int plen = get_path_prefix(path, sizeof(header.prefix)); + int rest = path->len - plen - 1; + if (plen > 0 && rest <= sizeof(header.name)) { + memcpy(header.prefix, path->buf, plen); + memcpy(header.name, path->buf + plen + 1, rest); + } else { + sprintf(header.name, "%s.data", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "path", + path->buf, path->len); + } + } else + memcpy(header.name, path->buf, path->len); + } + + if (S_ISLNK(mode) && buffer) { + if (size > sizeof(header.linkname)) { + sprintf(header.linkname, "see %s.paxheader", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "linkpath", + buffer, size); + } else + memcpy(header.linkname, buffer, size); + } + + sprintf(header.mode, "%07o", mode & 07777); + sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); + sprintf(header.mtime, "%011lo", archive_time); + + /* XXX: should we provide more meaningful info here? */ + sprintf(header.uid, "%07o", 0); + sprintf(header.gid, "%07o", 0); + strlcpy(header.uname, "git", sizeof(header.uname)); + strlcpy(header.gname, "git", sizeof(header.gname)); + sprintf(header.devmajor, "%07o", 0); + sprintf(header.devminor, "%07o", 0); + + memcpy(header.magic, "ustar", 6); + memcpy(header.version, "00", 2); + + sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); + + if (ext_header.len > 0) { + write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); + } + write_blocked(&header, sizeof(header)); + if (S_ISREG(mode) && buffer && size > 0) + write_blocked(buffer, size); +} + +static void write_global_extended_header(const unsigned char *sha1) +{ + struct strbuf ext_header; + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); + write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); +} + +static int git_tar_config(const char *var, const char *value) +{ + if (!strcmp(var, "tar.umask")) { + if (!strcmp(value, "user")) { + tar_umask = umask(0); + umask(tar_umask); + } else { + tar_umask = git_config_int(var, value); + } + return 0; + } + return git_default_config(var, value); +} + +static int write_tar_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + static struct strbuf path; + int filenamelen = strlen(filename); + void *buffer; + char type[20]; + unsigned long size; + + if (!path.alloc) { + path.buf = xmalloc(PATH_MAX); + path.alloc = PATH_MAX; + path.len = path.eof = 0; + } + if (path.alloc < baselen + filenamelen) { + free(path.buf); + path.buf = xmalloc(baselen + filenamelen); + path.alloc = baselen + filenamelen; + } + memcpy(path.buf, base, baselen); + memcpy(path.buf + baselen, filename, filenamelen); + path.len = baselen + filenamelen; + if (S_ISDIR(mode)) { + strbuf_append_string(&path, "/"); + buffer = NULL; + size = 0; + } else { + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + } + + write_entry(sha1, &path, mode, buffer, size); + free(buffer); + + return READ_TREE_RECURSIVE; +} + +int write_tar_archive(struct archiver_args *args) +{ + int plen = args->base ? strlen(args->base) : 0; + + git_config(git_tar_config); + + archive_time = args->time; + verbose = args->verbose; + + if (args->commit_sha1) + write_global_extended_header(args->commit_sha1); + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_tar_entry); + write_trailer(); + + return 0; +} diff --git a/archive-zip.c b/archive-zip.c new file mode 100644 index 0000000000..3ffdad68d1 --- /dev/null +++ b/archive-zip.c @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2006 Rene Scharfe + */ +#include +#include "cache.h" +#include "commit.h" +#include "blob.h" +#include "tree.h" +#include "quote.h" +#include "builtin.h" +#include "archive.h" + +static int verbose; +static int zip_date; +static int zip_time; + +static unsigned char *zip_dir; +static unsigned int zip_dir_size; + +static unsigned int zip_offset; +static unsigned int zip_dir_offset; +static unsigned int zip_dir_entries; + +#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) + +struct zip_local_header { + unsigned char magic[4]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; +}; + +struct zip_dir_header { + unsigned char magic[4]; + unsigned char creator_version[2]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; + unsigned char comment_length[2]; + unsigned char disk[2]; + unsigned char attr1[2]; + unsigned char attr2[4]; + unsigned char offset[4]; +}; + +struct zip_dir_trailer { + unsigned char magic[4]; + unsigned char disk[2]; + unsigned char directory_start_disk[2]; + unsigned char entries_on_this_disk[2]; + unsigned char entries[2]; + unsigned char size[4]; + unsigned char offset[4]; + unsigned char comment_length[2]; +}; + +static void copy_le16(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); +} + +static void copy_le32(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); + dest[2] = 0xff & (n >> 020); + dest[3] = 0xff & (n >> 030); +} + +static void *zlib_deflate(void *data, unsigned long size, + unsigned long *compressed_size) +{ + z_stream stream; + unsigned long maxsize; + void *buffer; + int result; + + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, zlib_compression_level); + maxsize = deflateBound(&stream, size); + buffer = xmalloc(maxsize); + + stream.next_in = data; + stream.avail_in = size; + stream.next_out = buffer; + stream.avail_out = maxsize; + + do { + result = deflate(&stream, Z_FINISH); + } while (result == Z_OK); + + if (result != Z_STREAM_END) { + free(buffer); + return NULL; + } + + deflateEnd(&stream); + *compressed_size = stream.total_out; + + return buffer; +} + +static char *construct_path(const char *base, int baselen, + const char *filename, int isdir, int *pathlen) +{ + int filenamelen = strlen(filename); + int len = baselen + filenamelen; + char *path, *p; + + if (isdir) + len++; + p = path = xmalloc(len + 1); + + memcpy(p, base, baselen); + p += baselen; + memcpy(p, filename, filenamelen); + p += filenamelen; + if (isdir) + *p++ = '/'; + *p = '\0'; + + *pathlen = len; + + return path; +} + +static int write_zip_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + struct zip_local_header header; + struct zip_dir_header dirent; + unsigned long compressed_size; + unsigned long uncompressed_size; + unsigned long crc; + unsigned long direntsize; + unsigned long size; + int method; + int result = -1; + int pathlen; + unsigned char *out; + char *path; + char type[20]; + void *buffer = NULL; + void *deflated = NULL; + + crc = crc32(0, Z_NULL, 0); + + path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); + if (verbose) + fprintf(stderr, "%s\n", path); + if (pathlen > 0xffff) { + error("path too long (%d chars, SHA1: %s): %s", pathlen, + sha1_to_hex(sha1), path); + goto out; + } + + if (S_ISDIR(mode)) { + method = 0; + result = READ_TREE_RECURSIVE; + out = NULL; + uncompressed_size = 0; + compressed_size = 0; + } else if (S_ISREG(mode)) { + method = zlib_compression_level == 0 ? 0 : 8; + result = 0; + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + crc = crc32(crc, buffer, size); + out = buffer; + uncompressed_size = size; + compressed_size = size; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", mode, + sha1_to_hex(sha1)); + goto out; + } + + if (method == 8) { + deflated = zlib_deflate(buffer, size, &compressed_size); + if (deflated && compressed_size - 6 < size) { + /* ZLIB --> raw compressed data (see RFC 1950) */ + /* CMF and FLG ... */ + out = (unsigned char *)deflated + 2; + compressed_size -= 6; /* ... and ADLER32 */ + } else { + method = 0; + compressed_size = size; + } + } + + /* make sure we have enough free space in the dictionary */ + direntsize = sizeof(struct zip_dir_header) + pathlen; + while (zip_dir_size < zip_dir_offset + direntsize) { + zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; + zip_dir = xrealloc(zip_dir, zip_dir_size); + } + + copy_le32(dirent.magic, 0x02014b50); + copy_le16(dirent.creator_version, 0); + copy_le16(dirent.version, 20); + copy_le16(dirent.flags, 0); + copy_le16(dirent.compression_method, method); + copy_le16(dirent.mtime, zip_time); + copy_le16(dirent.mdate, zip_date); + copy_le32(dirent.crc32, crc); + copy_le32(dirent.compressed_size, compressed_size); + copy_le32(dirent.size, uncompressed_size); + copy_le16(dirent.filename_length, pathlen); + copy_le16(dirent.extra_length, 0); + copy_le16(dirent.comment_length, 0); + copy_le16(dirent.disk, 0); + copy_le16(dirent.attr1, 0); + copy_le32(dirent.attr2, 0); + copy_le32(dirent.offset, zip_offset); + memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); + zip_dir_offset += sizeof(struct zip_dir_header); + memcpy(zip_dir + zip_dir_offset, path, pathlen); + zip_dir_offset += pathlen; + zip_dir_entries++; + + copy_le32(header.magic, 0x04034b50); + copy_le16(header.version, 20); + copy_le16(header.flags, 0); + copy_le16(header.compression_method, method); + copy_le16(header.mtime, zip_time); + copy_le16(header.mdate, zip_date); + copy_le32(header.crc32, crc); + copy_le32(header.compressed_size, compressed_size); + copy_le32(header.size, uncompressed_size); + copy_le16(header.filename_length, pathlen); + copy_le16(header.extra_length, 0); + write_or_die(1, &header, sizeof(struct zip_local_header)); + zip_offset += sizeof(struct zip_local_header); + write_or_die(1, path, pathlen); + zip_offset += pathlen; + if (compressed_size > 0) { + write_or_die(1, out, compressed_size); + zip_offset += compressed_size; + } + +out: + free(buffer); + free(deflated); + free(path); + + return result; +} + +static void write_zip_trailer(const unsigned char *sha1) +{ + struct zip_dir_trailer trailer; + + copy_le32(trailer.magic, 0x06054b50); + copy_le16(trailer.disk, 0); + copy_le16(trailer.directory_start_disk, 0); + copy_le16(trailer.entries_on_this_disk, zip_dir_entries); + copy_le16(trailer.entries, zip_dir_entries); + copy_le32(trailer.size, zip_dir_offset); + copy_le32(trailer.offset, zip_offset); + copy_le16(trailer.comment_length, sha1 ? 40 : 0); + + write_or_die(1, zip_dir, zip_dir_offset); + write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); + if (sha1) + write_or_die(1, sha1_to_hex(sha1), 40); +} + +static void dos_time(time_t *time, int *dos_date, int *dos_time) +{ + struct tm *t = localtime(time); + + *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + + (t->tm_year + 1900 - 1980) * 512; + *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; +} + +int write_zip_archive(struct archiver_args *args) +{ + int plen = strlen(args->base); + + dos_time(&args->time, &zip_date, &zip_time); + + zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); + zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; + verbose = args->verbose; + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_zip_entry); + write_zip_trailer(args->commit_sha1); + + free(zip_dir); + + return 0; +} + +void *parse_extra_zip_args(int argc, const char **argv) +{ + for (; argc > 0; argc--, argv++) { + const char *arg = argv[0]; + + if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') + zlib_compression_level = arg[1] - '0'; + else + die("Unknown argument for zip format: %s", arg); + } + return NULL; +} diff --git a/builtin-grep.c b/builtin-grep.c index ed87a5550c..4205e5d38d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -11,6 +11,7 @@ #include "tree-walk.h" #include "builtin.h" #include +#include "grep.h" #include #include @@ -82,498 +83,6 @@ static int pathspec_matches(const char **paths, const char *name) return 0; } -enum grep_pat_token { - GREP_PATTERN, - GREP_AND, - GREP_OPEN_PAREN, - GREP_CLOSE_PAREN, - GREP_NOT, - GREP_OR, -}; - -struct grep_pat { - struct grep_pat *next; - const char *origin; - int no; - enum grep_pat_token token; - const char *pattern; - regex_t regexp; -}; - -enum grep_expr_node { - GREP_NODE_ATOM, - GREP_NODE_NOT, - GREP_NODE_AND, - GREP_NODE_OR, -}; - -struct grep_expr { - enum grep_expr_node node; - union { - struct grep_pat *atom; - struct grep_expr *unary; - struct { - struct grep_expr *left; - struct grep_expr *right; - } binary; - } u; -}; - -struct grep_opt { - struct grep_pat *pattern_list; - struct grep_pat **pattern_tail; - struct grep_expr *pattern_expression; - int prefix_length; - regex_t regexp; - unsigned linenum:1; - unsigned invert:1; - unsigned name_only:1; - unsigned unmatch_name_only:1; - unsigned count:1; - unsigned word_regexp:1; - unsigned fixed:1; -#define GREP_BINARY_DEFAULT 0 -#define GREP_BINARY_NOMATCH 1 -#define GREP_BINARY_TEXT 2 - unsigned binary:2; - unsigned extended:1; - unsigned relative:1; - unsigned pathname:1; - int regflags; - unsigned pre_context; - unsigned post_context; -}; - -static void add_pattern(struct grep_opt *opt, const char *pat, - const char *origin, int no, enum grep_pat_token t) -{ - struct grep_pat *p = xcalloc(1, sizeof(*p)); - p->pattern = pat; - p->origin = origin; - p->no = no; - p->token = t; - *opt->pattern_tail = p; - opt->pattern_tail = &p->next; - p->next = NULL; -} - -static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) -{ - int err = regcomp(&p->regexp, p->pattern, opt->regflags); - if (err) { - char errbuf[1024]; - char where[1024]; - if (p->no) - sprintf(where, "In '%s' at %d, ", - p->origin, p->no); - else if (p->origin) - sprintf(where, "%s, ", p->origin); - else - where[0] = 0; - regerror(err, &p->regexp, errbuf, 1024); - regfree(&p->regexp); - die("%s'%s': %s", where, p->pattern, errbuf); - } -} - -static struct grep_expr *compile_pattern_expr(struct grep_pat **); -static struct grep_expr *compile_pattern_atom(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x; - - p = *list; - switch (p->token) { - case GREP_PATTERN: /* atom */ - x = xcalloc(1, sizeof (struct grep_expr)); - x->node = GREP_NODE_ATOM; - x->u.atom = p; - *list = p->next; - return x; - case GREP_OPEN_PAREN: - *list = p->next; - x = compile_pattern_expr(list); - if (!x) - return NULL; - if (!*list || (*list)->token != GREP_CLOSE_PAREN) - die("unmatched parenthesis"); - *list = (*list)->next; - return x; - default: - return NULL; - } -} - -static struct grep_expr *compile_pattern_not(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x; - - p = *list; - switch (p->token) { - case GREP_NOT: - if (!p->next) - die("--not not followed by pattern expression"); - *list = p->next; - x = xcalloc(1, sizeof (struct grep_expr)); - x->node = GREP_NODE_NOT; - x->u.unary = compile_pattern_not(list); - if (!x->u.unary) - die("--not followed by non pattern expression"); - return x; - default: - return compile_pattern_atom(list); - } -} - -static struct grep_expr *compile_pattern_and(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x, *y, *z; - - x = compile_pattern_not(list); - p = *list; - if (p && p->token == GREP_AND) { - if (!p->next) - die("--and not followed by pattern expression"); - *list = p->next; - y = compile_pattern_and(list); - if (!y) - die("--and not followed by pattern expression"); - z = xcalloc(1, sizeof (struct grep_expr)); - z->node = GREP_NODE_AND; - z->u.binary.left = x; - z->u.binary.right = y; - return z; - } - return x; -} - -static struct grep_expr *compile_pattern_or(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x, *y, *z; - - x = compile_pattern_and(list); - p = *list; - if (x && p && p->token != GREP_CLOSE_PAREN) { - y = compile_pattern_or(list); - if (!y) - die("not a pattern expression %s", p->pattern); - z = xcalloc(1, sizeof (struct grep_expr)); - z->node = GREP_NODE_OR; - z->u.binary.left = x; - z->u.binary.right = y; - return z; - } - return x; -} - -static struct grep_expr *compile_pattern_expr(struct grep_pat **list) -{ - return compile_pattern_or(list); -} - -static void compile_patterns(struct grep_opt *opt) -{ - struct grep_pat *p; - - /* First compile regexps */ - for (p = opt->pattern_list; p; p = p->next) { - if (p->token == GREP_PATTERN) - compile_regexp(p, opt); - else - opt->extended = 1; - } - - if (!opt->extended) - return; - - /* Then bundle them up in an expression. - * A classic recursive descent parser would do. - */ - p = opt->pattern_list; - opt->pattern_expression = compile_pattern_expr(&p); - if (p) - die("incomplete pattern expression: %s", p->pattern); -} - -static char *end_of_line(char *cp, unsigned long *left) -{ - unsigned long l = *left; - while (l && *cp != '\n') { - l--; - cp++; - } - *left = l; - return cp; -} - -static int word_char(char ch) -{ - return isalnum(ch) || ch == '_'; -} - -static void show_line(struct grep_opt *opt, const char *bol, const char *eol, - const char *name, unsigned lno, char sign) -{ - if (opt->pathname) - printf("%s%c", name, sign); - if (opt->linenum) - printf("%d%c", lno, sign); - printf("%.*s\n", (int)(eol-bol), bol); -} - -/* - * NEEDSWORK: share code with diff.c - */ -#define FIRST_FEW_BYTES 8000 -static int buffer_is_binary(const char *ptr, unsigned long size) -{ - if (FIRST_FEW_BYTES < size) - size = FIRST_FEW_BYTES; - return !!memchr(ptr, 0, size); -} - -static int fixmatch(const char *pattern, char *line, regmatch_t *match) -{ - char *hit = strstr(line, pattern); - if (!hit) { - match->rm_so = match->rm_eo = -1; - return REG_NOMATCH; - } - else { - match->rm_so = hit - line; - match->rm_eo = match->rm_so + strlen(pattern); - return 0; - } -} - -static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) -{ - int hit = 0; - int at_true_bol = 1; - regmatch_t pmatch[10]; - - again: - if (!opt->fixed) { - regex_t *exp = &p->regexp; - hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), - pmatch, 0); - } - else { - hit = !fixmatch(p->pattern, bol, pmatch); - } - - if (hit && opt->word_regexp) { - if ((pmatch[0].rm_so < 0) || - (eol - bol) <= pmatch[0].rm_so || - (pmatch[0].rm_eo < 0) || - (eol - bol) < pmatch[0].rm_eo) - die("regexp returned nonsense"); - - /* Match beginning must be either beginning of the - * line, or at word boundary (i.e. the last char must - * not be a word char). Similarly, match end must be - * either end of the line, or at word boundary - * (i.e. the next char must not be a word char). - */ - if ( ((pmatch[0].rm_so == 0 && at_true_bol) || - !word_char(bol[pmatch[0].rm_so-1])) && - ((pmatch[0].rm_eo == (eol-bol)) || - !word_char(bol[pmatch[0].rm_eo])) ) - ; - else - hit = 0; - - if (!hit && pmatch[0].rm_so + bol + 1 < eol) { - /* There could be more than one match on the - * line, and the first match might not be - * strict word match. But later ones could be! - */ - bol = pmatch[0].rm_so + bol + 1; - at_true_bol = 0; - goto again; - } - } - return hit; -} - -static int match_expr_eval(struct grep_opt *opt, - struct grep_expr *x, - char *bol, char *eol) -{ - switch (x->node) { - case GREP_NODE_ATOM: - return match_one_pattern(opt, x->u.atom, bol, eol); - break; - case GREP_NODE_NOT: - return !match_expr_eval(opt, x->u.unary, bol, eol); - case GREP_NODE_AND: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) && - match_expr_eval(opt, x->u.binary.right, bol, eol)); - case GREP_NODE_OR: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) || - match_expr_eval(opt, x->u.binary.right, bol, eol)); - } - die("Unexpected node type (internal error) %d\n", x->node); -} - -static int match_expr(struct grep_opt *opt, char *bol, char *eol) -{ - struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol); -} - -static int match_line(struct grep_opt *opt, char *bol, char *eol) -{ - struct grep_pat *p; - if (opt->extended) - return match_expr(opt, bol, eol); - for (p = opt->pattern_list; p; p = p->next) { - if (match_one_pattern(opt, p, bol, eol)) - return 1; - } - return 0; -} - -static int grep_buffer(struct grep_opt *opt, const char *name, - char *buf, unsigned long size) -{ - char *bol = buf; - unsigned long left = size; - unsigned lno = 1; - struct pre_context_line { - char *bol; - char *eol; - } *prev = NULL, *pcl; - unsigned last_hit = 0; - unsigned last_shown = 0; - int binary_match_only = 0; - const char *hunk_mark = ""; - unsigned count = 0; - - if (buffer_is_binary(buf, size)) { - switch (opt->binary) { - case GREP_BINARY_DEFAULT: - binary_match_only = 1; - break; - case GREP_BINARY_NOMATCH: - return 0; /* Assume unmatch */ - break; - default: - break; - } - } - - if (opt->pre_context) - prev = xcalloc(opt->pre_context, sizeof(*prev)); - if (opt->pre_context || opt->post_context) - hunk_mark = "--\n"; - - while (left) { - char *eol, ch; - int hit = 0; - - eol = end_of_line(bol, &left); - ch = *eol; - *eol = 0; - - hit = match_line(opt, bol, eol); - - /* "grep -v -e foo -e bla" should list lines - * that do not have either, so inversion should - * be done outside. - */ - if (opt->invert) - hit = !hit; - if (opt->unmatch_name_only) { - if (hit) - return 0; - goto next_line; - } - if (hit) { - count++; - if (binary_match_only) { - printf("Binary file %s matches\n", name); - return 1; - } - if (opt->name_only) { - printf("%s\n", name); - return 1; - } - /* Hit at this line. If we haven't shown the - * pre-context lines, we would need to show them. - * When asked to do "count", this still show - * the context which is nonsense, but the user - * deserves to get that ;-). - */ - if (opt->pre_context) { - unsigned from; - if (opt->pre_context < lno) - from = lno - opt->pre_context; - else - from = 1; - if (from <= last_shown) - from = last_shown + 1; - if (last_shown && from != last_shown + 1) - printf(hunk_mark); - while (from < lno) { - pcl = &prev[lno-from-1]; - show_line(opt, pcl->bol, pcl->eol, - name, from, '-'); - from++; - } - last_shown = lno-1; - } - if (last_shown && lno != last_shown + 1) - printf(hunk_mark); - if (!opt->count) - show_line(opt, bol, eol, name, lno, ':'); - last_shown = last_hit = lno; - } - else if (last_hit && - lno <= last_hit + opt->post_context) { - /* If the last hit is within the post context, - * we need to show this line. - */ - if (last_shown && lno != last_shown + 1) - printf(hunk_mark); - show_line(opt, bol, eol, name, lno, '-'); - last_shown = lno; - } - if (opt->pre_context) { - memmove(prev+1, prev, - (opt->pre_context-1) * sizeof(*prev)); - prev->bol = bol; - prev->eol = eol; - } - - next_line: - *eol = ch; - bol = eol + 1; - if (!left) - break; - left--; - lno++; - } - - if (opt->unmatch_name_only) { - /* We did not see any hit, so we want to show this */ - printf("%s\n", name); - return 1; - } - - /* NEEDSWORK: - * The real "grep -c foo *.c" gives many "bar.c:0" lines, - * which feels mostly useless but sometimes useful. Maybe - * make it another option? For now suppress them. - */ - if (opt->count && count) - printf("%s:%u\n", name, count); - return !!last_hit; -} - static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len) { unsigned long size; @@ -816,6 +325,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) else hit |= grep_file(opt, ce->name); } + free_grep_patterns(opt); return hit; } @@ -1055,8 +565,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* ignore empty line like grep does */ if (!buf[0]) continue; - add_pattern(&opt, xstrdup(buf), argv[1], ++lno, - GREP_PATTERN); + append_grep_pattern(&opt, xstrdup(buf), + argv[1], ++lno, + GREP_PATTERN); } fclose(patterns); argv++; @@ -1064,27 +575,32 @@ int cmd_grep(int argc, const char **argv, const char *prefix) continue; } if (!strcmp("--not", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_NOT); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_NOT); continue; } if (!strcmp("--and", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_AND); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_AND); continue; } if (!strcmp("--or", arg)) continue; /* no-op */ if (!strcmp("(", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_OPEN_PAREN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_OPEN_PAREN); continue; } if (!strcmp(")", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_CLOSE_PAREN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_CLOSE_PAREN); continue; } if (!strcmp("-e", arg)) { if (1 < argc) { - add_pattern(&opt, argv[1], "-e option", 0, - GREP_PATTERN); + append_grep_pattern(&opt, argv[1], + "-e option", 0, + GREP_PATTERN); argv++; argc--; continue; @@ -1106,8 +622,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* First unrecognized non-option token */ if (!opt.pattern_list) { - add_pattern(&opt, arg, "command line", 0, - GREP_PATTERN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_PATTERN); break; } else { @@ -1124,8 +640,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) die("no pattern given."); if ((opt.regflags != REG_NEWLINE) && opt.fixed) die("cannot mix --fixed-strings and regexp"); - if (!opt.fixed) - compile_patterns(&opt); + compile_grep_patterns(&opt); /* Check revs and then paths */ for (i = 1; i < argc; i++) { @@ -1180,5 +695,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (grep_object(&opt, paths, real_obj, list.objects[i].name)) hit = 1; } + free_grep_patterns(&opt); return !hit; } diff --git a/builtin-init-db.c b/builtin-init-db.c index 5085018e46..c3ed1ce492 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -311,6 +311,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) */ sprintf(buf, "%d", shared_repository); git_config_set("core.sharedrepository", buf); + git_config_set("receive.denyNonFastforwards", "true"); } return 0; diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 0c65f93145..b8d7dbc0b7 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -451,17 +451,6 @@ static int read_one_header_line(char *line, int sz, FILE *in) return ofs; } -static unsigned hexval(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return ~0; -} - static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047) { int c; diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 8d7a1209d5..96c069a81d 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -597,15 +597,15 @@ static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclud if (!exclude) { for (p = packed_git; p; p = p->next) { - struct pack_entry e; - if (find_pack_entry_one(sha1, &e, p)) { + unsigned long offset = find_pack_entry_one(sha1, p); + if (offset) { if (incremental) return 0; if (local && !p->pack_local) return 0; if (!found_pack) { - found_offset = e.offset; - found_pack = e.p; + found_offset = offset; + found_pack = p; } } } diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 1f3333da38..fb7fc92145 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -269,7 +269,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); - save_commit_buffer = revs.verbose_header; + save_commit_buffer = revs.verbose_header || revs.grep_filter; track_object_refs = 0; if (bisect_list) revs.limited = 1; diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 437eb726a9..4d4cfec878 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -4,413 +4,68 @@ #include #include "cache.h" #include "commit.h" -#include "strbuf.h" #include "tar.h" #include "builtin.h" -#include "pkt-line.h" -#include "archive.h" - -#define RECORDSIZE (512) -#define BLOCKSIZE (RECORDSIZE * 20) +#include "quote.h" static const char tar_tree_usage[] = -"git-tar-tree [--remote=] [basedir]"; - -static char block[BLOCKSIZE]; -static unsigned long offset; - -static time_t archive_time; -static int tar_umask; -static int verbose; - -/* writes out the whole block, but only if it is full */ -static void write_if_needed(void) -{ - if (offset == BLOCKSIZE) { - write_or_die(1, block, BLOCKSIZE); - offset = 0; - } -} - -/* - * queues up writes, so that all our write(2) calls write exactly one - * full block; pads writes to RECORDSIZE - */ -static void write_blocked(const void *data, unsigned long size) -{ - const char *buf = data; - unsigned long tail; - - if (offset) { - unsigned long chunk = BLOCKSIZE - offset; - if (size < chunk) - chunk = size; - memcpy(block + offset, buf, chunk); - size -= chunk; - offset += chunk; - buf += chunk; - write_if_needed(); - } - while (size >= BLOCKSIZE) { - write_or_die(1, buf, BLOCKSIZE); - size -= BLOCKSIZE; - buf += BLOCKSIZE; - } - if (size) { - memcpy(block + offset, buf, size); - offset += size; - } - tail = offset % RECORDSIZE; - if (tail) { - memset(block + offset, 0, RECORDSIZE - tail); - offset += RECORDSIZE - tail; - } - write_if_needed(); -} - -/* - * The end of tar archives is marked by 2*512 nul bytes and after that - * follows the rest of the block (if any). - */ -static void write_trailer(void) -{ - int tail = BLOCKSIZE - offset; - memset(block + offset, 0, tail); - write_or_die(1, block, BLOCKSIZE); - if (tail < 2 * RECORDSIZE) { - memset(block, 0, offset); - write_or_die(1, block, BLOCKSIZE); - } -} - -static void strbuf_append_string(struct strbuf *sb, const char *s) -{ - int slen = strlen(s); - int total = sb->len + slen; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - memcpy(sb->buf + sb->len, s, slen); - sb->len = total; -} - -/* - * pax extended header records have the format "%u %s=%s\n". %u contains - * the size of the whole string (including the %u), the first %s is the - * keyword, the second one is the value. This function constructs such a - * string and appends it to a struct strbuf. - */ -static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, - const char *value, unsigned int valuelen) -{ - char *p; - int len, total, tmp; - - /* "%u %s=%s\n" */ - len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; - for (tmp = len; tmp > 9; tmp /= 10) - len++; - - total = sb->len + len; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - - p = sb->buf; - p += sprintf(p, "%u %s=", len, keyword); - memcpy(p, value, valuelen); - p += valuelen; - *p = '\n'; - sb->len = total; -} - -static unsigned int ustar_header_chksum(const struct ustar_header *header) -{ - char *p = (char *)header; - unsigned int chksum = 0; - while (p < header->chksum) - chksum += *p++; - chksum += sizeof(header->chksum) * ' '; - p += sizeof(header->chksum); - while (p < (char *)header + sizeof(struct ustar_header)) - chksum += *p++; - return chksum; -} - -static int get_path_prefix(const struct strbuf *path, int maxlen) -{ - int i = path->len; - if (i > maxlen) - i = maxlen; - do { - i--; - } while (i > 0 && path->buf[i] != '/'); - return i; -} - -static void write_entry(const unsigned char *sha1, struct strbuf *path, - unsigned int mode, void *buffer, unsigned long size) -{ - struct ustar_header header; - struct strbuf ext_header; - - memset(&header, 0, sizeof(header)); - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - - if (!sha1) { - *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666; - strcpy(header.name, "pax_global_header"); - } else if (!path) { - *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666; - sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); - } else { - if (verbose) - fprintf(stderr, "%.*s\n", path->len, path->buf); - if (S_ISDIR(mode)) { - *header.typeflag = TYPEFLAG_DIR; - mode = (mode | 0777) & ~tar_umask; - } else if (S_ISLNK(mode)) { - *header.typeflag = TYPEFLAG_LNK; - mode |= 0777; - } else if (S_ISREG(mode)) { - *header.typeflag = TYPEFLAG_REG; - mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", - mode, sha1_to_hex(sha1)); - return; - } - if (path->len > sizeof(header.name)) { - int plen = get_path_prefix(path, sizeof(header.prefix)); - int rest = path->len - plen - 1; - if (plen > 0 && rest <= sizeof(header.name)) { - memcpy(header.prefix, path->buf, plen); - memcpy(header.name, path->buf + plen + 1, rest); - } else { - sprintf(header.name, "%s.data", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "path", - path->buf, path->len); - } - } else - memcpy(header.name, path->buf, path->len); - } - - if (S_ISLNK(mode) && buffer) { - if (size > sizeof(header.linkname)) { - sprintf(header.linkname, "see %s.paxheader", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "linkpath", - buffer, size); - } else - memcpy(header.linkname, buffer, size); - } - - sprintf(header.mode, "%07o", mode & 07777); - sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header.mtime, "%011lo", archive_time); - - /* XXX: should we provide more meaningful info here? */ - sprintf(header.uid, "%07o", 0); - sprintf(header.gid, "%07o", 0); - strlcpy(header.uname, "git", sizeof(header.uname)); - strlcpy(header.gname, "git", sizeof(header.gname)); - sprintf(header.devmajor, "%07o", 0); - sprintf(header.devminor, "%07o", 0); - - memcpy(header.magic, "ustar", 6); - memcpy(header.version, "00", 2); - - sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); - - if (ext_header.len > 0) { - write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); - } - write_blocked(&header, sizeof(header)); - if (S_ISREG(mode) && buffer && size > 0) - write_blocked(buffer, size); -} - -static void write_global_extended_header(const unsigned char *sha1) -{ - struct strbuf ext_header; - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); - write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); -} - -static int git_tar_config(const char *var, const char *value) -{ - if (!strcmp(var, "tar.umask")) { - if (!strcmp(value, "user")) { - tar_umask = umask(0); - umask(tar_umask); - } else { - tar_umask = git_config_int(var, value); - } - return 0; - } - return git_default_config(var, value); -} - -static int generate_tar(int argc, const char **argv, const char *prefix) -{ - struct archiver_args args; - int result; - char *base = NULL; - - git_config(git_tar_config); - - memset(&args, 0, sizeof(args)); - if (argc != 2 && argc != 3) - usage(tar_tree_usage); - if (argc == 3) { - int baselen = strlen(argv[2]); - base = xmalloc(baselen + 2); - memcpy(base, argv[2], baselen); - base[baselen] = '/'; - base[baselen + 1] = '\0'; - } - args.base = base; - parse_treeish_arg(argv + 1, &args, NULL); - - result = write_tar_archive(&args); - free(base); - - return result; -} - -static int write_tar_entry(const unsigned char *sha1, - const char *base, int baselen, - const char *filename, unsigned mode, int stage) -{ - static struct strbuf path; - int filenamelen = strlen(filename); - void *buffer; - char type[20]; - unsigned long size; - - if (!path.alloc) { - path.buf = xmalloc(PATH_MAX); - path.alloc = PATH_MAX; - path.len = path.eof = 0; - } - if (path.alloc < baselen + filenamelen) { - free(path.buf); - path.buf = xmalloc(baselen + filenamelen); - path.alloc = baselen + filenamelen; - } - memcpy(path.buf, base, baselen); - memcpy(path.buf + baselen, filename, filenamelen); - path.len = baselen + filenamelen; - if (S_ISDIR(mode)) { - strbuf_append_string(&path, "/"); - buffer = NULL; - size = 0; - } else { - buffer = read_sha1_file(sha1, type, &size); - if (!buffer) - die("cannot read %s", sha1_to_hex(sha1)); - } - - write_entry(sha1, &path, mode, buffer, size); - free(buffer); - - return READ_TREE_RECURSIVE; -} - -int write_tar_archive(struct archiver_args *args) -{ - int plen = args->base ? strlen(args->base) : 0; - - git_config(git_tar_config); - - archive_time = args->time; - verbose = args->verbose; - - if (args->commit_sha1) - write_global_extended_header(args->commit_sha1); - - if (args->base && plen > 0 && args->base[plen - 1] == '/') { - char *base = xstrdup(args->base); - int baselen = strlen(base); - - while (baselen > 0 && base[baselen - 1] == '/') - base[--baselen] = '\0'; - write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); - free(base); - } - read_tree_recursive(args->tree, args->base, plen, 0, - args->pathspec, write_tar_entry); - write_trailer(); - - 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= */ - url = xstrdup(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; -} +"git-tar-tree [--remote=] [basedir]\n" +"*** Note that this command is now deprecated; use git-archive instead."; int cmd_tar_tree(int argc, const char **argv, const char *prefix) { - if (argc < 2) + /* + * git-tar-tree is now a wrapper around git-archive --format=tar + * + * $0 --remote= arg... ==> + * git-archive --format=tar --remote= arg... + * $0 tree-ish ==> + * git-archive --format=tar tree-ish + * $0 tree-ish basedir ==> + * git-archive --format-tar --prefix=basedir tree-ish + */ + int i; + const char **nargv = xcalloc(sizeof(*nargv), argc + 2); + char *basedir_arg; + int nargc = 0; + + nargv[nargc++] = "git-archive"; + nargv[nargc++] = "--format=tar"; + + if (2 <= argc && !strncmp("--remote=", argv[1], 9)) { + nargv[nargc++] = argv[1]; + argv++; + argc--; + } + switch (argc) { + default: usage(tar_tree_usage); - if (!strncmp("--remote=", argv[1], 9)) - return remote_tar(argc, argv); - return generate_tar(argc, argv, prefix); + break; + case 3: + /* base-path */ + basedir_arg = xmalloc(strlen(argv[2]) + 11); + sprintf(basedir_arg, "--prefix=%s/", argv[2]); + nargv[nargc++] = basedir_arg; + /* fallthru */ + case 2: + /* tree-ish */ + nargv[nargc++] = argv[1]; + } + nargv[nargc] = NULL; + + fprintf(stderr, + "*** git-tar-tree is now deprecated.\n" + "*** Running git-archive instead.\n***"); + for (i = 0; i < nargc; i++) { + fputc(' ', stderr); + sq_quote_print(stderr, nargv[i]); + } + fputc('\n', stderr); + return cmd_archive(nargc, nargv, prefix); } /* ustar header + extended global header content */ +#define RECORDSIZE (512) #define HEADERSIZE (2 * RECORDSIZE) int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) diff --git a/builtin-update-index.c b/builtin-update-index.c index 0620e779b0..a3c0a455ae 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -112,11 +112,13 @@ static int add_file_to_cache(const char *path) 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. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (0 <= pos) ce->ce_mode = active_cache[pos]->ce_mode; + else + ce->ce_mode = create_ce_mode(S_IFREG | 0644); } if (index_path(ce->sha1, path, &st, !info_only)) diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index 0596865679..45c92e163c 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -2,13 +2,13 @@ * Copyright (c) 2006 Franck Bui-Huu */ #include +#include +#include #include "cache.h" #include "builtin.h" #include "archive.h" #include "pkt-line.h" #include "sideband.h" -#include -#include static const char upload_archive_usage[] = "git-upload-archive "; diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c deleted file mode 100644 index 06a945a4b1..0000000000 --- a/builtin-upload-tar.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 "; - -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, const char *prefix) -{ - 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 = xstrdup(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; -} diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c deleted file mode 100644 index 52d4b7a17e..0000000000 --- a/builtin-zip-tree.c +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Copyright (c) 2006 Rene Scharfe - */ -#include -#include "cache.h" -#include "commit.h" -#include "blob.h" -#include "tree.h" -#include "quote.h" -#include "builtin.h" -#include "archive.h" - -static const char zip_tree_usage[] = -"git-zip-tree [-0|...|-9] [ ]"; - -static int verbose; -static int zip_date; -static int zip_time; - -static unsigned char *zip_dir; -static unsigned int zip_dir_size; - -static unsigned int zip_offset; -static unsigned int zip_dir_offset; -static unsigned int zip_dir_entries; - -#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) - -struct zip_local_header { - unsigned char magic[4]; - unsigned char version[2]; - unsigned char flags[2]; - unsigned char compression_method[2]; - unsigned char mtime[2]; - unsigned char mdate[2]; - unsigned char crc32[4]; - unsigned char compressed_size[4]; - unsigned char size[4]; - unsigned char filename_length[2]; - unsigned char extra_length[2]; -}; - -struct zip_dir_header { - unsigned char magic[4]; - unsigned char creator_version[2]; - unsigned char version[2]; - unsigned char flags[2]; - unsigned char compression_method[2]; - unsigned char mtime[2]; - unsigned char mdate[2]; - unsigned char crc32[4]; - unsigned char compressed_size[4]; - unsigned char size[4]; - unsigned char filename_length[2]; - unsigned char extra_length[2]; - unsigned char comment_length[2]; - unsigned char disk[2]; - unsigned char attr1[2]; - unsigned char attr2[4]; - unsigned char offset[4]; -}; - -struct zip_dir_trailer { - unsigned char magic[4]; - unsigned char disk[2]; - unsigned char directory_start_disk[2]; - unsigned char entries_on_this_disk[2]; - unsigned char entries[2]; - unsigned char size[4]; - unsigned char offset[4]; - unsigned char comment_length[2]; -}; - -static void copy_le16(unsigned char *dest, unsigned int n) -{ - dest[0] = 0xff & n; - dest[1] = 0xff & (n >> 010); -} - -static void copy_le32(unsigned char *dest, unsigned int n) -{ - dest[0] = 0xff & n; - dest[1] = 0xff & (n >> 010); - dest[2] = 0xff & (n >> 020); - dest[3] = 0xff & (n >> 030); -} - -static void *zlib_deflate(void *data, unsigned long size, - unsigned long *compressed_size) -{ - z_stream stream; - unsigned long maxsize; - void *buffer; - int result; - - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - maxsize = deflateBound(&stream, size); - buffer = xmalloc(maxsize); - - stream.next_in = data; - stream.avail_in = size; - stream.next_out = buffer; - stream.avail_out = maxsize; - - do { - result = deflate(&stream, Z_FINISH); - } while (result == Z_OK); - - if (result != Z_STREAM_END) { - free(buffer); - return NULL; - } - - deflateEnd(&stream); - *compressed_size = stream.total_out; - - return buffer; -} - -static char *construct_path(const char *base, int baselen, - const char *filename, int isdir, int *pathlen) -{ - int filenamelen = strlen(filename); - int len = baselen + filenamelen; - char *path, *p; - - if (isdir) - len++; - p = path = xmalloc(len + 1); - - memcpy(p, base, baselen); - p += baselen; - memcpy(p, filename, filenamelen); - p += filenamelen; - if (isdir) - *p++ = '/'; - *p = '\0'; - - *pathlen = len; - - return path; -} - -static int write_zip_entry(const unsigned char *sha1, - const char *base, int baselen, - const char *filename, unsigned mode, int stage) -{ - struct zip_local_header header; - struct zip_dir_header dirent; - unsigned long compressed_size; - unsigned long uncompressed_size; - unsigned long crc; - unsigned long direntsize; - unsigned long size; - int method; - int result = -1; - int pathlen; - unsigned char *out; - char *path; - char type[20]; - void *buffer = NULL; - void *deflated = NULL; - - crc = crc32(0, Z_NULL, 0); - - path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); - if (verbose) - fprintf(stderr, "%s\n", path); - if (pathlen > 0xffff) { - error("path too long (%d chars, SHA1: %s): %s", pathlen, - sha1_to_hex(sha1), path); - goto out; - } - - if (S_ISDIR(mode)) { - method = 0; - result = READ_TREE_RECURSIVE; - out = NULL; - uncompressed_size = 0; - compressed_size = 0; - } else if (S_ISREG(mode)) { - method = zlib_compression_level == 0 ? 0 : 8; - result = 0; - buffer = read_sha1_file(sha1, type, &size); - if (!buffer) - die("cannot read %s", sha1_to_hex(sha1)); - crc = crc32(crc, buffer, size); - out = buffer; - uncompressed_size = size; - compressed_size = size; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", mode, - sha1_to_hex(sha1)); - goto out; - } - - if (method == 8) { - deflated = zlib_deflate(buffer, size, &compressed_size); - if (deflated && compressed_size - 6 < size) { - /* ZLIB --> raw compressed data (see RFC 1950) */ - /* CMF and FLG ... */ - out = (unsigned char *)deflated + 2; - compressed_size -= 6; /* ... and ADLER32 */ - } else { - method = 0; - compressed_size = size; - } - } - - /* make sure we have enough free space in the dictionary */ - direntsize = sizeof(struct zip_dir_header) + pathlen; - while (zip_dir_size < zip_dir_offset + direntsize) { - zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; - zip_dir = xrealloc(zip_dir, zip_dir_size); - } - - copy_le32(dirent.magic, 0x02014b50); - copy_le16(dirent.creator_version, 0); - copy_le16(dirent.version, 20); - copy_le16(dirent.flags, 0); - copy_le16(dirent.compression_method, method); - copy_le16(dirent.mtime, zip_time); - copy_le16(dirent.mdate, zip_date); - copy_le32(dirent.crc32, crc); - copy_le32(dirent.compressed_size, compressed_size); - copy_le32(dirent.size, uncompressed_size); - copy_le16(dirent.filename_length, pathlen); - copy_le16(dirent.extra_length, 0); - copy_le16(dirent.comment_length, 0); - copy_le16(dirent.disk, 0); - copy_le16(dirent.attr1, 0); - copy_le32(dirent.attr2, 0); - copy_le32(dirent.offset, zip_offset); - memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); - zip_dir_offset += sizeof(struct zip_dir_header); - memcpy(zip_dir + zip_dir_offset, path, pathlen); - zip_dir_offset += pathlen; - zip_dir_entries++; - - copy_le32(header.magic, 0x04034b50); - copy_le16(header.version, 20); - copy_le16(header.flags, 0); - copy_le16(header.compression_method, method); - copy_le16(header.mtime, zip_time); - copy_le16(header.mdate, zip_date); - copy_le32(header.crc32, crc); - copy_le32(header.compressed_size, compressed_size); - copy_le32(header.size, uncompressed_size); - copy_le16(header.filename_length, pathlen); - copy_le16(header.extra_length, 0); - write_or_die(1, &header, sizeof(struct zip_local_header)); - zip_offset += sizeof(struct zip_local_header); - write_or_die(1, path, pathlen); - zip_offset += pathlen; - if (compressed_size > 0) { - write_or_die(1, out, compressed_size); - zip_offset += compressed_size; - } - -out: - free(buffer); - free(deflated); - free(path); - - return result; -} - -static void write_zip_trailer(const unsigned char *sha1) -{ - struct zip_dir_trailer trailer; - - copy_le32(trailer.magic, 0x06054b50); - copy_le16(trailer.disk, 0); - copy_le16(trailer.directory_start_disk, 0); - copy_le16(trailer.entries_on_this_disk, zip_dir_entries); - copy_le16(trailer.entries, zip_dir_entries); - copy_le32(trailer.size, zip_dir_offset); - copy_le32(trailer.offset, zip_offset); - copy_le16(trailer.comment_length, sha1 ? 40 : 0); - - write_or_die(1, zip_dir, zip_dir_offset); - write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); - if (sha1) - write_or_die(1, sha1_to_hex(sha1), 40); -} - -static void dos_time(time_t *time, int *dos_date, int *dos_time) -{ - struct tm *t = localtime(time); - - *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + - (t->tm_year + 1900 - 1980) * 512; - *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; -} - -int cmd_zip_tree(int argc, const char **argv, const char *prefix) -{ - unsigned char sha1[20]; - struct tree *tree; - struct commit *commit; - time_t archive_time; - char *base; - int baselen; - - git_config(git_default_config); - - if (argc > 1 && argv[1][0] == '-') { - if (isdigit(argv[1][1]) && argv[1][2] == '\0') { - zlib_compression_level = argv[1][1] - '0'; - argc--; - argv++; - } - } - - switch (argc) { - case 3: - base = xstrdup(argv[2]); - baselen = strlen(base); - break; - case 2: - base = xstrdup(""); - baselen = 0; - break; - default: - usage(zip_tree_usage); - } - - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - - commit = lookup_commit_reference_gently(sha1, 1); - archive_time = commit ? commit->date : time(NULL); - dos_time(&archive_time, &zip_date, &zip_time); - - zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); - zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; - - tree = parse_tree_indirect(sha1); - if (!tree) - die("not a tree object"); - - if (baselen > 0) { - write_zip_entry(tree->object.sha1, "", 0, base, 040777, 0); - base = xrealloc(base, baselen + 1); - base[baselen] = '/'; - baselen++; - base[baselen] = '\0'; - } - read_tree_recursive(tree, base, baselen, 0, NULL, write_zip_entry); - write_zip_trailer(commit ? commit->object.sha1 : NULL); - - free(zip_dir); - free(base); - - return 0; -} - -int write_zip_archive(struct archiver_args *args) -{ - int plen = strlen(args->base); - - dos_time(&args->time, &zip_date, &zip_time); - - zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); - zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; - verbose = args->verbose; - - if (args->base && plen > 0 && args->base[plen - 1] == '/') { - char *base = xstrdup(args->base); - int baselen = strlen(base); - - while (baselen > 0 && base[baselen - 1] == '/') - base[--baselen] = '\0'; - write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); - free(base); - } - read_tree_recursive(args->tree, args->base, plen, 0, - args->pathspec, write_zip_entry); - write_zip_trailer(args->commit_sha1); - - free(zip_dir); - - return 0; -} - -void *parse_extra_zip_args(int argc, const char **argv) -{ - for (; argc > 0; argc--, argv++) { - const char *arg = argv[0]; - - if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') - zlib_compression_level = arg[1] - '0'; - else - die("Unknown argument for zip format: %s", arg); - } - return NULL; -} diff --git a/builtin.h b/builtin.h index ccade94e26..f9fa9ff1d2 100644 --- a/builtin.h +++ b/builtin.h @@ -53,7 +53,6 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); -extern int cmd_zip_tree(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); diff --git a/cache.h b/cache.h index 57db7c9b20..97debd03c5 100644 --- a/cache.h +++ b/cache.h @@ -188,6 +188,7 @@ extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int shared_repository; +extern int deny_non_fast_forwards; extern const char *apply_default_whitespace; extern int zlib_compression_level; @@ -278,6 +279,12 @@ enum object_type { OBJ_BAD, }; +extern signed char hexval_table[256]; +static inline unsigned int hexval(unsigned int c) +{ + return hexval_table[c]; +} + /* Convert to/from hex/sha1 representation */ #define MINIMUM_ABBREV 4 #define DEFAULT_ABBREV 7 @@ -383,10 +390,10 @@ extern void unuse_packed_git(struct packed_git *); extern struct packed_git *add_packed_git(char *, int, int); extern int num_packed_objects(const struct packed_git *p); extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*); -extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *); -extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *); +extern unsigned long find_pack_entry_one(const unsigned char *, struct packed_git *); +extern void *unpack_entry_gently(struct packed_git *, unsigned long, char *, unsigned long *); extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); -extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *); +extern void packed_object_info_detail(struct packed_git *, unsigned long, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *); /* Dumb servers support */ extern int update_server_info(int); diff --git a/configure.ac b/configure.ac index 511cac93d6..b1a5833b40 100644 --- a/configure.ac +++ b/configure.ac @@ -75,7 +75,6 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) AS_HELP_STRING([--without-python], [don't use python scripts])], @@ -100,7 +99,6 @@ AC_PROG_CC AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. if test -z "$NO_PYTHON"; then if test -z "$PYTHON_PATH"; then diff --git a/daemon.c b/daemon.c index a2954a0451..eb4f3f1e9f 100644 --- a/daemon.c +++ b/daemon.c @@ -12,6 +12,7 @@ #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" +#include "interpolate.h" static int log_syslog; static int verbose; @@ -21,6 +22,7 @@ static const char daemon_usage[] = "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n" " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" +" [--interpolated-path=path]\n" " [--reuseaddr] [--detach] [--pid-file=file]\n" " [--[enable|disable|allow-override|forbid-override]=service]\n" " [--user=user [[--group=group]] [directory...]"; @@ -34,6 +36,10 @@ static int export_all_trees; /* Take all paths relative to this one if non-NULL */ static char *base_path; +static char *interpolated_path; + +/* Flag indicating client sent extra args. */ +static int saw_extended_args; /* If defined, ~user notation is allowed and the string is inserted * after ~user/. E.g. a request to git://host/~alice/frotz would @@ -45,6 +51,21 @@ static const char *user_path; static unsigned int timeout; static unsigned int init_timeout; +/* + * Static table for now. Ugh. + * Feel free to make dynamic as needed. + */ +#define INTERP_SLOT_HOST (0) +#define INTERP_SLOT_DIR (1) +#define INTERP_SLOT_PERCENT (2) + +static struct interp interp_table[] = { + { "%H", 0}, + { "%D", 0}, + { "%%", "%"}, +}; + + static void logreport(int priority, const char *err, va_list params) { /* We should do a single write so that it is atomic and output @@ -152,10 +173,14 @@ static int avoid_alias(char *p) } } -static char *path_ok(char *dir) +static char *path_ok(struct interp *itable) { static char rpath[PATH_MAX]; + static char interp_path[PATH_MAX]; char *path; + char *dir; + + dir = itable[INTERP_SLOT_DIR].value; if (avoid_alias(dir)) { logerror("'%s': aliased", dir); @@ -184,16 +209,27 @@ static char *path_ok(char *dir) dir = rpath; } } + else if (interpolated_path && saw_extended_args) { + if (*dir != '/') { + /* Allow only absolute */ + logerror("'%s': Non-absolute path denied (interpolated-path active)", dir); + return NULL; + } + + interpolate(interp_path, PATH_MAX, interpolated_path, + interp_table, ARRAY_SIZE(interp_table)); + loginfo("Interpolated dir '%s'", interp_path); + + dir = interp_path; + } else if (base_path) { if (*dir != '/') { /* Allow only absolute */ logerror("'%s': Non-absolute path denied (base-path active)", dir); return NULL; } - else { - snprintf(rpath, PATH_MAX, "%s%s", base_path, dir); - dir = rpath; - } + snprintf(rpath, PATH_MAX, "%s%s", base_path, dir); + dir = rpath; } path = enter_repo(dir, strict_paths); @@ -257,12 +293,14 @@ static int git_daemon_config(const char *var, const char *value) return 0; } -static int run_service(char *dir, struct daemon_service *service) +static int run_service(struct interp *itable, struct daemon_service *service) { const char *path; int enabled = service->enabled; - loginfo("Request %s for '%s'", service->name, dir); + loginfo("Request %s for '%s'", + service->name, + itable[INTERP_SLOT_DIR].value); if (!enabled && !service->overridable) { logerror("'%s': service not enabled.", service->name); @@ -270,7 +308,7 @@ static int run_service(char *dir, struct daemon_service *service) return -1; } - if (!(path = path_ok(dir))) + if (!(path = path_ok(itable))) return -1; /* @@ -358,6 +396,28 @@ static void make_service_overridable(const char *name, int ena) { die("No such service %s", name); } +static void parse_extra_args(char *extra_args, int buflen) +{ + char *val; + int vallen; + char *end = extra_args + buflen; + + while (extra_args < end && *extra_args) { + saw_extended_args = 1; + if (strncasecmp("host=", extra_args, 5) == 0) { + val = extra_args + 5; + vallen = strlen(val) + 1; + if (*val) { + char *save = xmalloc(vallen); + interp_table[INTERP_SLOT_HOST].value = save; + strlcpy(save, val, vallen); + } + /* On to the next one */ + extra_args = val + vallen; + } + } +} + static int execute(struct sockaddr *addr) { static char line[1000]; @@ -398,13 +458,18 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; + if (len != pktlen) + parse_extra_args(line + len + 1, pktlen - len - 1); + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); int namelen = strlen(s->name); if (!strncmp("git-", line, 4) && !strncmp(s->name, line + 4, namelen) && - line[namelen + 4] == ' ') - return run_service(line + namelen + 5, s); + line[namelen + 4] == ' ') { + interp_table[INTERP_SLOT_DIR].value = line+namelen+5; + return run_service(interp_table, s); + } } logerror("Protocol error: '%s'", line); @@ -867,6 +932,10 @@ int main(int argc, char **argv) base_path = arg+12; continue; } + if (!strncmp(arg, "--interpolated-path=", 20)) { + interpolated_path = arg+20; + continue; + } if (!strcmp(arg, "--reuseaddr")) { reuseaddr = 1; continue; diff --git a/diff-delta.c b/diff-delta.c index fa16d06c8d..51df4608a8 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -308,8 +308,8 @@ create_delta(const struct delta_index *index, continue; if (ref_size > top - src) ref_size = top - src; - if (ref_size > 0x10000) - ref_size = 0x10000; + if (ref_size > 0xffffff) + ref_size = 0xffffff; if (ref_size <= msize) break; while (ref_size-- && *src++ == *ref) @@ -318,6 +318,8 @@ create_delta(const struct delta_index *index, /* this is our best match so far */ msize = ref - entry->ptr; moff = entry->ptr - ref_data; + if (msize >= 0x10000) + break; /* this is good enough */ } } @@ -381,6 +383,8 @@ create_delta(const struct delta_index *index, if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; } msize >>= 8; if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; } + msize >>= 8; + if (msize & 0xff) { out[outpos++] = msize; i |= 0x40; } *op = i; } diff --git a/dir.c b/dir.c index e2f472ba7f..96389b32e6 100644 --- a/dir.c +++ b/dir.c @@ -283,7 +283,7 @@ static int dir_exists(const char *dirname, int len) * 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) +static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only) { DIR *fdir = opendir(path); int contents = 0; @@ -314,7 +314,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co switch (DTYPE(de)) { struct stat st; - int subdir, rewind_base; default: continue; case DT_UNKNOWN: @@ -328,26 +327,30 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co 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]); + if (dir->hide_empty_directories && + !read_directory_recursive(dir, + fullname, fullname, + baselen + len, 1)) + continue; break; } - contents += subdir; + + contents += read_directory_recursive(dir, + fullname, fullname, baselen + len, 0); continue; case DT_REG: case DT_LNK: break; } - add_name(dir, fullname, baselen + len); contents++; + if (check_only) + goto exit_early; + else + add_name(dir, fullname, baselen + len); } +exit_early: closedir(fdir); pop_exclude_per_directory(dir, exclude_stk); @@ -393,7 +396,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i } } - read_directory_recursive(dir, path, base, baselen); + read_directory_recursive(dir, path, base, baselen, 0); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; } diff --git a/environment.c b/environment.c index 84d870ca4e..63b1d155be 100644 --- a/environment.c +++ b/environment.c @@ -20,6 +20,7 @@ int warn_ambiguous_refs = 1; int repository_format_version; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; +int deny_non_fast_forwards = 0; const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; diff --git a/git-fetch.sh b/git-fetch.sh index 09a5d6ceab..50ad101e89 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -68,11 +68,10 @@ done case "$#" in 0) - test -f "$GIT_DIR/branches/origin" || - test -f "$GIT_DIR/remotes/origin" || - git-repo-config --get remote.origin.url >/dev/null || - die "Where do you want to fetch from today?" - set origin ;; + origin=$(get_default_remote) + test -n "$(get_remote_url ${origin})" || + die "Where do you want to fetch from today?" + set x $origin ; shift ;; esac remote_nick="$1" diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py new file mode 100755 index 0000000000..4039435ce4 --- /dev/null +++ b/git-merge-recursive-old.py @@ -0,0 +1,944 @@ +#!/usr/bin/python +# +# Copyright (C) 2005 Fredrik Kuivinen +# + +import sys +sys.path.append('''@@GIT_PYTHON_PATH@@''') + +import math, random, os, re, signal, tempfile, stat, errno, traceback +from heapq import heappush, heappop +from sets import Set + +from gitMergeCommon import * + +outputIndent = 0 +def output(*args): + sys.stdout.write(' '*outputIndent) + printList(args) + +originalIndexFile = os.environ.get('GIT_INDEX_FILE', + os.environ.get('GIT_DIR', '.git') + '/index') +temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ + '/merge-recursive-tmp-index' +def setupIndex(temporary): + try: + os.unlink(temporaryIndexFile) + except OSError: + pass + if temporary: + newIndex = temporaryIndexFile + else: + newIndex = originalIndexFile + os.environ['GIT_INDEX_FILE'] = newIndex + +# This is a global variable which is used in a number of places but +# only written to in the 'merge' function. + +# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and +# don't update the working directory. +# False => Leave unmerged entries in the cache and update +# the working directory. + +cacheOnly = False + +# The entry point to the merge code +# --------------------------------- + +def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): + '''Merge the commits h1 and h2, return the resulting virtual + commit object and a flag indicating the cleanness of the merge.''' + assert(isinstance(h1, Commit) and isinstance(h2, Commit)) + + global outputIndent + + output('Merging:') + output(h1) + output(h2) + sys.stdout.flush() + + if ancestor: + ca = [ancestor] + else: + assert(isinstance(graph, Graph)) + ca = getCommonAncestors(graph, h1, h2) + output('found', len(ca), 'common ancestor(s):') + for x in ca: + output(x) + sys.stdout.flush() + + mergedCA = ca[0] + for h in ca[1:]: + outputIndent = callDepth+1 + [mergedCA, dummy] = merge(mergedCA, h, + 'Temporary merge branch 1', + 'Temporary merge branch 2', + graph, callDepth+1) + outputIndent = callDepth + assert(isinstance(mergedCA, Commit)) + + global cacheOnly + if callDepth == 0: + setupIndex(False) + cacheOnly = False + else: + setupIndex(True) + runProgram(['git-read-tree', h1.tree()]) + cacheOnly = True + + [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), + branch1Name, branch2Name) + + if graph and (clean or cacheOnly): + res = Commit(None, [h1, h2], tree=shaRes) + graph.addNode(res) + else: + res = None + + return [res, clean] + +getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) +def getFilesAndDirs(tree): + files = Set() + dirs = Set() + out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) + for l in out.split('\0'): + m = getFilesRE.match(l) + if m: + if m.group(2) == 'tree': + dirs.add(m.group(4)) + elif m.group(2) == 'blob': + files.add(m.group(4)) + + return [files, dirs] + +# Those two global variables are used in a number of places but only +# written to in 'mergeTrees' and 'uniquePath'. They keep track of +# every file and directory in the two branches that are about to be +# merged. +currentFileSet = None +currentDirectorySet = None + +def mergeTrees(head, merge, common, branch1Name, branch2Name): + '''Merge the trees 'head' and 'merge' with the common ancestor + 'common'. The name of the head branch is 'branch1Name' and the name of + the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) + where tree is the resulting tree and cleanMerge is True iff the + merge was clean.''' + + assert(isSha(head) and isSha(merge) and isSha(common)) + + if common == merge: + output('Already uptodate!') + return [head, True] + + if cacheOnly: + updateArg = '-i' + else: + updateArg = '-u' + + [out, code] = runProgram(['git-read-tree', updateArg, '-m', + common, head, merge], returnCode = True) + if code != 0: + die('git-read-tree:', out) + + [tree, code] = runProgram('git-write-tree', returnCode=True) + tree = tree.rstrip() + if code != 0: + global currentFileSet, currentDirectorySet + [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) + [filesM, dirsM] = getFilesAndDirs(merge) + currentFileSet.union_update(filesM) + currentDirectorySet.union_update(dirsM) + + entries = unmergedCacheEntries() + renamesHead = getRenames(head, common, head, merge, entries) + renamesMerge = getRenames(merge, common, head, merge, entries) + + cleanMerge = processRenames(renamesHead, renamesMerge, + branch1Name, branch2Name) + for entry in entries: + if entry.processed: + continue + if not processEntry(entry, branch1Name, branch2Name): + cleanMerge = False + + if cleanMerge or cacheOnly: + tree = runProgram('git-write-tree').rstrip() + else: + tree = None + else: + cleanMerge = True + + return [tree, cleanMerge] + +# Low level file merging, update and removal +# ------------------------------------------ + +def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, + branch1Name, branch2Name): + + merge = False + clean = True + + if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): + clean = False + if stat.S_ISREG(aMode): + mode = aMode + sha = aSha + else: + mode = bMode + sha = bSha + else: + if aSha != oSha and bSha != oSha: + merge = True + + if aMode == oMode: + mode = bMode + else: + mode = aMode + + if aSha == oSha: + sha = bSha + elif bSha == oSha: + sha = aSha + elif stat.S_ISREG(aMode): + assert(stat.S_ISREG(bMode)) + + orig = runProgram(['git-unpack-file', oSha]).rstrip() + src1 = runProgram(['git-unpack-file', aSha]).rstrip() + src2 = runProgram(['git-unpack-file', bSha]).rstrip() + try: + [out, code] = runProgram(['merge', + '-L', branch1Name + '/' + aPath, + '-L', 'orig/' + oPath, + '-L', branch2Name + '/' + bPath, + src1, orig, src2], returnCode=True) + except ProgramError, e: + print >>sys.stderr, e + die("Failed to execute 'merge'. merge(1) is used as the " + "file-level merge tool. Is 'merge' in your path?") + + sha = runProgram(['git-hash-object', '-t', 'blob', '-w', + src1]).rstrip() + + os.unlink(orig) + os.unlink(src1) + os.unlink(src2) + + clean = (code == 0) + else: + assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) + sha = aSha + + if aSha != bSha: + clean = False + + return [sha, mode, clean, merge] + +def updateFile(clean, sha, mode, path): + updateCache = cacheOnly or clean + updateWd = not cacheOnly + + return updateFileExt(sha, mode, path, updateCache, updateWd) + +def updateFileExt(sha, mode, path, updateCache, updateWd): + if cacheOnly: + updateWd = False + + if updateWd: + pathComponents = path.split('/') + for x in xrange(1, len(pathComponents)): + p = '/'.join(pathComponents[0:x]) + + try: + createDir = not stat.S_ISDIR(os.lstat(p).st_mode) + except OSError: + createDir = True + + if createDir: + try: + os.mkdir(p) + except OSError, e: + die("Couldn't create directory", p, e.strerror) + + prog = ['git-cat-file', 'blob', sha] + if stat.S_ISREG(mode): + try: + os.unlink(path) + except OSError: + pass + if mode & 0100: + mode = 0777 + else: + mode = 0666 + fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) + proc = subprocess.Popen(prog, stdout=fd) + proc.wait() + os.close(fd) + elif stat.S_ISLNK(mode): + linkTarget = runProgram(prog) + os.symlink(linkTarget, path) + else: + assert(False) + + if updateWd and updateCache: + runProgram(['git-update-index', '--add', '--', path]) + elif updateCache: + runProgram(['git-update-index', '--add', '--cacheinfo', + '0%o' % mode, sha, path]) + +def setIndexStages(path, + oSHA1, oMode, + aSHA1, aMode, + bSHA1, bMode, + clear=True): + istring = [] + if clear: + istring.append("0 " + ("0" * 40) + "\t" + path + "\0") + if oMode: + istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) + if aMode: + istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) + if bMode: + istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) + + runProgram(['git-update-index', '-z', '--index-info'], + input="".join(istring)) + +def removeFile(clean, path): + updateCache = cacheOnly or clean + updateWd = not cacheOnly + + if updateCache: + runProgram(['git-update-index', '--force-remove', '--', path]) + + if updateWd: + try: + os.unlink(path) + except OSError, e: + if e.errno != errno.ENOENT and e.errno != errno.EISDIR: + raise + try: + os.removedirs(os.path.dirname(path)) + except OSError: + pass + +def uniquePath(path, branch): + def fileExists(path): + try: + os.lstat(path) + return True + except OSError, e: + if e.errno == errno.ENOENT: + return False + else: + raise + + branch = branch.replace('/', '_') + newPath = path + '~' + branch + suffix = 0 + while newPath in currentFileSet or \ + newPath in currentDirectorySet or \ + fileExists(newPath): + suffix += 1 + newPath = path + '~' + branch + '_' + str(suffix) + currentFileSet.add(newPath) + return newPath + +# Cache entry management +# ---------------------- + +class CacheEntry: + def __init__(self, path): + class Stage: + def __init__(self): + self.sha1 = None + self.mode = None + + # Used for debugging only + def __str__(self): + if self.mode != None: + m = '0%o' % self.mode + else: + m = 'None' + + if self.sha1: + sha1 = self.sha1 + else: + sha1 = 'None' + return 'sha1: ' + sha1 + ' mode: ' + m + + self.stages = [Stage(), Stage(), Stage(), Stage()] + self.path = path + self.processed = False + + def __str__(self): + return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) + +class CacheEntryContainer: + def __init__(self): + self.entries = {} + + def add(self, entry): + self.entries[entry.path] = entry + + def get(self, path): + return self.entries.get(path) + + def __iter__(self): + return self.entries.itervalues() + +unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) +def unmergedCacheEntries(): + '''Create a dictionary mapping file names to CacheEntry + objects. The dictionary contains one entry for every path with a + non-zero stage entry.''' + + lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') + lines.pop() + + res = CacheEntryContainer() + for l in lines: + m = unmergedRE.match(l) + if m: + mode = int(m.group(1), 8) + sha1 = m.group(2) + stage = int(m.group(3)) + path = m.group(4) + + e = res.get(path) + if not e: + e = CacheEntry(path) + res.add(e) + + e.stages[stage].mode = mode + e.stages[stage].sha1 = sha1 + else: + die('Error: Merge program failed: Unexpected output from', + 'git-ls-files:', l) + return res + +lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) +def getCacheEntry(path, origTree, aTree, bTree): + '''Returns a CacheEntry object which doesn't have to correspond to + a real cache entry in Git's index.''' + + def parse(out): + if out == '': + return [None, None] + else: + m = lsTreeRE.match(out) + if not m: + die('Unexpected output from git-ls-tree:', out) + elif m.group(2) == 'blob': + return [m.group(3), int(m.group(1), 8)] + else: + return [None, None] + + res = CacheEntry(path) + + [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) + [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) + [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) + + res.stages[1].sha1 = oSha + res.stages[1].mode = oMode + res.stages[2].sha1 = aSha + res.stages[2].mode = aMode + res.stages[3].sha1 = bSha + res.stages[3].mode = bMode + + return res + +# Rename detection and handling +# ----------------------------- + +class RenameEntry: + def __init__(self, + src, srcSha, srcMode, srcCacheEntry, + dst, dstSha, dstMode, dstCacheEntry, + score): + self.srcName = src + self.srcSha = srcSha + self.srcMode = srcMode + self.srcCacheEntry = srcCacheEntry + self.dstName = dst + self.dstSha = dstSha + self.dstMode = dstMode + self.dstCacheEntry = dstCacheEntry + self.score = score + + self.processed = False + +class RenameEntryContainer: + def __init__(self): + self.entriesSrc = {} + self.entriesDst = {} + + def add(self, entry): + self.entriesSrc[entry.srcName] = entry + self.entriesDst[entry.dstName] = entry + + def getSrc(self, path): + return self.entriesSrc.get(path) + + def getDst(self, path): + return self.entriesDst.get(path) + + def __iter__(self): + return self.entriesSrc.itervalues() + +parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') +def getRenames(tree, oTree, aTree, bTree, cacheEntries): + '''Get information of all renames which occured between 'oTree' and + 'tree'. We need the three trees in the merge ('oTree', 'aTree' and + 'bTree') to be able to associate the correct cache entries with + the rename information. 'tree' is always equal to either aTree or bTree.''' + + assert(tree == aTree or tree == bTree) + inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', + '-z', oTree, tree]) + + ret = RenameEntryContainer() + try: + recs = inp.split("\0") + recs.pop() # remove last entry (which is '') + it = recs.__iter__() + while True: + rec = it.next() + m = parseDiffRenamesRE.match(rec) + + if not m: + die('Unexpected output from git-diff-tree:', rec) + + srcMode = int(m.group(1), 8) + dstMode = int(m.group(2), 8) + srcSha = m.group(3) + dstSha = m.group(4) + score = m.group(5) + src = it.next() + dst = it.next() + + srcCacheEntry = cacheEntries.get(src) + if not srcCacheEntry: + srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) + cacheEntries.add(srcCacheEntry) + + dstCacheEntry = cacheEntries.get(dst) + if not dstCacheEntry: + dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) + cacheEntries.add(dstCacheEntry) + + ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, + dst, dstSha, dstMode, dstCacheEntry, + score)) + except StopIteration: + pass + return ret + +def fmtRename(src, dst): + srcPath = src.split('/') + dstPath = dst.split('/') + path = [] + endIndex = min(len(srcPath), len(dstPath)) - 1 + for x in range(0, endIndex): + if srcPath[x] == dstPath[x]: + path.append(srcPath[x]) + else: + endIndex = x + break + + if len(path) > 0: + return '/'.join(path) + \ + '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ + '/'.join(dstPath[endIndex:]) + '}' + else: + return src + ' => ' + dst + +def processRenames(renamesA, renamesB, branchNameA, branchNameB): + srcNames = Set() + for x in renamesA: + srcNames.add(x.srcName) + for x in renamesB: + srcNames.add(x.srcName) + + cleanMerge = True + for path in srcNames: + if renamesA.getSrc(path): + renames1 = renamesA + renames2 = renamesB + branchName1 = branchNameA + branchName2 = branchNameB + else: + renames1 = renamesB + renames2 = renamesA + branchName1 = branchNameB + branchName2 = branchNameA + + ren1 = renames1.getSrc(path) + ren2 = renames2.getSrc(path) + + ren1.dstCacheEntry.processed = True + ren1.srcCacheEntry.processed = True + + if ren1.processed: + continue + + ren1.processed = True + + if ren2: + # Renamed in 1 and renamed in 2 + assert(ren1.srcName == ren2.srcName) + ren2.dstCacheEntry.processed = True + ren2.processed = True + + if ren1.dstName != ren2.dstName: + output('CONFLICT (rename/rename): Rename', + fmtRename(path, ren1.dstName), 'in branch', branchName1, + 'rename', fmtRename(path, ren2.dstName), 'in', + branchName2) + cleanMerge = False + + if ren1.dstName in currentDirectorySet: + dstName1 = uniquePath(ren1.dstName, branchName1) + output(ren1.dstName, 'is a directory in', branchName2, + 'adding as', dstName1, 'instead.') + removeFile(False, ren1.dstName) + else: + dstName1 = ren1.dstName + + if ren2.dstName in currentDirectorySet: + dstName2 = uniquePath(ren2.dstName, branchName2) + output(ren2.dstName, 'is a directory in', branchName1, + 'adding as', dstName2, 'instead.') + removeFile(False, ren2.dstName) + else: + dstName2 = ren2.dstName + setIndexStages(dstName1, + None, None, + ren1.dstSha, ren1.dstMode, + None, None) + setIndexStages(dstName2, + None, None, + None, None, + ren2.dstSha, ren2.dstMode) + + else: + removeFile(True, ren1.srcName) + + [resSha, resMode, clean, merge] = \ + mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, + ren1.dstName, ren1.dstSha, ren1.dstMode, + ren2.dstName, ren2.dstSha, ren2.dstMode, + branchName1, branchName2) + + if merge or not clean: + output('Renaming', fmtRename(path, ren1.dstName)) + + if merge: + output('Auto-merging', ren1.dstName) + + if not clean: + output('CONFLICT (content): merge conflict in', + ren1.dstName) + cleanMerge = False + + if not cacheOnly: + setIndexStages(ren1.dstName, + ren1.srcSha, ren1.srcMode, + ren1.dstSha, ren1.dstMode, + ren2.dstSha, ren2.dstMode) + + updateFile(clean, resSha, resMode, ren1.dstName) + else: + removeFile(True, ren1.srcName) + + # Renamed in 1, maybe changed in 2 + if renamesA == renames1: + stage = 3 + else: + stage = 2 + + srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 + srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode + + dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 + dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode + + tryMerge = False + + if ren1.dstName in currentDirectorySet: + newPath = uniquePath(ren1.dstName, branchName1) + output('CONFLICT (rename/directory): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, + 'directory', ren1.dstName, 'added in', branchName2) + output('Renaming', ren1.srcName, 'to', newPath, 'instead') + cleanMerge = False + removeFile(False, ren1.dstName) + updateFile(False, ren1.dstSha, ren1.dstMode, newPath) + elif srcShaOtherBranch == None: + output('CONFLICT (rename/delete): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1, 'and deleted in', branchName2) + cleanMerge = False + updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) + elif dstShaOtherBranch: + newPath = uniquePath(ren1.dstName, branchName2) + output('CONFLICT (rename/add): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1 + '.', ren1.dstName, 'added in', branchName2) + output('Adding as', newPath, 'instead') + updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) + cleanMerge = False + tryMerge = True + elif renames2.getDst(ren1.dstName): + dst2 = renames2.getDst(ren1.dstName) + newPath1 = uniquePath(ren1.dstName, branchName1) + newPath2 = uniquePath(dst2.dstName, branchName2) + output('CONFLICT (rename/rename): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1+'. Rename', + fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) + output('Renaming', ren1.srcName, 'to', newPath1, 'and', + dst2.srcName, 'to', newPath2, 'instead') + removeFile(False, ren1.dstName) + updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) + updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) + dst2.processed = True + cleanMerge = False + else: + tryMerge = True + + if tryMerge: + + oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode + aName, bName = ren1.dstName, ren1.srcName + aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch + aMode, bMode = ren1.dstMode, srcModeOtherBranch + aBranch, bBranch = branchName1, branchName2 + + if renamesA != renames1: + aName, bName = bName, aName + aSHA1, bSHA1 = bSHA1, aSHA1 + aMode, bMode = bMode, aMode + aBranch, bBranch = bBranch, aBranch + + [resSha, resMode, clean, merge] = \ + mergeFile(oName, oSHA1, oMode, + aName, aSHA1, aMode, + bName, bSHA1, bMode, + aBranch, bBranch); + + if merge or not clean: + output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) + + if merge: + output('Auto-merging', ren1.dstName) + + if not clean: + output('CONFLICT (rename/modify): Merge conflict in', + ren1.dstName) + cleanMerge = False + + if not cacheOnly: + setIndexStages(ren1.dstName, + oSHA1, oMode, + aSHA1, aMode, + bSHA1, bMode) + + updateFile(clean, resSha, resMode, ren1.dstName) + + return cleanMerge + +# Per entry merge function +# ------------------------ + +def processEntry(entry, branch1Name, branch2Name): + '''Merge one cache entry.''' + + debug('processing', entry.path, 'clean cache:', cacheOnly) + + cleanMerge = True + + path = entry.path + oSha = entry.stages[1].sha1 + oMode = entry.stages[1].mode + aSha = entry.stages[2].sha1 + aMode = entry.stages[2].mode + bSha = entry.stages[3].sha1 + bMode = entry.stages[3].mode + + assert(oSha == None or isSha(oSha)) + assert(aSha == None or isSha(aSha)) + assert(bSha == None or isSha(bSha)) + + assert(oMode == None or type(oMode) is int) + assert(aMode == None or type(aMode) is int) + assert(bMode == None or type(bMode) is int) + + if (oSha and (not aSha or not bSha)): + # + # Case A: Deleted in one + # + if (not aSha and not bSha) or \ + (aSha == oSha and not bSha) or \ + (not aSha and bSha == oSha): + # Deleted in both or deleted in one and unchanged in the other + if aSha: + output('Removing', path) + removeFile(True, path) + else: + # Deleted in one and changed in the other + cleanMerge = False + if not aSha: + output('CONFLICT (delete/modify):', path, 'deleted in', + branch1Name, 'and modified in', branch2Name + '.', + 'Version', branch2Name, 'of', path, 'left in tree.') + mode = bMode + sha = bSha + else: + output('CONFLICT (modify/delete):', path, 'deleted in', + branch2Name, 'and modified in', branch1Name + '.', + 'Version', branch1Name, 'of', path, 'left in tree.') + mode = aMode + sha = aSha + + updateFile(False, sha, mode, path) + + elif (not oSha and aSha and not bSha) or \ + (not oSha and not aSha and bSha): + # + # Case B: Added in one. + # + if aSha: + addBranch = branch1Name + otherBranch = branch2Name + mode = aMode + sha = aSha + conf = 'file/directory' + else: + addBranch = branch2Name + otherBranch = branch1Name + mode = bMode + sha = bSha + conf = 'directory/file' + + if path in currentDirectorySet: + cleanMerge = False + newPath = uniquePath(path, addBranch) + output('CONFLICT (' + conf + '):', + 'There is a directory with name', path, 'in', + otherBranch + '. Adding', path, 'as', newPath) + + removeFile(False, path) + updateFile(False, sha, mode, newPath) + else: + output('Adding', path) + updateFile(True, sha, mode, path) + + elif not oSha and aSha and bSha: + # + # Case C: Added in both (check for same permissions). + # + if aSha == bSha: + if aMode != bMode: + cleanMerge = False + output('CONFLICT: File', path, + 'added identically in both branches, but permissions', + 'conflict', '0%o' % aMode, '->', '0%o' % bMode) + output('CONFLICT: adding with permission:', '0%o' % aMode) + + updateFile(False, aSha, aMode, path) + else: + # This case is handled by git-read-tree + assert(False) + else: + cleanMerge = False + newPath1 = uniquePath(path, branch1Name) + newPath2 = uniquePath(path, branch2Name) + output('CONFLICT (add/add): File', path, + 'added non-identically in both branches. Adding as', + newPath1, 'and', newPath2, 'instead.') + removeFile(False, path) + updateFile(False, aSha, aMode, newPath1) + updateFile(False, bSha, bMode, newPath2) + + elif oSha and aSha and bSha: + # + # case D: Modified in both, but differently. + # + output('Auto-merging', path) + [sha, mode, clean, dummy] = \ + mergeFile(path, oSha, oMode, + path, aSha, aMode, + path, bSha, bMode, + branch1Name, branch2Name) + if clean: + updateFile(True, sha, mode, path) + else: + cleanMerge = False + output('CONFLICT (content): Merge conflict in', path) + + if cacheOnly: + updateFile(False, sha, mode, path) + else: + updateFileExt(sha, mode, path, updateCache=False, updateWd=True) + else: + die("ERROR: Fatal merge failure, shouldn't happen.") + + return cleanMerge + +def usage(): + die('Usage:', sys.argv[0], ' ... -- ..') + +# main entry point as merge strategy module +# The first parameters up to -- are merge bases, and the rest are heads. + +if len(sys.argv) < 4: + usage() + +bases = [] +for nextArg in xrange(1, len(sys.argv)): + if sys.argv[nextArg] == '--': + if len(sys.argv) != nextArg + 3: + die('Not handling anything other than two heads merge.') + try: + h1 = firstBranch = sys.argv[nextArg + 1] + h2 = secondBranch = sys.argv[nextArg + 2] + except IndexError: + usage() + break + else: + bases.append(sys.argv[nextArg]) + +print 'Merging', h1, 'with', h2 + +try: + h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() + h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() + + if len(bases) == 1: + base = runProgram(['git-rev-parse', '--verify', + bases[0] + '^0']).rstrip() + ancestor = Commit(base, None) + [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), + firstBranch, secondBranch, None, 0, + ancestor) + else: + graph = buildGraph([h1, h2]) + [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], + firstBranch, secondBranch, graph) + + print '' +except: + if isinstance(sys.exc_info()[1], SystemExit): + raise + else: + traceback.print_exc(None, sys.stderr) + sys.exit(2) + +if clean: + sys.exit(0) +else: + sys.exit(1) diff --git a/git-merge-recursive.py b/git-merge-recursive.py deleted file mode 100755 index 4039435ce4..0000000000 --- a/git-merge-recursive.py +++ /dev/null @@ -1,944 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys -sys.path.append('''@@GIT_PYTHON_PATH@@''') - -import math, random, os, re, signal, tempfile, stat, errno, traceback -from heapq import heappush, heappop -from sets import Set - -from gitMergeCommon import * - -outputIndent = 0 -def output(*args): - sys.stdout.write(' '*outputIndent) - printList(args) - -originalIndexFile = os.environ.get('GIT_INDEX_FILE', - os.environ.get('GIT_DIR', '.git') + '/index') -temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ - '/merge-recursive-tmp-index' -def setupIndex(temporary): - try: - os.unlink(temporaryIndexFile) - except OSError: - pass - if temporary: - newIndex = temporaryIndexFile - else: - newIndex = originalIndexFile - os.environ['GIT_INDEX_FILE'] = newIndex - -# This is a global variable which is used in a number of places but -# only written to in the 'merge' function. - -# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and -# don't update the working directory. -# False => Leave unmerged entries in the cache and update -# the working directory. - -cacheOnly = False - -# The entry point to the merge code -# --------------------------------- - -def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): - '''Merge the commits h1 and h2, return the resulting virtual - commit object and a flag indicating the cleanness of the merge.''' - assert(isinstance(h1, Commit) and isinstance(h2, Commit)) - - global outputIndent - - output('Merging:') - output(h1) - output(h2) - sys.stdout.flush() - - if ancestor: - ca = [ancestor] - else: - assert(isinstance(graph, Graph)) - ca = getCommonAncestors(graph, h1, h2) - output('found', len(ca), 'common ancestor(s):') - for x in ca: - output(x) - sys.stdout.flush() - - mergedCA = ca[0] - for h in ca[1:]: - outputIndent = callDepth+1 - [mergedCA, dummy] = merge(mergedCA, h, - 'Temporary merge branch 1', - 'Temporary merge branch 2', - graph, callDepth+1) - outputIndent = callDepth - assert(isinstance(mergedCA, Commit)) - - global cacheOnly - if callDepth == 0: - setupIndex(False) - cacheOnly = False - else: - setupIndex(True) - runProgram(['git-read-tree', h1.tree()]) - cacheOnly = True - - [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), - branch1Name, branch2Name) - - if graph and (clean or cacheOnly): - res = Commit(None, [h1, h2], tree=shaRes) - graph.addNode(res) - else: - res = None - - return [res, clean] - -getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) -def getFilesAndDirs(tree): - files = Set() - dirs = Set() - out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) - for l in out.split('\0'): - m = getFilesRE.match(l) - if m: - if m.group(2) == 'tree': - dirs.add(m.group(4)) - elif m.group(2) == 'blob': - files.add(m.group(4)) - - return [files, dirs] - -# Those two global variables are used in a number of places but only -# written to in 'mergeTrees' and 'uniquePath'. They keep track of -# every file and directory in the two branches that are about to be -# merged. -currentFileSet = None -currentDirectorySet = None - -def mergeTrees(head, merge, common, branch1Name, branch2Name): - '''Merge the trees 'head' and 'merge' with the common ancestor - 'common'. The name of the head branch is 'branch1Name' and the name of - the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) - where tree is the resulting tree and cleanMerge is True iff the - merge was clean.''' - - assert(isSha(head) and isSha(merge) and isSha(common)) - - if common == merge: - output('Already uptodate!') - return [head, True] - - if cacheOnly: - updateArg = '-i' - else: - updateArg = '-u' - - [out, code] = runProgram(['git-read-tree', updateArg, '-m', - common, head, merge], returnCode = True) - if code != 0: - die('git-read-tree:', out) - - [tree, code] = runProgram('git-write-tree', returnCode=True) - tree = tree.rstrip() - if code != 0: - global currentFileSet, currentDirectorySet - [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) - [filesM, dirsM] = getFilesAndDirs(merge) - currentFileSet.union_update(filesM) - currentDirectorySet.union_update(dirsM) - - entries = unmergedCacheEntries() - renamesHead = getRenames(head, common, head, merge, entries) - renamesMerge = getRenames(merge, common, head, merge, entries) - - cleanMerge = processRenames(renamesHead, renamesMerge, - branch1Name, branch2Name) - for entry in entries: - if entry.processed: - continue - if not processEntry(entry, branch1Name, branch2Name): - cleanMerge = False - - if cleanMerge or cacheOnly: - tree = runProgram('git-write-tree').rstrip() - else: - tree = None - else: - cleanMerge = True - - return [tree, cleanMerge] - -# Low level file merging, update and removal -# ------------------------------------------ - -def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, - branch1Name, branch2Name): - - merge = False - clean = True - - if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): - clean = False - if stat.S_ISREG(aMode): - mode = aMode - sha = aSha - else: - mode = bMode - sha = bSha - else: - if aSha != oSha and bSha != oSha: - merge = True - - if aMode == oMode: - mode = bMode - else: - mode = aMode - - if aSha == oSha: - sha = bSha - elif bSha == oSha: - sha = aSha - elif stat.S_ISREG(aMode): - assert(stat.S_ISREG(bMode)) - - orig = runProgram(['git-unpack-file', oSha]).rstrip() - src1 = runProgram(['git-unpack-file', aSha]).rstrip() - src2 = runProgram(['git-unpack-file', bSha]).rstrip() - try: - [out, code] = runProgram(['merge', - '-L', branch1Name + '/' + aPath, - '-L', 'orig/' + oPath, - '-L', branch2Name + '/' + bPath, - src1, orig, src2], returnCode=True) - except ProgramError, e: - print >>sys.stderr, e - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?") - - sha = runProgram(['git-hash-object', '-t', 'blob', '-w', - src1]).rstrip() - - os.unlink(orig) - os.unlink(src1) - os.unlink(src2) - - clean = (code == 0) - else: - assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) - sha = aSha - - if aSha != bSha: - clean = False - - return [sha, mode, clean, merge] - -def updateFile(clean, sha, mode, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - return updateFileExt(sha, mode, path, updateCache, updateWd) - -def updateFileExt(sha, mode, path, updateCache, updateWd): - if cacheOnly: - updateWd = False - - if updateWd: - pathComponents = path.split('/') - for x in xrange(1, len(pathComponents)): - p = '/'.join(pathComponents[0:x]) - - try: - createDir = not stat.S_ISDIR(os.lstat(p).st_mode) - except OSError: - createDir = True - - if createDir: - try: - os.mkdir(p) - except OSError, e: - die("Couldn't create directory", p, e.strerror) - - prog = ['git-cat-file', 'blob', sha] - if stat.S_ISREG(mode): - try: - os.unlink(path) - except OSError: - pass - if mode & 0100: - mode = 0777 - else: - mode = 0666 - fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) - proc = subprocess.Popen(prog, stdout=fd) - proc.wait() - os.close(fd) - elif stat.S_ISLNK(mode): - linkTarget = runProgram(prog) - os.symlink(linkTarget, path) - else: - assert(False) - - if updateWd and updateCache: - runProgram(['git-update-index', '--add', '--', path]) - elif updateCache: - runProgram(['git-update-index', '--add', '--cacheinfo', - '0%o' % mode, sha, path]) - -def setIndexStages(path, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode, - clear=True): - istring = [] - if clear: - istring.append("0 " + ("0" * 40) + "\t" + path + "\0") - if oMode: - istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) - if aMode: - istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) - if bMode: - istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) - - runProgram(['git-update-index', '-z', '--index-info'], - input="".join(istring)) - -def removeFile(clean, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - if updateCache: - runProgram(['git-update-index', '--force-remove', '--', path]) - - if updateWd: - try: - os.unlink(path) - except OSError, e: - if e.errno != errno.ENOENT and e.errno != errno.EISDIR: - raise - try: - os.removedirs(os.path.dirname(path)) - except OSError: - pass - -def uniquePath(path, branch): - def fileExists(path): - try: - os.lstat(path) - return True - except OSError, e: - if e.errno == errno.ENOENT: - return False - else: - raise - - branch = branch.replace('/', '_') - newPath = path + '~' + branch - suffix = 0 - while newPath in currentFileSet or \ - newPath in currentDirectorySet or \ - fileExists(newPath): - suffix += 1 - newPath = path + '~' + branch + '_' + str(suffix) - currentFileSet.add(newPath) - return newPath - -# Cache entry management -# ---------------------- - -class CacheEntry: - def __init__(self, path): - class Stage: - def __init__(self): - self.sha1 = None - self.mode = None - - # Used for debugging only - def __str__(self): - if self.mode != None: - m = '0%o' % self.mode - else: - m = 'None' - - if self.sha1: - sha1 = self.sha1 - else: - sha1 = 'None' - return 'sha1: ' + sha1 + ' mode: ' + m - - self.stages = [Stage(), Stage(), Stage(), Stage()] - self.path = path - self.processed = False - - def __str__(self): - return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) - -class CacheEntryContainer: - def __init__(self): - self.entries = {} - - def add(self, entry): - self.entries[entry.path] = entry - - def get(self, path): - return self.entries.get(path) - - def __iter__(self): - return self.entries.itervalues() - -unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) -def unmergedCacheEntries(): - '''Create a dictionary mapping file names to CacheEntry - objects. The dictionary contains one entry for every path with a - non-zero stage entry.''' - - lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') - lines.pop() - - res = CacheEntryContainer() - for l in lines: - m = unmergedRE.match(l) - if m: - mode = int(m.group(1), 8) - sha1 = m.group(2) - stage = int(m.group(3)) - path = m.group(4) - - e = res.get(path) - if not e: - e = CacheEntry(path) - res.add(e) - - e.stages[stage].mode = mode - e.stages[stage].sha1 = sha1 - else: - die('Error: Merge program failed: Unexpected output from', - 'git-ls-files:', l) - return res - -lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) -def getCacheEntry(path, origTree, aTree, bTree): - '''Returns a CacheEntry object which doesn't have to correspond to - a real cache entry in Git's index.''' - - def parse(out): - if out == '': - return [None, None] - else: - m = lsTreeRE.match(out) - if not m: - die('Unexpected output from git-ls-tree:', out) - elif m.group(2) == 'blob': - return [m.group(3), int(m.group(1), 8)] - else: - return [None, None] - - res = CacheEntry(path) - - [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) - [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) - [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) - - res.stages[1].sha1 = oSha - res.stages[1].mode = oMode - res.stages[2].sha1 = aSha - res.stages[2].mode = aMode - res.stages[3].sha1 = bSha - res.stages[3].mode = bMode - - return res - -# Rename detection and handling -# ----------------------------- - -class RenameEntry: - def __init__(self, - src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score): - self.srcName = src - self.srcSha = srcSha - self.srcMode = srcMode - self.srcCacheEntry = srcCacheEntry - self.dstName = dst - self.dstSha = dstSha - self.dstMode = dstMode - self.dstCacheEntry = dstCacheEntry - self.score = score - - self.processed = False - -class RenameEntryContainer: - def __init__(self): - self.entriesSrc = {} - self.entriesDst = {} - - def add(self, entry): - self.entriesSrc[entry.srcName] = entry - self.entriesDst[entry.dstName] = entry - - def getSrc(self, path): - return self.entriesSrc.get(path) - - def getDst(self, path): - return self.entriesDst.get(path) - - def __iter__(self): - return self.entriesSrc.itervalues() - -parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') -def getRenames(tree, oTree, aTree, bTree, cacheEntries): - '''Get information of all renames which occured between 'oTree' and - 'tree'. We need the three trees in the merge ('oTree', 'aTree' and - 'bTree') to be able to associate the correct cache entries with - the rename information. 'tree' is always equal to either aTree or bTree.''' - - assert(tree == aTree or tree == bTree) - inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', - '-z', oTree, tree]) - - ret = RenameEntryContainer() - try: - recs = inp.split("\0") - recs.pop() # remove last entry (which is '') - it = recs.__iter__() - while True: - rec = it.next() - m = parseDiffRenamesRE.match(rec) - - if not m: - die('Unexpected output from git-diff-tree:', rec) - - srcMode = int(m.group(1), 8) - dstMode = int(m.group(2), 8) - srcSha = m.group(3) - dstSha = m.group(4) - score = m.group(5) - src = it.next() - dst = it.next() - - srcCacheEntry = cacheEntries.get(src) - if not srcCacheEntry: - srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) - cacheEntries.add(srcCacheEntry) - - dstCacheEntry = cacheEntries.get(dst) - if not dstCacheEntry: - dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) - cacheEntries.add(dstCacheEntry) - - ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score)) - except StopIteration: - pass - return ret - -def fmtRename(src, dst): - srcPath = src.split('/') - dstPath = dst.split('/') - path = [] - endIndex = min(len(srcPath), len(dstPath)) - 1 - for x in range(0, endIndex): - if srcPath[x] == dstPath[x]: - path.append(srcPath[x]) - else: - endIndex = x - break - - if len(path) > 0: - return '/'.join(path) + \ - '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ - '/'.join(dstPath[endIndex:]) + '}' - else: - return src + ' => ' + dst - -def processRenames(renamesA, renamesB, branchNameA, branchNameB): - srcNames = Set() - for x in renamesA: - srcNames.add(x.srcName) - for x in renamesB: - srcNames.add(x.srcName) - - cleanMerge = True - for path in srcNames: - if renamesA.getSrc(path): - renames1 = renamesA - renames2 = renamesB - branchName1 = branchNameA - branchName2 = branchNameB - else: - renames1 = renamesB - renames2 = renamesA - branchName1 = branchNameB - branchName2 = branchNameA - - ren1 = renames1.getSrc(path) - ren2 = renames2.getSrc(path) - - ren1.dstCacheEntry.processed = True - ren1.srcCacheEntry.processed = True - - if ren1.processed: - continue - - ren1.processed = True - - if ren2: - # Renamed in 1 and renamed in 2 - assert(ren1.srcName == ren2.srcName) - ren2.dstCacheEntry.processed = True - ren2.processed = True - - if ren1.dstName != ren2.dstName: - output('CONFLICT (rename/rename): Rename', - fmtRename(path, ren1.dstName), 'in branch', branchName1, - 'rename', fmtRename(path, ren2.dstName), 'in', - branchName2) - cleanMerge = False - - if ren1.dstName in currentDirectorySet: - dstName1 = uniquePath(ren1.dstName, branchName1) - output(ren1.dstName, 'is a directory in', branchName2, - 'adding as', dstName1, 'instead.') - removeFile(False, ren1.dstName) - else: - dstName1 = ren1.dstName - - if ren2.dstName in currentDirectorySet: - dstName2 = uniquePath(ren2.dstName, branchName2) - output(ren2.dstName, 'is a directory in', branchName1, - 'adding as', dstName2, 'instead.') - removeFile(False, ren2.dstName) - else: - dstName2 = ren2.dstName - setIndexStages(dstName1, - None, None, - ren1.dstSha, ren1.dstMode, - None, None) - setIndexStages(dstName2, - None, None, - None, None, - ren2.dstSha, ren2.dstMode) - - else: - removeFile(True, ren1.srcName) - - [resSha, resMode, clean, merge] = \ - mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, - ren1.dstName, ren1.dstSha, ren1.dstMode, - ren2.dstName, ren2.dstSha, ren2.dstMode, - branchName1, branchName2) - - if merge or not clean: - output('Renaming', fmtRename(path, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (content): merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - ren1.srcSha, ren1.srcMode, - ren1.dstSha, ren1.dstMode, - ren2.dstSha, ren2.dstMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - else: - removeFile(True, ren1.srcName) - - # Renamed in 1, maybe changed in 2 - if renamesA == renames1: - stage = 3 - else: - stage = 2 - - srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 - srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode - - dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 - dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode - - tryMerge = False - - if ren1.dstName in currentDirectorySet: - newPath = uniquePath(ren1.dstName, branchName1) - output('CONFLICT (rename/directory): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, - 'directory', ren1.dstName, 'added in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath, 'instead') - cleanMerge = False - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath) - elif srcShaOtherBranch == None: - output('CONFLICT (rename/delete): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1, 'and deleted in', branchName2) - cleanMerge = False - updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) - elif dstShaOtherBranch: - newPath = uniquePath(ren1.dstName, branchName2) - output('CONFLICT (rename/add): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1 + '.', ren1.dstName, 'added in', branchName2) - output('Adding as', newPath, 'instead') - updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) - cleanMerge = False - tryMerge = True - elif renames2.getDst(ren1.dstName): - dst2 = renames2.getDst(ren1.dstName) - newPath1 = uniquePath(ren1.dstName, branchName1) - newPath2 = uniquePath(dst2.dstName, branchName2) - output('CONFLICT (rename/rename): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1+'. Rename', - fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath1, 'and', - dst2.srcName, 'to', newPath2, 'instead') - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) - updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) - dst2.processed = True - cleanMerge = False - else: - tryMerge = True - - if tryMerge: - - oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode - aName, bName = ren1.dstName, ren1.srcName - aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch - aMode, bMode = ren1.dstMode, srcModeOtherBranch - aBranch, bBranch = branchName1, branchName2 - - if renamesA != renames1: - aName, bName = bName, aName - aSHA1, bSHA1 = bSHA1, aSHA1 - aMode, bMode = bMode, aMode - aBranch, bBranch = bBranch, aBranch - - [resSha, resMode, clean, merge] = \ - mergeFile(oName, oSHA1, oMode, - aName, aSHA1, aMode, - bName, bSHA1, bMode, - aBranch, bBranch); - - if merge or not clean: - output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (rename/modify): Merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - - return cleanMerge - -# Per entry merge function -# ------------------------ - -def processEntry(entry, branch1Name, branch2Name): - '''Merge one cache entry.''' - - debug('processing', entry.path, 'clean cache:', cacheOnly) - - cleanMerge = True - - path = entry.path - oSha = entry.stages[1].sha1 - oMode = entry.stages[1].mode - aSha = entry.stages[2].sha1 - aMode = entry.stages[2].mode - bSha = entry.stages[3].sha1 - bMode = entry.stages[3].mode - - assert(oSha == None or isSha(oSha)) - assert(aSha == None or isSha(aSha)) - assert(bSha == None or isSha(bSha)) - - assert(oMode == None or type(oMode) is int) - assert(aMode == None or type(aMode) is int) - assert(bMode == None or type(bMode) is int) - - if (oSha and (not aSha or not bSha)): - # - # Case A: Deleted in one - # - if (not aSha and not bSha) or \ - (aSha == oSha and not bSha) or \ - (not aSha and bSha == oSha): - # Deleted in both or deleted in one and unchanged in the other - if aSha: - output('Removing', path) - removeFile(True, path) - else: - # Deleted in one and changed in the other - cleanMerge = False - if not aSha: - output('CONFLICT (delete/modify):', path, 'deleted in', - branch1Name, 'and modified in', branch2Name + '.', - 'Version', branch2Name, 'of', path, 'left in tree.') - mode = bMode - sha = bSha - else: - output('CONFLICT (modify/delete):', path, 'deleted in', - branch2Name, 'and modified in', branch1Name + '.', - 'Version', branch1Name, 'of', path, 'left in tree.') - mode = aMode - sha = aSha - - updateFile(False, sha, mode, path) - - elif (not oSha and aSha and not bSha) or \ - (not oSha and not aSha and bSha): - # - # Case B: Added in one. - # - if aSha: - addBranch = branch1Name - otherBranch = branch2Name - mode = aMode - sha = aSha - conf = 'file/directory' - else: - addBranch = branch2Name - otherBranch = branch1Name - mode = bMode - sha = bSha - conf = 'directory/file' - - if path in currentDirectorySet: - cleanMerge = False - newPath = uniquePath(path, addBranch) - output('CONFLICT (' + conf + '):', - 'There is a directory with name', path, 'in', - otherBranch + '. Adding', path, 'as', newPath) - - removeFile(False, path) - updateFile(False, sha, mode, newPath) - else: - output('Adding', path) - updateFile(True, sha, mode, path) - - elif not oSha and aSha and bSha: - # - # Case C: Added in both (check for same permissions). - # - if aSha == bSha: - if aMode != bMode: - cleanMerge = False - output('CONFLICT: File', path, - 'added identically in both branches, but permissions', - 'conflict', '0%o' % aMode, '->', '0%o' % bMode) - output('CONFLICT: adding with permission:', '0%o' % aMode) - - updateFile(False, aSha, aMode, path) - else: - # This case is handled by git-read-tree - assert(False) - else: - cleanMerge = False - newPath1 = uniquePath(path, branch1Name) - newPath2 = uniquePath(path, branch2Name) - output('CONFLICT (add/add): File', path, - 'added non-identically in both branches. Adding as', - newPath1, 'and', newPath2, 'instead.') - removeFile(False, path) - updateFile(False, aSha, aMode, newPath1) - updateFile(False, bSha, bMode, newPath2) - - elif oSha and aSha and bSha: - # - # case D: Modified in both, but differently. - # - output('Auto-merging', path) - [sha, mode, clean, dummy] = \ - mergeFile(path, oSha, oMode, - path, aSha, aMode, - path, bSha, bMode, - branch1Name, branch2Name) - if clean: - updateFile(True, sha, mode, path) - else: - cleanMerge = False - output('CONFLICT (content): Merge conflict in', path) - - if cacheOnly: - updateFile(False, sha, mode, path) - else: - updateFileExt(sha, mode, path, updateCache=False, updateWd=True) - else: - die("ERROR: Fatal merge failure, shouldn't happen.") - - return cleanMerge - -def usage(): - die('Usage:', sys.argv[0], ' ... -- ..') - -# main entry point as merge strategy module -# The first parameters up to -- are merge bases, and the rest are heads. - -if len(sys.argv) < 4: - usage() - -bases = [] -for nextArg in xrange(1, len(sys.argv)): - if sys.argv[nextArg] == '--': - if len(sys.argv) != nextArg + 3: - die('Not handling anything other than two heads merge.') - try: - h1 = firstBranch = sys.argv[nextArg + 1] - h2 = secondBranch = sys.argv[nextArg + 2] - except IndexError: - usage() - break - else: - bases.append(sys.argv[nextArg]) - -print 'Merging', h1, 'with', h2 - -try: - h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() - h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() - - if len(bases) == 1: - base = runProgram(['git-rev-parse', '--verify', - bases[0] + '^0']).rstrip() - ancestor = Commit(base, None) - [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), - firstBranch, secondBranch, None, 0, - ancestor) - else: - graph = buildGraph([h1, h2]) - [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], - firstBranch, secondBranch, graph) - - print '' -except: - if isinstance(sys.exc_info()[1], SystemExit): - raise - else: - traceback.print_exc(None, sys.stderr) - sys.exit(2) - -if clean: - sys.exit(0) -else: - sys.exit(1) diff --git a/git-merge.sh b/git-merge.sh index d049e16431..5b34b4de99 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,21 +9,15 @@ USAGE='[-n] [--no-commit] [--squash] [-s ]... < LF=' ' -all_strategies='recursive recur octopus resolve stupid ours' -case "${GIT_USE_RECUR_FOR_RECURSIVE}" in -'') - default_twohead_strategies=recursive ;; -?*) - default_twohead_strategies=recur ;; -esac +all_strategies='recur recursive recursive-old octopus resolve stupid ours' +default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t if test "@@NO_PYTHON@@"; then - all_strategies='recur resolve octopus stupid ours' - default_twohead_strategies='resolve' + all_strategies='recur recursive resolve octopus stupid ours' fi dropsave() { @@ -122,10 +116,6 @@ do strategy="$2" shift ;; esac - case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in - recursive,?*) - strategy=recur ;; - esac case " $all_strategies " in *" $strategy "*) use_strategies="$use_strategies$strategy " ;; diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 187f0883c9..c325ef761e 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -68,6 +68,12 @@ get_remote_url () { esac } +get_default_remote () { + curr_branch=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||') + origin=$(git-repo-config --get "branch.$curr_branch.remote") + echo ${origin:-origin} +} + get_remote_default_refs_for_push () { data_source=$(get_data_source "$1") case "$data_source" in @@ -86,9 +92,22 @@ get_remote_default_refs_for_push () { # Subroutine to canonicalize remote:local notation. canon_refs_list_for_fetch () { - # Leave only the first one alone; add prefix . to the rest + # If called from get_remote_default_refs_for_fetch + # leave the branches in branch.${curr_branch}.merge alone, + # or the first one otherwise; add prefix . to the rest # to prevent the secondary branches to be merged by default. - dot_prefix= + merge_branches= + if test "$1" = "-d" + then + shift ; remote="$1" ; shift + if test "$remote" = "$(get_default_remote)" + then + curr_branch=$(git-symbolic-ref HEAD | \ + sed -e 's|^refs/heads/||') + merge_branches=$(git-repo-config \ + --get-all "branch.${curr_branch}.merge") + fi + fi for ref do force= @@ -101,6 +120,18 @@ canon_refs_list_for_fetch () { expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:" remote=$(expr "z$ref" : 'z\([^:]*\):') local=$(expr "z$ref" : 'z[^:]*:\(.*\)') + dot_prefix=. + if test -z "$merge_branches" + then + merge_branches=$remote + dot_prefix= + else + for merge_branch in $merge_branches + do + [ "$remote" = "$merge_branch" ] && + dot_prefix= && break + done + fi case "$remote" in '') remote=HEAD ;; refs/heads/* | refs/tags/* | refs/remotes/*) ;; @@ -120,7 +151,6 @@ canon_refs_list_for_fetch () { die "* refusing to create funny ref '$local_ref_name' locally" fi echo "${dot_prefix}${force}${remote}:${local}" - dot_prefix=. done } @@ -131,7 +161,7 @@ get_remote_default_refs_for_fetch () { '' | config-partial | branches-partial) echo "HEAD:" ;; config) - canon_refs_list_for_fetch \ + canon_refs_list_for_fetch -d "$1" \ $(git-repo-config --get-all "remote.$1.fetch") ;; branches) remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1") @@ -139,10 +169,7 @@ get_remote_default_refs_for_fetch () { echo "refs/heads/${remote_branch}:refs/heads/$1" ;; remotes) - # This prefixes the second and later default refspecs - # with a '.', to signal git-fetch to mark them - # not-for-merge. - canon_refs_list_for_fetch $(sed -ne '/^Pull: */{ + canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{ s///p }' "$GIT_DIR/remotes/$1") ;; diff --git a/git-rebase.sh b/git-rebase.sh index 20f74d4167..a7373c0532 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -35,13 +35,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase -case "${GIT_USE_RECUR_FOR_RECURSIVE}" in -'') - strategy=recursive ;; -?*) - strategy=recur ;; -esac - +strategy=recursive do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 @@ -206,11 +200,6 @@ do shift done -case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in -recursive,?*) - strategy=recur ;; -esac - # Make sure we do not have .dotest if test -z "$do_merge" then @@ -303,11 +292,11 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recursive" +if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old" then - die 'The recursive merge strategy currently relies on Python, + die 'The recursive-old merge strategy is written in Python, which this installation of git was not configured with. Please consider -a different merge strategy (e.g. octopus, resolve, stupid, ours) +a different merge strategy (e.g. recursive, resolve, or stupid) or install Python and git with Python support.' fi diff --git a/git-resolve.sh b/git-resolve.sh index a7bc680d90..729ec65dc9 100755 --- a/git-resolve.sh +++ b/git-resolve.sh @@ -5,6 +5,10 @@ # Resolve two trees. # +echo 'WARNING: This command is DEPRECATED and will be removed very soon.' >&2 +echo 'WARNING: Please use git-merge or git-pull instead.' >&2 +sleep 2 + USAGE=' ' . git-sh-setup diff --git a/git-svn.perl b/git-svn.perl index 0290850b66..f5c7d46341 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -52,7 +52,7 @@ $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, - $_merge, $_strategy, $_dry_run); + $_merge, $_strategy, $_dry_run, $_ignore_nodate); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -65,6 +65,7 @@ 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, + 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); my ($_trunk, $_tags, $_branches); @@ -1246,6 +1247,7 @@ sub assert_svn_wc_clean { } my @status = grep(!/^Performing status on external/,(`svn status`)); @status = grep(!/^\s*$/,@status); + @status = grep(!/^X/,@status) if $_no_ignore_ext; if (scalar @status) { print STDERR "Tree ($SVN_WC) is not clean:\n"; print STDERR $_ foreach @status; @@ -1734,6 +1736,8 @@ sub next_log_entry { my $rev = $1; my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3); ($lines) = ($lines =~ /(\d+)/); + $date = '1970-01-01 00:00:00 +0000' + if ($_ignore_nodate && $date eq '(no date)'); my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~ /(\d{4})\-(\d\d)\-(\d\d)\s (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x) @@ -2168,7 +2172,7 @@ sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; while (<$authors>) { chomp; - next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; + next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/; my ($user, $name, $email) = ($1, $2, $3); $users{$user} = [$name, $email]; } diff --git a/git-svnimport.perl b/git-svnimport.perl index 26dc454795..ed628974d7 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -31,7 +31,7 @@ $ENV{'TZ'}="UTC"; our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, - $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D); + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S); sub usage() { print STDERR <$/) { - ($author_name, $author_email) = ($1, $2); + ($committer_name, $committer_email) = ($1, $2); } else { $author =~ s/^<(.*)>$/$1/; - $author_name = $author_email = $author; + $committer_name = $committer_email = $author; + } + + if ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + ($author_name, $author_email) = ($1, $2); + } else { + $author_name = $committer_name; + $author_email = $committer_email; } + $date = pdate($date); my $tag; @@ -772,8 +781,8 @@ sub commit { "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_NAME=$committer_name", + "GIT_COMMITTER_EMAIL=$committer_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"; @@ -825,7 +834,7 @@ sub commit { print $out ("object $cid\n". "type commit\n". "tag $dest\n". - "tagger $author_name <$author_email>\n") and + "tagger $committer_name <$committer_email>\n") and close($out) or die "Cannot create tag object $dest: $!\n"; diff --git a/git.c b/git.c index 44ab0de94d..ae80e78456 100644 --- a/git.c +++ b/git.c @@ -259,12 +259,10 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tar-tree", cmd_tar_tree, RUN_SETUP }, - { "zip-tree", cmd_zip_tree, RUN_SETUP }, { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, - { "upload-tar", cmd_upload_tar }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index baadbe7512..597d29f22f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -212,19 +212,9 @@ sub feature_pickaxe { } } +# We have to handle those containing any characters: our $file_name = $cgi->param('f'); -if (defined $file_name) { - if (!validate_input($file_name)) { - die_error(undef, "Invalid file parameter"); - } -} - our $file_parent = $cgi->param('fp'); -if (defined $file_parent) { - if (!validate_input($file_parent)) { - die_error(undef, "Invalid file parent parameter"); - } -} our $hash = $cgi->param('h'); if (defined $hash) { @@ -300,11 +290,12 @@ sub evaluate_path_info { $pathname =~ s,^/+,,; if (!$pathname || substr($pathname, -1) eq "/") { $action ||= "tree"; + $pathname =~ s,/$,,; } else { $action ||= "blob_plain"; } $hash_base ||= validate_input($refname); - $file_name ||= validate_input($pathname); + $file_name ||= $pathname; } elsif (defined $refname) { # we got "project.git/branch" $action ||= "shortlog"; @@ -415,7 +406,7 @@ sub validate_input { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; - $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; + $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg; $str =~ s/\+/%2B/g; $str =~ s/ /\+/g; return $str; @@ -626,7 +617,7 @@ sub format_subject_html { if (length($short) < length($long)) { return $cgi->a({-href => $href, -class => "list subject", - -title => $long}, + -title => decode("utf8", $long, Encode::FB_DEFAULT)}, esc_html($short) . $extra); } else { return $cgi->a({-href => $href, -class => "list subject"}, @@ -717,6 +708,7 @@ sub git_get_project_config { sub git_get_hash_by_path { my $base = shift; my $path = shift || return undef; + my $type = shift; my $tree = $base; @@ -727,6 +719,10 @@ sub git_get_hash_by_path { #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + if (defined $type && $type ne $2) { + # type doesn't match + return undef; + } return $3; } @@ -746,7 +742,7 @@ sub git_get_project_description { sub git_get_project_url_list { my $path = shift; - open my $fd, "$projectroot/$path/cloneurl" or return undef; + open my $fd, "$projectroot/$path/cloneurl" or return; my @git_project_url_list = map { chomp; $_ } <$fd>; close $fd; @@ -1276,7 +1272,7 @@ sub git_header_html { if (defined $action) { $title .= "/$action"; if (defined $file_name) { - $title .= " - $file_name"; + $title .= " - " . esc_html($file_name); if ($action eq "tree" && $file_name !~ m|/$|) { $title .= "/"; } @@ -1513,12 +1509,15 @@ sub git_print_page_path { my $fullname = ''; print "
"; + print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), + -title => 'tree root'}, "[$project]"); + print " / "; foreach my $dir (@dirname) { - $fullname .= $dir . '/'; + $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), -title => $fullname}, esc_html($dir)); - print "/"; + print " / "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, @@ -1528,7 +1527,6 @@ sub git_print_page_path { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), -title => $name}, esc_html($basename)); - print "/"; } else { print esc_html($basename); } @@ -1967,9 +1965,6 @@ sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); - $from = 0 unless defined $from; $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); @@ -1995,10 +1990,8 @@ sub git_shortlog_body { print "\n" . "" . $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . - $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); - if ($have_snapshot) { - print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); - } + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); print "\n" . "\n"; } @@ -2160,7 +2153,8 @@ sub git_heads_body { "\n" . "" . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . - $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . + $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") . "\n" . ""; } @@ -2274,7 +2268,8 @@ sub git_project_list { "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . "\n" . "\n"; } @@ -2425,15 +2420,18 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '-l', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . " | " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, - "head"); + "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); @@ -2498,8 +2496,11 @@ sub git_blame { $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . " | " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, - "head"); + "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, 'blob', $hash_base); @@ -2673,16 +2674,20 @@ sub git_blob { " | "; } $formats_nav .= + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$hash, file_name=>$file_name)}, - "plain") . + "raw") . " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$file_name)}, - "head"); + "HEAD"); } else { $formats_nav .= - $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "plain"); + $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw"); } git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); @@ -2708,6 +2713,9 @@ sub git_blob { } sub git_tree { + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + if (!defined $hash) { $hash = git_get_head_hash($project); if (defined $file_name) { @@ -2731,7 +2739,23 @@ sub git_tree { my $base = ""; my ($have_blame) = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { - git_print_page_nav('tree','', $hash_base); + my @views_nav = (); + if (defined $file_name) { + push @views_nav, + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "history"), + $cgi->a({-href => href(action=>"tree", + hash_base=>"HEAD", file_name=>$file_name)}, + "HEAD"), + } + if ($have_snapshot) { + # FIXME: Should be available when we have no hash base as well. + push @views_nav, + $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, + "snapshot"); + } + git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { undef $hash_base; @@ -2836,6 +2860,8 @@ sub git_log { $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . "
\n" . "
\n" . "" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]
\n" . @@ -2876,17 +2902,22 @@ sub git_commit { my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); my $have_snapshot = (defined $ctype && defined $suffix); - my $formats_nav = ''; + my @views_nav = (); if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; - $formats_nav .= + push @views_nav, $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); } + if (defined $co{'parent'}) { + push @views_nav, + $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"), + $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log"); + } git_header_html(undef, $expires); git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', $hash, $co{'tree'}, $hash, - $formats_nav); + join (' | ', @views_nav)); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); @@ -3065,7 +3096,7 @@ sub git_blobdiff { hash=>$hash, hash_parent=>$hash_parent, hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, file_name=>$file_name, file_parent=>$file_parent)}, - "plain"); + "raw"); git_header_html(undef, $expires); if (defined $hash_base && (my %co = parse_commit($hash_base))) { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); @@ -3085,7 +3116,7 @@ sub git_blobdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => qq(inline; filename="${file_name}.patch")); + -content_disposition => qq(inline; filename=") . quotemeta($file_name) . qq(.patch")); print "X-Git-Url: " . $cgi->self_url() . "\n\n"; @@ -3105,8 +3136,8 @@ sub git_blobdiff { } else { while (my $line = <$fd>) { - $line =~ s!a/($hash|$hash_parent)!a/$diffinfo{'from_file'}!g; - $line =~ s!b/($hash|$hash_parent)!b/$diffinfo{'to_file'}!g; + $line =~ s!a/($hash|$hash_parent)!'a/'.esc_html($diffinfo{'from_file'})!eg; + $line =~ s!b/($hash|$hash_parent)!'b/'.esc_html($diffinfo{'to_file'})!eg; print $line; @@ -3168,7 +3199,7 @@ sub git_commitdiff { my $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, - "plain"); + "raw"); git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); @@ -3535,7 +3566,7 @@ sub git_rss { if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { next; } - my $file = validate_input(unquote($7)); + my $file = esc_html(unquote($7)); $file = decode("utf8", $file, Encode::FB_DEFAULT); print "$file
\n"; } diff --git a/grep.c b/grep.c new file mode 100644 index 0000000000..c411ddd4d5 --- /dev/null +++ b/grep.c @@ -0,0 +1,498 @@ +#include "cache.h" +#include +#include "grep.h" + +void append_grep_pattern(struct grep_opt *opt, const char *pat, + const char *origin, int no, enum grep_pat_token t) +{ + struct grep_pat *p = xcalloc(1, sizeof(*p)); + p->pattern = pat; + p->origin = origin; + p->no = no; + p->token = t; + *opt->pattern_tail = p; + opt->pattern_tail = &p->next; + p->next = NULL; +} + +static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + int err = regcomp(&p->regexp, p->pattern, opt->regflags); + if (err) { + char errbuf[1024]; + char where[1024]; + if (p->no) + sprintf(where, "In '%s' at %d, ", + p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + regerror(err, &p->regexp, errbuf, 1024); + regfree(&p->regexp); + die("%s'%s': %s", where, p->pattern, errbuf); + } +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **); +static struct grep_expr *compile_pattern_atom(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + p = *list; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_ATOM; + x->u.atom = p; + *list = p->next; + return x; + case GREP_OPEN_PAREN: + *list = p->next; + x = compile_pattern_expr(list); + if (!x) + return NULL; + if (!*list || (*list)->token != GREP_CLOSE_PAREN) + die("unmatched parenthesis"); + *list = (*list)->next; + return x; + default: + return NULL; + } +} + +static struct grep_expr *compile_pattern_not(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + p = *list; + switch (p->token) { + case GREP_NOT: + if (!p->next) + die("--not not followed by pattern expression"); + *list = p->next; + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_NOT; + x->u.unary = compile_pattern_not(list); + if (!x->u.unary) + die("--not followed by non pattern expression"); + return x; + default: + return compile_pattern_atom(list); + } +} + +static struct grep_expr *compile_pattern_and(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + x = compile_pattern_not(list); + p = *list; + if (p && p->token == GREP_AND) { + if (!p->next) + die("--and not followed by pattern expression"); + *list = p->next; + y = compile_pattern_and(list); + if (!y) + die("--and not followed by pattern expression"); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_AND; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_or(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + x = compile_pattern_and(list); + p = *list; + if (x && p && p->token != GREP_CLOSE_PAREN) { + y = compile_pattern_or(list); + if (!y) + die("not a pattern expression %s", p->pattern); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_OR; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **list) +{ + return compile_pattern_or(list); +} + +void compile_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p; + + for (p = opt->pattern_list; p; p = p->next) { + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + if (!opt->fixed) + compile_regexp(p, opt); + break; + default: + opt->extended = 1; + break; + } + } + + if (!opt->extended) + return; + + /* Then bundle them up in an expression. + * A classic recursive descent parser would do. + */ + p = opt->pattern_list; + opt->pattern_expression = compile_pattern_expr(&p); + if (p) + die("incomplete pattern expression: %s", p->pattern); +} + +static void free_pattern_expr(struct grep_expr *x) +{ + switch (x->node) { + case GREP_NODE_ATOM: + break; + case GREP_NODE_NOT: + free_pattern_expr(x->u.unary); + break; + case GREP_NODE_AND: + case GREP_NODE_OR: + free_pattern_expr(x->u.binary.left); + free_pattern_expr(x->u.binary.right); + break; + } + free(x); +} + +void free_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p, *n; + + for (p = opt->pattern_list; p; p = n) { + n = p->next; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + regfree(&p->regexp); + break; + default: + break; + } + free(p); + } + + if (!opt->extended) + return; + free_pattern_expr(opt->pattern_expression); +} + +static char *end_of_line(char *cp, unsigned long *left) +{ + unsigned long l = *left; + while (l && *cp != '\n') { + l--; + cp++; + } + *left = l; + return cp; +} + +static int word_char(char ch) +{ + return isalnum(ch) || ch == '_'; +} + +static void show_line(struct grep_opt *opt, const char *bol, const char *eol, + const char *name, unsigned lno, char sign) +{ + if (opt->pathname) + printf("%s%c", name, sign); + if (opt->linenum) + printf("%d%c", lno, sign); + printf("%.*s\n", (int)(eol-bol), bol); +} + +/* + * NEEDSWORK: share code with diff.c + */ +#define FIRST_FEW_BYTES 8000 +static int buffer_is_binary(const char *ptr, unsigned long size) +{ + if (FIRST_FEW_BYTES < size) + size = FIRST_FEW_BYTES; + return !!memchr(ptr, 0, size); +} + +static int fixmatch(const char *pattern, char *line, regmatch_t *match) +{ + char *hit = strstr(line, pattern); + if (!hit) { + match->rm_so = match->rm_eo = -1; + return REG_NOMATCH; + } + else { + match->rm_so = hit - line; + match->rm_eo = match->rm_so + strlen(pattern); + return 0; + } +} + +static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx) +{ + int hit = 0; + int at_true_bol = 1; + regmatch_t pmatch[10]; + + if ((p->token != GREP_PATTERN) && + ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD))) + return 0; + + again: + if (!opt->fixed) { + regex_t *exp = &p->regexp; + hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), + pmatch, 0); + } + else { + hit = !fixmatch(p->pattern, bol, pmatch); + } + + if (hit && opt->word_regexp) { + if ((pmatch[0].rm_so < 0) || + (eol - bol) <= pmatch[0].rm_so || + (pmatch[0].rm_eo < 0) || + (eol - bol) < pmatch[0].rm_eo) + die("regexp returned nonsense"); + + /* Match beginning must be either beginning of the + * line, or at word boundary (i.e. the last char must + * not be a word char). Similarly, match end must be + * either end of the line, or at word boundary + * (i.e. the next char must not be a word char). + */ + if ( ((pmatch[0].rm_so == 0 && at_true_bol) || + !word_char(bol[pmatch[0].rm_so-1])) && + ((pmatch[0].rm_eo == (eol-bol)) || + !word_char(bol[pmatch[0].rm_eo])) ) + ; + else + hit = 0; + + if (!hit && pmatch[0].rm_so + bol + 1 < eol) { + /* There could be more than one match on the + * line, and the first match might not be + * strict word match. But later ones could be! + */ + bol = pmatch[0].rm_so + bol + 1; + at_true_bol = 0; + goto again; + } + } + return hit; +} + +static int match_expr_eval(struct grep_opt *opt, + struct grep_expr *x, + char *bol, char *eol, + enum grep_context ctx) +{ + switch (x->node) { + case GREP_NODE_ATOM: + return match_one_pattern(opt, x->u.atom, bol, eol, ctx); + break; + case GREP_NODE_NOT: + return !match_expr_eval(opt, x->u.unary, bol, eol, ctx); + case GREP_NODE_AND: + return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) && + match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); + case GREP_NODE_OR: + return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) || + match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); + } + die("Unexpected node type (internal error) %d\n", x->node); +} + +static int match_expr(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx) +{ + struct grep_expr *x = opt->pattern_expression; + return match_expr_eval(opt, x, bol, eol, ctx); +} + +static int match_line(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx) +{ + struct grep_pat *p; + if (opt->extended) + return match_expr(opt, bol, eol, ctx); + for (p = opt->pattern_list; p; p = p->next) { + if (match_one_pattern(opt, p, bol, eol, ctx)) + return 1; + } + return 0; +} + +int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) +{ + char *bol = buf; + unsigned long left = size; + unsigned lno = 1; + struct pre_context_line { + char *bol; + char *eol; + } *prev = NULL, *pcl; + unsigned last_hit = 0; + unsigned last_shown = 0; + int binary_match_only = 0; + const char *hunk_mark = ""; + unsigned count = 0; + enum grep_context ctx = GREP_CONTEXT_HEAD; + + if (buffer_is_binary(buf, size)) { + switch (opt->binary) { + case GREP_BINARY_DEFAULT: + binary_match_only = 1; + break; + case GREP_BINARY_NOMATCH: + return 0; /* Assume unmatch */ + break; + default: + break; + } + } + + if (opt->pre_context) + prev = xcalloc(opt->pre_context, sizeof(*prev)); + if (opt->pre_context || opt->post_context) + hunk_mark = "--\n"; + + while (left) { + char *eol, ch; + int hit = 0; + + eol = end_of_line(bol, &left); + ch = *eol; + *eol = 0; + + if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol)) + ctx = GREP_CONTEXT_BODY; + + hit = match_line(opt, bol, eol, ctx); + *eol = ch; + + /* "grep -v -e foo -e bla" should list lines + * that do not have either, so inversion should + * be done outside. + */ + if (opt->invert) + hit = !hit; + if (opt->unmatch_name_only) { + if (hit) + return 0; + goto next_line; + } + if (hit) { + count++; + if (opt->status_only) + return 1; + if (binary_match_only) { + printf("Binary file %s matches\n", name); + return 1; + } + if (opt->name_only) { + printf("%s\n", name); + return 1; + } + /* Hit at this line. If we haven't shown the + * pre-context lines, we would need to show them. + * When asked to do "count", this still show + * the context which is nonsense, but the user + * deserves to get that ;-). + */ + if (opt->pre_context) { + unsigned from; + if (opt->pre_context < lno) + from = lno - opt->pre_context; + else + from = 1; + if (from <= last_shown) + from = last_shown + 1; + if (last_shown && from != last_shown + 1) + printf(hunk_mark); + while (from < lno) { + pcl = &prev[lno-from-1]; + show_line(opt, pcl->bol, pcl->eol, + name, from, '-'); + from++; + } + last_shown = lno-1; + } + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + if (!opt->count) + show_line(opt, bol, eol, name, lno, ':'); + last_shown = last_hit = lno; + } + else if (last_hit && + lno <= last_hit + opt->post_context) { + /* If the last hit is within the post context, + * we need to show this line. + */ + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + show_line(opt, bol, eol, name, lno, '-'); + last_shown = lno; + } + if (opt->pre_context) { + memmove(prev+1, prev, + (opt->pre_context-1) * sizeof(*prev)); + prev->bol = bol; + prev->eol = eol; + } + + next_line: + bol = eol + 1; + if (!left) + break; + left--; + lno++; + } + + free(prev); + + if (opt->status_only) + return 0; + if (opt->unmatch_name_only) { + /* We did not see any hit, so we want to show this */ + printf("%s\n", name); + return 1; + } + + /* NEEDSWORK: + * The real "grep -c foo *.c" gives many "bar.c:0" lines, + * which feels mostly useless but sometimes useful. Maybe + * make it another option? For now suppress them. + */ + if (opt->count && count) + printf("%s:%u\n", name, count); + return !!last_hit; +} + diff --git a/grep.h b/grep.h new file mode 100644 index 0000000000..af9098cfe8 --- /dev/null +++ b/grep.h @@ -0,0 +1,79 @@ +#ifndef GREP_H +#define GREP_H + +enum grep_pat_token { + GREP_PATTERN, + GREP_PATTERN_HEAD, + GREP_PATTERN_BODY, + GREP_AND, + GREP_OPEN_PAREN, + GREP_CLOSE_PAREN, + GREP_NOT, + GREP_OR, +}; + +enum grep_context { + GREP_CONTEXT_HEAD, + GREP_CONTEXT_BODY, +}; + +struct grep_pat { + struct grep_pat *next; + const char *origin; + int no; + enum grep_pat_token token; + const char *pattern; + regex_t regexp; +}; + +enum grep_expr_node { + GREP_NODE_ATOM, + GREP_NODE_NOT, + GREP_NODE_AND, + GREP_NODE_OR, +}; + +struct grep_expr { + enum grep_expr_node node; + union { + struct grep_pat *atom; + struct grep_expr *unary; + struct { + struct grep_expr *left; + struct grep_expr *right; + } binary; + } u; +}; + +struct grep_opt { + struct grep_pat *pattern_list; + struct grep_pat **pattern_tail; + struct grep_expr *pattern_expression; + int prefix_length; + regex_t regexp; + unsigned linenum:1; + unsigned invert:1; + unsigned status_only:1; + unsigned name_only:1; + unsigned unmatch_name_only:1; + unsigned count:1; + unsigned word_regexp:1; + unsigned fixed:1; +#define GREP_BINARY_DEFAULT 0 +#define GREP_BINARY_NOMATCH 1 +#define GREP_BINARY_TEXT 2 + unsigned binary:2; + unsigned extended:1; + unsigned relative:1; + unsigned pathname:1; + int regflags; + unsigned pre_context; + unsigned post_context; +}; + +extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); +extern void compile_grep_patterns(struct grep_opt *opt); +extern void free_grep_patterns(struct grep_opt *opt); +extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size); + +#endif diff --git a/interpolate.c b/interpolate.c new file mode 100644 index 0000000000..4570c123dc --- /dev/null +++ b/interpolate.c @@ -0,0 +1,82 @@ +/* + * Copyright 2006 Jon Loeliger + */ + +#include + +#include "interpolate.h" + + +/* + * Convert a NUL-terminated string in buffer orig + * into the supplied buffer, result, whose length is reslen, + * performing substitutions on %-named sub-strings from + * the table, interps, with ninterps entries. + * + * Example interps: + * { + * { "%H", "example.org"}, + * { "%port", "123"}, + * { "%%", "%"}, + * } + * + * Returns 1 on a successful substitution pass that fits in result, + * Returns 0 on a failed or overflowing substitution pass. + */ + +int interpolate(char *result, int reslen, + const char *orig, + const struct interp *interps, int ninterps) +{ + const char *src = orig; + char *dest = result; + int newlen = 0; + char *name, *value; + int namelen, valuelen; + int i; + char c; + + memset(result, 0, reslen); + + while ((c = *src) && newlen < reslen - 1) { + if (c == '%') { + /* Try to match an interpolation string. */ + for (i = 0; i < ninterps; i++) { + name = interps[i].name; + namelen = strlen(name); + if (strncmp(src, name, namelen) == 0) { + break; + } + } + + /* Check for valid interpolation. */ + if (i < ninterps) { + value = interps[i].value; + valuelen = strlen(value); + + if (newlen + valuelen < reslen - 1) { + /* Substitute. */ + strncpy(dest, value, valuelen); + newlen += valuelen; + dest += valuelen; + src += namelen; + } else { + /* Something's not fitting. */ + return 0; + } + + } else { + /* Skip bogus interpolation. */ + *dest++ = *src++; + newlen++; + } + + } else { + /* Straight copy one non-interpolation character. */ + *dest++ = *src++; + newlen++; + } + } + + return newlen < reslen - 1; +} diff --git a/interpolate.h b/interpolate.h new file mode 100644 index 0000000000..d16f9244f3 --- /dev/null +++ b/interpolate.h @@ -0,0 +1,23 @@ +/* + * Copyright 2006 Jon Loeliger + */ + +#ifndef INTERPOLATE_H +#define INTERPOLATE_H + +/* + * Convert a NUL-terminated string in buffer orig, + * performing substitutions on %-named sub-strings from + * the interpretation table. + */ + +struct interp { + char *name; + char *value; +}; + +extern int interpolate(char *result, int reslen, + const char *orig, + const struct interp *interps, int ninterps); + +#endif /* INTERPOLATE_H */ diff --git a/pack-check.c b/pack-check.c index 04c6c00821..c0caaee093 100644 --- a/pack-check.c +++ b/pack-check.c @@ -42,16 +42,16 @@ static int verify_packfile(struct packed_git *p) */ for (i = err = 0; i < nr_objects; i++) { unsigned char sha1[20]; - struct pack_entry e; void *data; char type[20]; - unsigned long size; + unsigned long size, offset; if (nth_packed_object_sha1(p, i, sha1)) die("internal error pack-check nth-packed-object"); - if (!find_pack_entry_one(sha1, &e, p)) + offset = find_pack_entry_one(sha1, p); + if (!offset) die("internal error pack-check find-pack-entry-one"); - data = unpack_entry_gently(&e, type, &size); + data = unpack_entry_gently(p, offset, type, &size); if (!data) { err = error("cannot unpack %s from %s", sha1_to_hex(sha1), p->pack_name); @@ -84,25 +84,26 @@ static void show_pack_info(struct packed_git *p) for (i = 0; i < nr_objects; i++) { unsigned char sha1[20], base_sha1[20]; - struct pack_entry e; char type[20]; unsigned long size; unsigned long store_size; + unsigned long offset; unsigned int delta_chain_length; if (nth_packed_object_sha1(p, i, sha1)) die("internal error pack-check nth-packed-object"); - if (!find_pack_entry_one(sha1, &e, p)) + offset = find_pack_entry_one(sha1, p); + if (!offset) die("internal error pack-check find-pack-entry-one"); - packed_object_info_detail(&e, type, &size, &store_size, + packed_object_info_detail(p, offset, type, &size, &store_size, &delta_chain_length, base_sha1); printf("%s ", sha1_to_hex(sha1)); if (!delta_chain_length) - printf("%-6s %lu %u\n", type, size, e.offset); + printf("%-6s %lu %lu\n", type, size, offset); else { - printf("%-6s %lu %u %u %s\n", type, size, e.offset, + printf("%-6s %lu %lu %u %s\n", type, size, offset, delta_chain_length, sha1_to_hex(base_sha1)); if (delta_chain_length < MAX_CHAIN) chain_histogram[delta_chain_length]++; diff --git a/pack.h b/pack.h index eb07b033ae..05557da152 100644 --- a/pack.h +++ b/pack.h @@ -7,7 +7,7 @@ * Packed object header */ #define PACK_SIGNATURE 0x5041434b /* "PACK" */ -#define PACK_VERSION 2 +#define PACK_VERSION 3 #define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) struct pack_header { unsigned int hdr_signature; diff --git a/read-cache.c b/read-cache.c index 20c9d494ac..97c38670b4 100644 --- a/read-cache.c +++ b/read-cache.c @@ -347,11 +347,13 @@ int add_file_to_index(const char *path, int verbose) 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. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (pos >= 0) ce->ce_mode = active_cache[pos]->ce_mode; + else + ce->ce_mode = create_ce_mode(S_IFREG | 0644); } if (index_path(ce->sha1, path, &st, 1)) diff --git a/receive-pack.c b/receive-pack.c index 78f75da5ca..ea2dbd4e33 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -2,6 +2,8 @@ #include "refs.h" #include "pkt-line.h" #include "run-command.h" +#include "commit.h" +#include "object.h" static const char receive_pack_usage[] = "git-receive-pack "; @@ -127,6 +129,21 @@ static int update(struct command *cmd) return error("unpack should have generated %s, " "but I can't find it!", new_hex); } + if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { + struct commit *old_commit, *new_commit; + struct commit_list *bases, *ent; + + old_commit = (struct commit *)parse_object(old_sha1); + new_commit = (struct commit *)parse_object(new_sha1); + bases = get_merge_bases(old_commit, new_commit, 1); + for (ent = bases; ent; ent = ent->next) + if (!hashcmp(old_sha1, ent->item->object.sha1)) + break; + free_commit_list(bases); + if (!ent) + return error("denying non-fast forward;" + " you should pull first"); + } safe_create_leading_directories(lock_name); newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); diff --git a/revision.c b/revision.c index 6a2539b623..93f25130a0 100644 --- a/revision.c +++ b/revision.c @@ -6,6 +6,8 @@ #include "diff.h" #include "refs.h" #include "revision.h" +#include +#include "grep.h" static char *path_name(struct name_path *path, const char *name) { @@ -672,6 +674,42 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; } +static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what) +{ + if (!revs->grep_filter) { + struct grep_opt *opt = xcalloc(1, sizeof(*opt)); + opt->status_only = 1; + opt->pattern_tail = &(opt->pattern_list); + opt->regflags = REG_NEWLINE; + revs->grep_filter = opt; + } + append_grep_pattern(revs->grep_filter, ptn, + "command line", 0, what); +} + +static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) +{ + char *pat; + const char *prefix; + int patlen, fldlen; + + fldlen = strlen(field); + patlen = strlen(pattern); + pat = xmalloc(patlen + fldlen + 10); + prefix = ".*"; + if (*pattern == '^') { + prefix = ""; + pattern++; + } + sprintf(pat, "^%s %s%s", field, prefix, pattern); + add_grep(revs, pat, GREP_PATTERN_HEAD); +} + +static void add_message_grep(struct rev_info *revs, const char *pattern) +{ + add_grep(revs, pattern, GREP_PATTERN_BODY); +} + static void add_ignore_packed(struct rev_info *revs, const char *name) { int num = ++revs->num_ignore_packed; @@ -913,6 +951,23 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->relative_date = 1; continue; } + + /* + * Grepping the commit log + */ + if (!strncmp(arg, "--author=", 9)) { + add_header_grep(revs, "author", arg+9); + continue; + } + if (!strncmp(arg, "--committer=", 12)) { + add_header_grep(revs, "committer", arg+12); + continue; + } + if (!strncmp(arg, "--grep=", 7)) { + add_message_grep(revs, arg+7); + continue; + } + opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { revs->diff = 1; @@ -973,6 +1028,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); + if (revs->grep_filter) + compile_grep_patterns(revs->grep_filter); + return left; } @@ -1045,6 +1103,15 @@ static void mark_boundary_to_show(struct commit *commit) } } +static int commit_match(struct commit *commit, struct rev_info *opt) +{ + if (!opt->grep_filter) + return 1; + return grep_buffer(opt->grep_filter, + NULL, /* we say nothing, not even filename */ + commit->buffer, strlen(commit->buffer)); +} + struct commit *get_revision(struct rev_info *revs) { struct commit_list *list = revs->commits; @@ -1105,6 +1172,8 @@ struct commit *get_revision(struct rev_info *revs) if (revs->no_merges && commit->parents && commit->parents->next) continue; + if (!commit_match(commit, revs)) + continue; if (revs->prune_fn && revs->dense) { /* Commit without changes? */ if (!(commit->object.flags & TREECHANGE)) { diff --git a/revision.h b/revision.h index a5c35d05cb..3adab9590a 100644 --- a/revision.h +++ b/revision.h @@ -71,6 +71,9 @@ struct rev_info { const char *add_signoff; const char *extra_headers; + /* Filter by commit log message */ + struct grep_opt *grep_filter; + /* special limits */ int max_count; unsigned long max_age; diff --git a/setup.c b/setup.c index 2afdba414a..9a46a58a4a 100644 --- a/setup.c +++ b/setup.c @@ -244,6 +244,8 @@ int check_repository_format_version(const char *var, const char *value) repository_format_version = git_config_int(var, value); else if (strcmp(var, "core.sharedrepository") == 0) shared_repository = git_config_perm(var, value); + else if (strcmp(var, "receive.denynonfastforwards") == 0) + deny_non_fast_forwards = git_config_bool(var, value); return 0; } diff --git a/sha1_file.c b/sha1_file.c index b89edb9515..27b1ebb720 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -26,44 +26,40 @@ const unsigned char null_sha1[20]; static unsigned int sha1_file_open_flag = O_NOATIME; -static inline unsigned int hexval(unsigned int c) -{ - static signed char val[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ - 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ - 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ - }; - return val[c]; -} +signed char hexval_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +}; int get_sha1_hex(const char *hex, unsigned char *sha1) { @@ -888,33 +884,32 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l } /* forward declaration for a mutually recursive function */ -static int packed_object_info(struct pack_entry *entry, +static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep); -static int packed_delta_info(unsigned char *base_sha1, - unsigned long delta_size, - unsigned long left, +static int packed_delta_info(struct packed_git *p, + unsigned long offset, char *type, - unsigned long *sizep, - struct packed_git *p) + unsigned long *sizep) { - struct pack_entry base_ent; + unsigned long base_offset; + unsigned char *base_sha1 = (unsigned char *) p->pack_base + offset; - if (left < 20) + if (p->pack_size < offset + 20) die("truncated pack file"); - /* The base entry _must_ be in the same pack */ - if (!find_pack_entry_one(base_sha1, &base_ent, p)) + base_offset = find_pack_entry_one(base_sha1, p); + if (!base_offset) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); + offset += 20; /* We choose to only get the type of the base object and * ignore potentially corrupt pack file that expects the delta * based on a base with a wrong size. This saves tons of * inflate() calls. */ - - if (packed_object_info(&base_ent, type, NULL)) + if (packed_object_info(p, base_offset, type, NULL)) die("cannot get info for delta-pack base"); if (sizep) { @@ -926,8 +921,8 @@ static int packed_delta_info(unsigned char *base_sha1, memset(&stream, 0, sizeof(stream)); - data = stream.next_in = base_sha1 + 20; - stream.avail_in = left - 20; + stream.next_in = (unsigned char *) p->pack_base + offset; + stream.avail_in = p->pack_size - offset; stream.next_out = delta_head; stream.avail_out = sizeof(delta_head); @@ -989,75 +984,60 @@ int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, return status; } -void packed_object_info_detail(struct pack_entry *e, +void packed_object_info_detail(struct packed_git *p, + unsigned long offset, char *type, unsigned long *size, unsigned long *store_size, unsigned int *delta_chain_length, unsigned char *base_sha1) { - struct packed_git *p = e->p; - unsigned long offset; - unsigned char *pack; + unsigned long val; + unsigned char *next_sha1; enum object_type kind; - offset = unpack_object_header(p, e->offset, &kind, size); - pack = (unsigned char *) p->pack_base + offset; - if (kind != OBJ_DELTA) - *delta_chain_length = 0; - else { - unsigned int chain_length = 0; - if (p->pack_size <= offset + 20) - die("pack file %s records an incomplete delta base", - p->pack_name); - hashcpy(base_sha1, pack); - do { - struct pack_entry base_ent; - unsigned long junk; - - find_pack_entry_one(pack, &base_ent, p); - offset = unpack_object_header(p, base_ent.offset, - &kind, &junk); - pack = (unsigned char *) p->pack_base + offset; - chain_length++; - } while (kind == OBJ_DELTA); - *delta_chain_length = chain_length; - } - switch (kind) { - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - strcpy(type, type_names[kind]); - break; - default: - die("corrupted pack file %s containing object of kind %d", - p->pack_name, kind); + *delta_chain_length = 0; + offset = unpack_object_header(p, offset, &kind, size); + + for (;;) { + switch (kind) { + default: + die("corrupted pack file %s containing object of kind %d", + p->pack_name, kind); + case OBJ_COMMIT: + case OBJ_TREE: + case OBJ_BLOB: + case OBJ_TAG: + strcpy(type, type_names[kind]); + *store_size = 0; /* notyet */ + return; + case OBJ_DELTA: + if (p->pack_size <= offset + 20) + die("pack file %s records an incomplete delta base", + p->pack_name); + next_sha1 = (unsigned char *) p->pack_base + offset; + if (*delta_chain_length == 0) + hashcpy(base_sha1, next_sha1); + offset = find_pack_entry_one(next_sha1, p); + break; + } + offset = unpack_object_header(p, offset, &kind, &val); + (*delta_chain_length)++; } - *store_size = 0; /* notyet */ } -static int packed_object_info(struct pack_entry *entry, +static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - struct packed_git *p = entry->p; - unsigned long offset, size, left; - unsigned char *pack; + unsigned long size; enum object_type kind; - int retval; - if (use_packed_git(p)) - die("cannot map packed file"); + offset = unpack_object_header(p, offset, &kind, &size); - offset = unpack_object_header(p, entry->offset, &kind, &size); - pack = (unsigned char *) p->pack_base + offset; - left = p->pack_size - offset; + if (kind == OBJ_DELTA) + return packed_delta_info(p, offset, type, sizep); switch (kind) { - case OBJ_DELTA: - retval = packed_delta_info(pack, size, left, type, sizep, p); - unuse_packed_git(p); - return retval; case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: @@ -1070,7 +1050,6 @@ static int packed_object_info(struct pack_entry *entry, } if (sizep) *sizep = size; - unuse_packed_git(p); return 0; } @@ -1107,25 +1086,26 @@ static void *unpack_delta_entry(struct packed_git *p, char *type, unsigned long *sizep) { - struct pack_entry base_ent; void *delta_data, *result, *base; - unsigned long result_size, base_size; - unsigned char* base_sha1; + unsigned long result_size, base_size, base_offset; + unsigned char *base_sha1; - if ((offset + 20) >= p->pack_size) + if (p->pack_size < offset + 20) die("truncated pack file"); - /* The base entry _must_ be in the same pack */ base_sha1 = (unsigned char*)p->pack_base + offset; - if (!find_pack_entry_one(base_sha1, &base_ent, p)) + base_offset = find_pack_entry_one(base_sha1, p); + if (!base_offset) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); - base = unpack_entry_gently(&base_ent, type, &base_size); + offset += 20; + + base = unpack_entry_gently(p, base_offset, type, &base_size); if (!base) - die("failed to read delta-pack base object %s", - sha1_to_hex(base_sha1)); + die("failed to read delta base object at %lu from %s", + base_offset, p->pack_name); - delta_data = unpack_compressed_entry(p, offset + 20, delta_size); + delta_data = unpack_compressed_entry(p, offset, delta_size); result = patch_delta(base, base_size, delta_data, delta_size, &result_size); @@ -1145,7 +1125,7 @@ static void *unpack_entry(struct pack_entry *entry, if (use_packed_git(p)) die("cannot map packed file"); - retval = unpack_entry_gently(entry, type, sizep); + retval = unpack_entry_gently(p, entry->offset, type, sizep); unuse_packed_git(p); if (!retval) die("corrupted pack file %s", p->pack_name); @@ -1153,14 +1133,13 @@ static void *unpack_entry(struct pack_entry *entry, } /* The caller is responsible for use_packed_git()/unuse_packed_git() pair */ -void *unpack_entry_gently(struct pack_entry *entry, +void *unpack_entry_gently(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - struct packed_git *p = entry->p; - unsigned long offset, size; + unsigned long size; enum object_type kind; - offset = unpack_object_header(p, entry->offset, &kind, &size); + offset = unpack_object_header(p, offset, &kind, &size); switch (kind) { case OBJ_DELTA: return unpack_delta_entry(p, offset, size, type, sizep); @@ -1192,8 +1171,8 @@ int nth_packed_object_sha1(const struct packed_git *p, int n, return 0; } -int find_pack_entry_one(const unsigned char *sha1, - struct pack_entry *e, struct packed_git *p) +unsigned long find_pack_entry_one(const unsigned char *sha1, + struct packed_git *p) { unsigned int *level1_ofs = p->index_base; int hi = ntohl(level1_ofs[*sha1]); @@ -1203,12 +1182,8 @@ int find_pack_entry_one(const unsigned char *sha1, do { int mi = (lo + hi) / 2; int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1); - if (!cmp) { - e->offset = ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); - hashcpy(e->sha1, sha1); - e->p = p; - return 1; - } + if (!cmp) + return ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); if (cmp > 0) hi = mi; else @@ -1220,6 +1195,8 @@ int find_pack_entry_one(const unsigned char *sha1, static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) { struct packed_git *p; + unsigned long offset; + prepare_packed_git(); for (p = packed_git; p; p = p->next) { @@ -1231,8 +1208,13 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons if (*ig) continue; } - if (find_pack_entry_one(sha1, e, p)) + offset = find_pack_entry_one(sha1, p); + if (offset) { + e->offset = offset; + e->p = p; + hashcpy(e->sha1, sha1); return 1; + } } return 0; } @@ -1241,10 +1223,9 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1, struct packed_git *packs) { struct packed_git *p; - struct pack_entry e; for (p = packs; p; p = p->next) { - if (find_pack_entry_one(sha1, &e, p)) + if (find_pack_entry_one(sha1, p)) return p; } return NULL; @@ -1263,12 +1244,16 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep if (!map) { struct pack_entry e; - if (find_pack_entry(sha1, &e, NULL)) - return packed_object_info(&e, type, sizep); - reprepare_packed_git(); - if (find_pack_entry(sha1, &e, NULL)) - return packed_object_info(&e, type, sizep); - return error("unable to find %s", sha1_to_hex(sha1)); + if (!find_pack_entry(sha1, &e, NULL)) { + reprepare_packed_git(); + if (!find_pack_entry(sha1, &e, NULL)) + return error("unable to find %s", sha1_to_hex(sha1)); + } + if (use_packed_git(e.p)) + die("cannot map packed file"); + status = packed_object_info(e.p, e.offset, type, sizep); + unuse_packed_git(e.p); + return status; } if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) status = error("unable to unpack %s header", diff --git a/sha1_name.c b/sha1_name.c index 1fbc443805..9b226e3579 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -431,6 +431,26 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) return 0; } +static int get_describe_name(const char *name, int len, unsigned char *sha1) +{ + const char *cp; + + for (cp = name + len - 1; name + 2 <= cp; cp--) { + char ch = *cp; + if (hexval(ch) & ~0377) { + /* We must be looking at g in "SOMETHING-g" + * for it to be describe output. + */ + if (ch == 'g' && cp[-1] == '-') { + cp++; + len -= cp - name; + return get_short_sha1(cp, len, sha1, 1); + } + } + } + return -1; +} + static int get_sha1_1(const char *name, int len, unsigned char *sha1) { int ret, has_suffix; @@ -472,6 +492,12 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) ret = get_sha1_basic(name, len, sha1); if (!ret) return 0; + + /* It could be describe output that is "SOMETHING-gXXXX" */ + ret = get_describe_name(name, len, sha1); + if (!ret) + return 0; + return get_short_sha1(name, len, sha1, 0); } diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 6cd05c3d90..c20e4c29fc 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -19,4 +19,26 @@ test_expect_success \ 'Test that "git-add -- -q" works' \ 'touch -- -q && git-add -- -q' +test_expect_success \ + 'git-add: Test that executable bit is not used if core.filemode=0' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo1 && + chmod 755 xfoo1 && + git-add xfoo1 && + case "`git-ls-files --stage xfoo1`" in + 100644" "*xfoo1) echo ok;; + *) echo fail; git-ls-files --stage xfoo1; exit 1;; + esac' + +test_expect_success \ + 'git-update-index --add: Test that executable bit is not used...' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo2 && + chmod 755 xfoo2 && + git-update-index --add xfoo2 && + case "`git-ls-files --stage xfoo2`" in + 100644" "*xfoo2) echo ok;; + *) echo fail; git-ls-files --stage xfoo2; exit 1;; + esac' + test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index f3694ac3c7..8afb899717 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -64,4 +64,18 @@ test_expect_success \ cmp victim/.git/refs/heads/master .git/refs/heads/master ' +unset GIT_CONFIG GIT_CONFIG_LOCAL +HOME=`pwd`/no-such-directory +export HOME ;# this way we force the victim/.git/config to be used. + +test_expect_success \ + 'pushing with --force should be denied with denyNonFastforwards' ' + cd victim && + git-repo-config receive.denyNonFastforwards true && + cd .. && + git-update-ref refs/heads/master master^ && + git-send-pack --force ./victim/.git/ master && + ! diff -u .git/refs/heads/master victim/.git/refs/heads/master +' + test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh new file mode 100755 index 0000000000..df0ae4811b --- /dev/null +++ b/t/t5510-fetch.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# Copyright (c) 2006, Junio C Hamano. + +test_description='Per branch config variables affects "git fetch". + +' + +. ./test-lib.sh + +D=`pwd` + +test_expect_success setup ' + echo >file original && + git add file && + git commit -a -m original' + +test_expect_success "clone and setup child repos" ' + git clone . one && + cd one && + echo >file updated by one && + git commit -a -m "updated by one" && + cd .. && + git clone . two && + cd two && + git repo-config branch.master.remote one && + { + echo "URL: ../one/.git/" + echo "Pull: refs/heads/master:refs/heads/one" + } >.git/remotes/one + cd .. && + git clone . three && + cd three && + git repo-config branch.master.remote two && + git repo-config branch.master.merge refs/heads/one && + { + echo "URL: ../two/.git/" + echo "Pull: refs/heads/master:refs/heads/two" + echo "Pull: refs/heads/one:refs/heads/one" + } >.git/remotes/two +' + +test_expect_success "fetch test" ' + cd "$D" && + echo >file updated by origin && + git commit -a -m "updated by origin" && + cd two && + git fetch && + test -f .git/refs/heads/one && + mine=`git rev-parse refs/heads/one` && + his=`cd ../one && git rev-parse refs/heads/master` && + test "z$mine" = "z$his" +' + +test_expect_success "fetch test for-merge" ' + cd "$D" && + cd three && + git fetch && + test -f .git/refs/heads/two && + test -f .git/refs/heads/one && + master_in_two=`cd ../two && git rev-parse master` && + one_in_two=`cd ../two && git rev-parse one` && + { + echo "$master_in_two not-for-merge" + echo "$one_in_two " + } >expected && + cut -f -2 .git/FETCH_HEAD >actual && + diff expected actual' + +test_done diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh new file mode 100755 index 0000000000..b2131cdacd --- /dev/null +++ b/t/t6001-rev-list-graft.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='Revision traversal vs grafts and path limiter' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir subdir && + echo >fileA fileA && + echo >subdir/fileB fileB && + git add fileA subdir/fileB && + git commit -a -m "Initial in one history." && + A0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified && + git commit -a -m "Second in one history." && + A1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified && + git commit -a -m "Third in one history." && + A2=`git rev-parse --verify HEAD` && + + rm -f .git/refs/heads/master .git/index && + + echo >fileA fileA again && + echo >subdir/fileB fileB again && + git add fileA subdir/fileB && + git commit -a -m "Initial in alternate history." && + B0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified in alternate history && + git commit -a -m "Second in alternate history." && + B1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified in alternate history && + git commit -a -m "Third in alternate history." && + B2=`git rev-parse --verify HEAD` && + : done +' + +check () { + type=$1 + shift + + arg= + which=arg + rm -f test.expect + for a + do + if test "z$a" = z-- + then + which=expect + child= + continue + fi + if test "$which" = arg + then + arg="$arg$a " + continue + fi + if test "$type" = basic + then + echo "$a" + else + if test "z$child" != z + then + echo "$child $a" + fi + child="$a" + fi + done >test.expect + if test "$type" != basic && test "z$child" != z + then + echo >>test.expect $child + fi + if test $type = basic + then + git rev-list $arg >test.actual + elif test $type = parents + then + git rev-list --parents $arg >test.actual + elif test $type = parents-raw + then + git rev-list --parents --pretty=raw $arg | + sed -n -e 's/^commit //p' >test.actual + fi + diff test.expect test.actual +} + +for type in basic parents parents-raw +do + test_expect_success 'without grafts' " + rm -f .git/info/grafts + check $type $B2 -- $B2 $B1 $B0 + " + + test_expect_success 'with grafts' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0 + " + + test_expect_success 'without grafts, with pathlimit' " + rm -f .git/info/grafts + check $type $B2 subdir -- $B2 $B0 + " + + test_expect_success 'with grafts, with pathlimit' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 subdir -- $B2 $B0 $A2 $A0 + " + +done +test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index e2629339d4..0fe2718845 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -34,7 +34,7 @@ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR VISUAL -case $(echo $GIT_TRACE |tr [A-Z] [a-z]) in +case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in 1|2|true) echo "* warning: Some tests will not work if GIT_TRACE" \ "is set as to trace on STDERR ! *" @@ -211,7 +211,7 @@ export PATH GIT_EXEC_PATH PYTHON=`sed -e '1{ s/^#!// q -}' ../git-merge-recursive` || { +}' ../git-merge-recursive-old` || { error "You haven't built things yet, have you?" } "$PYTHON" -c 'import subprocess' 2>/dev/null || {