diff options
44 files changed, 2406 insertions, 239 deletions
diff --git a/.gitignore b/.gitignore index 55cd9844d..78cb67153 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ git-merge-tree git-merge-octopus git-merge-one-file git-merge-ours +git-merge-recur git-merge-recursive git-merge-resolve git-merge-stupid @@ -124,6 +125,7 @@ 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/git-apply.txt b/Documentation/git-apply.txt index 20e12ceda..c76cfffdc 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -75,8 +75,8 @@ OPTIONS For atomicity, gitlink:git-apply[1] by default fails the whole patch and does not touch the working tree when some of the hunks do not apply. This option makes it apply - the parts of the patch that are applicable, and send the - rejected hunks to the standard output of the command. + the parts of the patch that are applicable, and leave the + rejected hunks in corresponding *.rej files. -z:: When showing the index information, do not munge paths, diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 0f7d274ea..17619a3f5 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,7 +11,8 @@ 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] - [--reuseaddr] [--detach] [--pid-file=file] [directory...] + [--reuseaddr] [--detach] [--pid-file=file] + [--user=user [--group=group]] [directory...] DESCRIPTION ----------- @@ -93,6 +94,17 @@ OPTIONS --pid-file=file:: Save the process id in 'file'. +--user=user, --group=group:: + Change daemon's uid and gid before entering the service loop. + When only `--user` is given without `--group`, the + primary group ID for the user is used. The values of + the option are given to `getpwnam(3)` and `getgrnam(3)` + and numeric IDs are not supported. ++ +Giving these options is an error when used with `--inetd`; use +the facility of inet daemon to achieve the same before spawning +`git-daemon` if needed. + <directory>:: A directory to add to the whitelist of allowed directories. Unless --strict-paths is specified this will also include subdirectories diff --git a/Documentation/git-zip-tree.txt b/Documentation/git-zip-tree.txt new file mode 100644 index 000000000..2e9d98124 --- /dev/null +++ b/Documentation/git-zip-tree.txt @@ -0,0 +1,67 @@ +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] <tree-ish> [ <base> ] + +DESCRIPTION +----------- +Creates a ZIP archive containing the tree structure for the named tree. +When <base> 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. + +<tree-ish>:: + The tree or commit to produce ZIP archive for. If it is + the object name of a commit object. + +<base>:: + 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@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite + @@ -196,6 +196,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 \ $(EXTRA_PROGRAMS) # Empty... @@ -292,7 +293,8 @@ BUILTIN_OBJS = \ builtin-update-ref.o \ builtin-upload-tar.o \ builtin-verify-pack.o \ - builtin-write-tree.o + builtin-write-tree.o \ + builtin-zip-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -710,6 +712,11 @@ 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 @@ -869,7 +876,7 @@ check-docs:: do \ case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ - git-merge-resolve | git-merge-stupid | \ + git-merge-resolve | git-merge-stupid | git-merge-recur | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/builtin-apply.c b/builtin-apply.c index f8f5eebd2..1a1deaf78 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -38,12 +38,14 @@ static int summary; static int check; static int apply = 1; static int apply_in_reverse; +static int apply_with_reject; +static int apply_verbosely; static int no_add; static int show_index_info; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; static enum whitespace_eol { nowarn_whitespace, @@ -122,6 +124,7 @@ struct fragment { unsigned long newpos, newlines; const char *patch; int size; + int rejected; struct fragment *next; }; @@ -138,6 +141,7 @@ struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; int is_rename, is_copy, is_new, is_delete, is_binary; + int rejected; unsigned long deflate_origlen; int lines_added, lines_deleted; int score; @@ -150,6 +154,24 @@ struct patch { struct patch *next; }; +static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post) +{ + fputs(pre, output); + if (patch->old_name && patch->new_name && + strcmp(patch->old_name, patch->new_name)) { + write_name_quoted(NULL, 0, patch->old_name, 1, output); + fputs(" => ", output); + write_name_quoted(NULL, 0, patch->new_name, 1, output); + } + else { + const char *n = patch->new_name; + if (!n) + n = patch->old_name; + write_name_quoted(NULL, 0, n, 1, output); + } + fputs(post, output); +} + #define CHUNKSIZE (8192) #define SLOP (16) @@ -1061,8 +1083,12 @@ static struct fragment *parse_binary_hunk(char **buf_p, llen = linelen(buffer, size); used += llen; linenr++; - if (llen == 1) + if (llen == 1) { + /* consume the blank line */ + buffer++; + size--; break; + } /* Minimum line is "A00000\n" which is 7-byte long, * and the line length must be multiple of 5 plus 2. */ @@ -1105,8 +1131,7 @@ static struct fragment *parse_binary_hunk(char **buf_p, return frag; corrupt: - if (data) - free(data); + free(data); *status_p = -1; error("corrupt binary patch at line %d: %.*s", linenr-1, llen-1, buffer); @@ -1303,8 +1328,7 @@ static void show_stats(struct patch *patch) printf(" %s%-*s |%5d %.*s%.*s\n", prefix, len, name, patch->lines_added + patch->lines_deleted, add, pluses, del, minuses); - if (qname) - free(qname); + free(qname); } static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size) @@ -1542,7 +1566,8 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i lines = 0; pos = frag->newpos; for (;;) { - offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); + offset = find_offset(buf, desc->size, + oldlines, oldsize, pos, &lines); if (match_end && offset + oldsize != desc->size) offset = -1; if (match_beginning && offset) @@ -1555,8 +1580,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i /* Warn if it was necessary to reduce the number * of context lines. */ - if ((leading != frag->leading) || (trailing != frag->trailing)) - fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n", + if ((leading != frag->leading) || + (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld)" + " to apply fragment at %d\n", leading, trailing, pos + lines); if (size > alloc) { @@ -1566,7 +1593,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i desc->buffer = buf; } desc->size = size; - memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); + memmove(buf + offset + newsize, + buf + offset + oldsize, + size - offset - newsize); memcpy(buf + offset, newlines, newsize); offset = 0; @@ -1616,7 +1645,7 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) "without the reverse hunk to '%s'", patch->new_name ? patch->new_name : patch->old_name); - fragment = fragment; + fragment = fragment->next; } data = (void*) fragment->patch; switch (fragment->binary_patch_method) { @@ -1715,7 +1744,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) write_sha1_file_prepare(desc->buffer, desc->size, blob_type, sha1, hdr, &hdrlen); if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error("binary patch to '%s' creates incorrect result", name); + return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1)); } return 0; @@ -1730,9 +1759,12 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return apply_binary(desc, patch); while (frag) { - if (apply_one_fragment(desc, frag, patch->inaccurate_eof) < 0) - return error("patch failed: %s:%ld", - name, frag->oldpos); + if (apply_one_fragment(desc, frag, patch->inaccurate_eof)) { + error("patch failed: %s:%ld", name, frag->oldpos); + if (!apply_with_reject) + return -1; + frag->rejected = 1; + } frag = frag->next; } return 0; @@ -1768,8 +1800,9 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * desc.size = size; desc.alloc = alloc; desc.buffer = buf; + if (apply_fragments(&desc, patch) < 0) - return -1; + return -1; /* note with --reject this succeeds. */ /* NUL terminate the result */ if (desc.alloc <= desc.size) @@ -1794,6 +1827,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) struct cache_entry *ce = NULL; int ok_if_exists; + patch->rejected = 1; /* we will drop this after we succeed */ if (old_name) { int changed = 0; int stat_ret = 0; @@ -1899,6 +1933,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) if (apply_data(patch, &st, ce) < 0) return error("%s: patch does not apply", name); + patch->rejected = 0; return 0; } @@ -1908,6 +1943,9 @@ static int check_patch_list(struct patch *patch) int err = 0; for (prev_patch = NULL; patch ; patch = patch->next) { + if (apply_verbosely) + say_patch_name(stderr, + "Checking patch ", patch, "...\n"); err |= check_patch(patch, prev_patch); prev_patch = patch; } @@ -2217,23 +2255,99 @@ static void write_out_one_result(struct patch *patch, int phase) if (phase == 0) remove_file(patch); if (phase == 1) - create_file(patch); + create_file(patch); } -static void write_out_results(struct patch *list, int skipped_patch) +static int write_out_one_reject(struct patch *patch) +{ + FILE *rej; + char namebuf[PATH_MAX]; + struct fragment *frag; + int cnt = 0; + + for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { + if (!frag->rejected) + continue; + cnt++; + } + + if (!cnt) { + if (apply_verbosely) + say_patch_name(stderr, + "Applied patch ", patch, " cleanly.\n"); + return 0; + } + + /* This should not happen, because a removal patch that leaves + * contents are marked "rejected" at the patch level. + */ + if (!patch->new_name) + die("internal error"); + + /* Say this even without --verbose */ + say_patch_name(stderr, "Applying patch ", patch, " with"); + fprintf(stderr, " %d rejects...\n", cnt); + + cnt = strlen(patch->new_name); + if (ARRAY_SIZE(namebuf) <= cnt + 5) { + cnt = ARRAY_SIZE(namebuf) - 5; + fprintf(stderr, + "warning: truncating .rej filename to %.*s.rej", + cnt - 1, patch->new_name); + } + memcpy(namebuf, patch->new_name, cnt); + memcpy(namebuf + cnt, ".rej", 5); + + rej = fopen(namebuf, "w"); + if (!rej) + return error("cannot open %s: %s", namebuf, strerror(errno)); + + /* Normal git tools never deal with .rej, so do not pretend + * this is a git patch by saying --git nor give extended + * headers. While at it, maybe please "kompare" that wants + * the trailing TAB and some garbage at the end of line ;-). + */ + fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", + patch->new_name, patch->new_name); + for (cnt = 1, frag = patch->fragments; + frag; + cnt++, frag = frag->next) { + if (!frag->rejected) { + fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt); + continue; + } + fprintf(stderr, "Rejected hunk #%d.\n", cnt); + fprintf(rej, "%.*s", frag->size, frag->patch); + if (frag->patch[frag->size-1] != '\n') + fputc('\n', rej); + } + fclose(rej); + return -1; +} + +static int write_out_results(struct patch *list, int skipped_patch) { int phase; + int errs = 0; + struct patch *l; if (!list && !skipped_patch) - die("No changes"); + return error("No changes"); for (phase = 0; phase < 2; phase++) { - struct patch *l = list; + l = list; while (l) { - write_out_one_result(l, phase); + if (l->rejected) + errs = 1; + else { + write_out_one_result(l, phase); + if (phase == 1 && write_out_one_reject(l)) + errs = 1; + } l = l->next; } } + return errs; } static struct lock_file lock_file; @@ -2308,11 +2422,13 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) die("unable to read index file"); } - if ((check || apply) && check_patch_list(list) < 0) + if ((check || apply) && + check_patch_list(list) < 0 && + !apply_with_reject) exit(1); - if (apply) - write_out_results(list, skipped_patch); + if (apply && write_out_results(list, skipped_patch)) + exit(1); if (show_index_info) show_index_list(list); @@ -2345,6 +2461,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int i; int read_stdin = 1; int inaccurate_eof = 0; + int errs = 0; const char *whitespace_option = NULL; @@ -2354,7 +2471,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int fd; if (!strcmp(arg, "-")) { - apply_patch(0, "<stdin>", inaccurate_eof); + errs |= apply_patch(0, "<stdin>", inaccurate_eof); read_stdin = 0; continue; } @@ -2435,6 +2552,14 @@ int cmd_apply(int argc, const char **argv, const char *prefix) apply_in_reverse = 1; continue; } + if (!strcmp(arg, "--reject")) { + apply = apply_with_reject = apply_verbosely = 1; + continue; + } + if (!strcmp(arg, "--verbose")) { + apply_verbosely = 1; + continue; + } if (!strcmp(arg, "--inaccurate-eof")) { inaccurate_eof = 1; continue; @@ -2455,18 +2580,19 @@ int cmd_apply(int argc, const char **argv, const char *prefix) usage(apply_usage); read_stdin = 0; set_default_whitespace_mode(whitespace_option); - apply_patch(fd, arg, inaccurate_eof); + errs |= apply_patch(fd, arg, inaccurate_eof); close(fd); } set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0, "<stdin>", inaccurate_eof); + errs |= apply_patch(0, "<stdin>", inaccurate_eof); if (whitespace_error) { if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) { int squelched = whitespace_error - squelch_whitespace_errors; - fprintf(stderr, "warning: squelched %d whitespace error%s\n", + fprintf(stderr, "warning: squelched %d " + "whitespace error%s\n", squelched, squelched == 1 ? "" : "s"); } @@ -2494,5 +2620,5 @@ int cmd_apply(int argc, const char **argv, const char *prefix) die("Unable to write new index file"); } - return 0; + return !!errs; } diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 7a6fa56e9..6c16bfa1a 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -54,7 +54,7 @@ static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long write_or_die(1, tagger, sp - tagger); date = strtoul(sp, &ep, 10); tz = strtol(ep, NULL, 10); - sp = show_date(date, tz); + sp = show_date(date, tz, 0); write_or_die(1, sp, strlen(sp)); xwrite(1, "\n", 1); break; diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index 6b55f931c..b097c888a 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -45,7 +45,7 @@ static int line_termination = '\n'; static int checkout_stage; /* default to checkout stage0 */ static int to_tempfile; -static char topath[4][MAXPATHLEN+1]; +static char topath[4][PATH_MAX + 1]; static struct checkout state; diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 28b5dfd05..76d22b47b 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -27,8 +27,8 @@ static void append_to_list(struct list *list, char *value, void *payload) { if (list->nr == list->alloc) { list->alloc += 32; - list->list = realloc(list->list, sizeof(char *) * list->alloc); - list->payload = realloc(list->payload, + list->list = xrealloc(list->list, sizeof(char *) * list->alloc); + list->payload = xrealloc(list->payload, sizeof(char *) * list->alloc); } list->payload[list->nr] = payload; @@ -55,8 +55,7 @@ static void free_list(struct list *list) for (i = 0; i < list->nr; i++) { free(list->list[i]); - if (list->payload[i]) - free(list->payload[i]); + free(list->payload[i]); } free(list->list); free(list->payload); diff --git a/builtin-log.c b/builtin-log.c index 691cf3aef..fbc58bbca 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -101,7 +101,7 @@ static int git_format_config(const char *var, const char *value) if (!strcmp(var, "format.headers")) { int len = strlen(value); extra_headers_size += len + 1; - extra_headers = realloc(extra_headers, extra_headers_size); + extra_headers = xrealloc(extra_headers, extra_headers_size); extra_headers[extra_headers_size - len - 1] = 0; strcat(extra_headers, value); return 0; @@ -381,7 +381,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; nr++; - list = realloc(list, nr * sizeof(list[0])); + list = xrealloc(list, nr * sizeof(list[0])); list[nr - 1] = commit; } total = nr; diff --git a/builtin-mv.c b/builtin-mv.c index fd1e52098..4d21d8841 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -168,13 +168,13 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int j, dst_len; if (last - first > 0) { - source = realloc(source, + source = xrealloc(source, (count + last - first) * sizeof(char *)); - destination = realloc(destination, + destination = xrealloc(destination, (count + last - first) * sizeof(char *)); - modes = realloc(modes, + modes = xrealloc(modes, (count + last - first) * sizeof(enum update_mode)); } diff --git a/builtin-repo-config.c b/builtin-repo-config.c index c41648020..6560cf1c2 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -122,10 +122,8 @@ static int get_value(const char* key_, const char* regex_) ret = (seen == 1) ? 0 : 1; free_strings: - if (repo_config) - free(repo_config); - if (global) - free(global); + free(repo_config); + free(global); return ret; } diff --git a/builtin-rev-list.c b/builtin-rev-list.c index bc48a3e23..7f3e1fcfb 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -93,10 +93,8 @@ static void show_commit(struct commit *commit) free_commit_list(commit->parents); commit->parents = NULL; } - if (commit->buffer) { - free(commit->buffer); - commit->buffer = NULL; - } + free(commit->buffer); + commit->buffer = NULL; } static void process_blob(struct blob *blob, diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c new file mode 100644 index 000000000..a5b834d36 --- /dev/null +++ b/builtin-zip-tree.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2006 Rene Scharfe + */ +#include <time.h> +#include "cache.h" +#include "commit.h" +#include "blob.h" +#include "tree.h" +#include "quote.h" +#include "builtin.h" + +static const char zip_tree_usage[] = +"git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]"; + +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 (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 = strdup(argv[2]); + baselen = strlen(base); + break; + case 2: + base = strdup(""); + 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; +} @@ -52,6 +52,7 @@ 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); @@ -145,6 +145,7 @@ extern void verify_non_filename(const char *prefix, const char *name); extern int read_cache(void); extern int read_cache_from(const char *path); extern int write_cache(int newfd, struct cache_entry **cache, int entries); +extern int discard_cache(void); extern int verify_path(const char *path); extern int cache_name_pos(const char *name, int namelen); #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ @@ -286,7 +287,7 @@ extern void *read_object_with_reference(const unsigned char *sha1, unsigned long *size, unsigned char *sha1_ret); -const char *show_date(unsigned long time, int timezone); +const char *show_date(unsigned long time, int timezone, int relative); const char *show_rfc2822_date(unsigned long time, int timezone); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); @@ -507,14 +507,14 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c } switch (fmt) { case CMIT_FMT_MEDIUM: - ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); + ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz, 0)); break; case CMIT_FMT_EMAIL: ret += sprintf(buf + ret, "Date: %s\n", show_rfc2822_date(time, tz)); break; case CMIT_FMT_FULLER: - ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); + ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz, 0)); break; default: /* notin' */ @@ -361,8 +361,7 @@ int git_config(config_fn_t fn) } ret += git_config_from_file(fn, filename); - if (repo_config) - free(repo_config); + free(repo_config); return ret; } @@ -734,8 +733,7 @@ int git_config_set_multivar(const char* key, const char* value, out_free: if (0 <= fd) close(fd); - if (config_filename) - free(config_filename); + free(config_filename); if (lock_file) { unlink(lock_file); free(lock_file); @@ -7,6 +7,8 @@ #include <netinet/in.h> #include <arpa/inet.h> #include <syslog.h> +#include <pwd.h> +#include <grp.h> #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" @@ -19,7 +21,8 @@ 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" -" [--reuseaddr] [--detach] [--pid-file=file] [directory...]"; +" [--reuseaddr] [--detach] [--pid-file=file]\n" +" [--user=user [[--group=group]] [directory...]"; /* List of acceptable pathname prefixes */ static char **ok_paths; @@ -526,7 +529,6 @@ static int socksetup(int port, int **socklist_p) for (ai = ai0; ai; ai = ai->ai_next) { int sockfd; - int *newlist; sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sockfd < 0) @@ -560,11 +562,7 @@ static int socksetup(int port, int **socklist_p) continue; /* not fatal */ } - newlist = realloc(socklist, sizeof(int) * (socknum + 1)); - if (!newlist) - die("memory allocation failed: %s", strerror(errno)); - - socklist = newlist; + socklist = xrealloc(socklist, sizeof(int) * (socknum + 1)); socklist[socknum++] = sockfd; if (maxfd < sockfd) @@ -701,7 +699,7 @@ static void store_pid(const char *path) fclose(f); } -static int serve(int port) +static int serve(int port, struct passwd *pass, gid_t gid) { int socknum, *socklist; @@ -709,6 +707,11 @@ static int serve(int port) if (socknum == 0) die("unable to allocate any listen sockets on port %u", port); + if (pass && gid && + (initgroups(pass->pw_name, gid) || setgid (gid) || + setuid(pass->pw_uid))) + die("cannot drop privileges"); + return service_loop(socknum, socklist); } @@ -716,8 +719,11 @@ int main(int argc, char **argv) { int port = DEFAULT_GIT_PORT; int inetd_mode = 0; - const char *pid_file = NULL; + const char *pid_file = NULL, *user_name = NULL, *group_name = NULL; int detach = 0; + struct passwd *pass = NULL; + struct group *group; + gid_t gid = 0; int i; /* Without this we cannot rely on waitpid() to tell @@ -791,6 +797,14 @@ int main(int argc, char **argv) log_syslog = 1; continue; } + if (!strncmp(arg, "--user=", 7)) { + user_name = arg + 7; + continue; + } + if (!strncmp(arg, "--group=", 8)) { + group_name = arg + 8; + continue; + } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; break; @@ -802,6 +816,28 @@ int main(int argc, char **argv) usage(daemon_usage); } + if (inetd_mode && (group_name || user_name)) + die("--user and --group are incompatible with --inetd"); + + if (group_name && !user_name) + die("--group supplied without --user"); + + if (user_name) { + pass = getpwnam(user_name); + if (!pass) + die("user not found - %s", user_name); + + if (!group_name) + gid = pass->pw_gid; + else { + group = getgrnam(group_name); + if (!group) + die("group not found - %s", group_name); + + gid = group->gr_gid; + } + } + if (log_syslog) { openlog("git-daemon", 0, LOG_DAEMON); set_die_routine(daemon_die); @@ -831,5 +867,5 @@ int main(int argc, char **argv) if (pid_file) store_pid(pid_file); - return serve(port); + return serve(port, pass, gid); } @@ -37,6 +37,16 @@ static const char *weekday_names[] = { "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" }; +static time_t gm_time_t(unsigned long time, int tz) +{ + int minutes; + + minutes = tz < 0 ? -tz : tz; + minutes = (minutes / 100)*60 + (minutes % 100); + minutes = tz < 0 ? -minutes : minutes; + return time + minutes * 60; +} + /* * The "tz" thing is passed in as this strange "decimal parse of tz" * thing, which means that tz -0100 is passed in as the integer -100, @@ -44,21 +54,58 @@ static const char *weekday_names[] = { */ static struct tm *time_to_tm(unsigned long time, int tz) { - time_t t; - int minutes; - - minutes = tz < 0 ? -tz : tz; - minutes = (minutes / 100)*60 + (minutes % 100); - minutes = tz < 0 ? -minutes : minutes; - t = time + minutes * 60; + time_t t = gm_time_t(time, tz); return gmtime(&t); } -const char *show_date(unsigned long time, int tz) +const char *show_date(unsigned long time, int tz, int relative) { struct tm *tm; static char timebuf[200]; + if (relative) { + unsigned long diff; + time_t t = gm_time_t(time, tz); + struct timeval now; + gettimeofday(&now, NULL); + if (now.tv_sec < t) + return "in the future"; + diff = now.tv_sec - t; + if (diff < 90) { + snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff); + return timebuf; + } + /* Turn it into minutes */ + diff = (diff + 30) / 60; + if (diff < 90) { + snprintf(timebuf, sizeof(timebuf), "%lu minutes ago", diff); + return timebuf; + } + /* Turn it into hours */ + diff = (diff + 30) / 60; + if (diff < 36) { + snprintf(timebuf, sizeof(timebuf), "%lu hours ago", diff); + return timebuf; + } + /* We deal with number of days from here on */ + diff = (diff + 12) / 24; + if (diff < 14) { + snprintf(timebuf, sizeof(timebuf), "%lu days ago", diff); + return timebuf; + } + /* Say weeks for the past 10 weeks or so */ + if (diff < 70) { + snprintf(timebuf, sizeof(timebuf), "%lu weeks ago", (diff + 3) / 7); + return timebuf; + } + /* Say months for the past 12 months or so */ + if (diff < 360) { + snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30); + return timebuf; + } + /* Else fall back on absolute format.. */ + } + tm = time_to_tm(time, tz); if (!tm) return NULL; diff --git a/diff-delta.c b/diff-delta.c index 51e2e5617..fa16d06c8 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -392,7 +392,7 @@ create_delta(const struct delta_index *index, outsize = max_size + MAX_OP_SIZE + 1; if (max_size && outpos > max_size) break; - out = realloc(out, outsize); + out = xrealloc(out, outsize); if (!out) { free(tmp); return NULL; @@ -838,7 +838,7 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -849,7 +849,6 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) unsigned long deflate_size; unsigned long data_size; - printf("GIT binary patch\n"); /* We could do deflated delta, or we could do just deflated two, * whichever is smaller. */ @@ -898,6 +897,13 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) free(data); } +static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +{ + printf("GIT binary patch\n"); + emit_binary_diff_body(one, two); + emit_binary_diff_body(two, one); +} + #define FIRST_FEW_BYTES 8000 static int mmfile_is_binary(mmfile_t *mf) { @@ -101,8 +101,8 @@ void add_exclude(const char *string, const char *base, x->baselen = baselen; if (which->nr == which->alloc) { which->alloc = alloc_nr(which->alloc); - which->excludes = realloc(which->excludes, - which->alloc * sizeof(x)); + which->excludes = xrealloc(which->excludes, + which->alloc * sizeof(x)); } which->excludes[which->nr++] = x; } @@ -112,17 +112,15 @@ static int add_excludes_from_file_1(const char *fname, int baselen, struct exclude_list *which) { + struct stat st; int fd, i; long size; char *buf, *entry; fd = open(fname, O_RDONLY); - if (fd < 0) + if (fd < 0 || fstat(fd, &st) < 0) goto err; - size = lseek(fd, 0, SEEK_END); - if (size < 0) - goto err; - lseek(fd, 0, SEEK_SET); + size = st.st_size; if (size == 0) { close(fd); return 0; @@ -293,7 +291,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (fdir) { int exclude_stk; struct dirent *de; - char fullname[MAXPATHLEN + 1]; + char fullname[PATH_MAX + 1]; memcpy(fullname, base, baselen); exclude_stk = push_exclude_per_directory(dir, base, baselen); @@ -135,7 +135,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) { - static char path[MAXPATHLEN+1]; + static char path[PATH_MAX + 1]; struct stat st; int len = state->base_dir_len; @@ -172,5 +172,3 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) create_directories(path, state); return write_entry(ce, path, state, 0); } - - @@ -302,8 +302,7 @@ int pull(int targets, char **target, const char **write_ref, if (ret) goto unlock_and_fail; } - if (msg) - free(msg); + free(msg); return 0; diff --git a/git-cherry.sh b/git-cherry.sh index f0e8831fa..8832573fe 100755 --- a/git-cherry.sh +++ b/git-cherry.sh @@ -51,9 +51,6 @@ patch=$tmp-patch mkdir $patch trap "rm -rf $tmp-*" 0 1 2 3 15 -_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" - for c in $inup do git-diff-tree -p $c diff --git a/git-compat-util.h b/git-compat-util.h index b2e18954c..91f2b0d3f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -172,7 +172,4 @@ static inline int sane_case(int x, int high) return x; } -#ifndef MAXPATHLEN -#define MAXPATHLEN 256 -#endif #endif diff --git a/git-merge.sh b/git-merge.sh index a9cfafb1d..d049e1643 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,15 +9,20 @@ USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> < LF=' ' -all_strategies='recursive octopus resolve stupid ours' -default_twohead_strategies='recursive' +all_strategies='recursive recur octopus resolve stupid ours' +case "${GIT_USE_RECUR_FOR_RECURSIVE}" in +'') + default_twohead_strategies=recursive ;; +?*) + default_twohead_strategies=recur ;; +esac default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t if test "@@NO_PYTHON@@"; then - all_strategies='resolve octopus stupid ours' + all_strategies='recur resolve octopus stupid ours' default_twohead_strategies='resolve' fi @@ -117,6 +122,10 @@ 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-rebase.sh b/git-rebase.sh index 7d3a5d0e7..20f74d416 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -35,7 +35,13 @@ 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 -strategy=recursive +case "${GIT_USE_RECUR_FOR_RECURSIVE}" in +'') + strategy=recursive ;; +?*) + strategy=recur ;; +esac + do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 @@ -200,6 +206,11 @@ 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 diff --git a/git-reset.sh b/git-reset.sh index 36fc8ce25..3133b5bd2 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -3,9 +3,6 @@ USAGE='[--mixed | --soft | --hard] [<commit-ish>]' . git-sh-setup -tmp=${GIT_DIR}/reset.$$ -trap 'rm -f $tmp-*' 0 1 2 3 15 - update= reset_type=--mixed case "$1" in diff --git a/git-svn.perl b/git-svn.perl index 9382a1504..0290850b6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -819,6 +819,7 @@ sub commit_diff { } else { $ed->close_edit; } + $_message = $_file = undef; } ########################### utility functions ######################### @@ -120,7 +120,7 @@ static int split_cmdline(char *cmdline, const char ***argv) ; /* skip */ if (count >= size) { size += 16; - *argv = realloc(*argv, sizeof(char*) * size); + *argv = xrealloc(*argv, sizeof(char*) * size); } (*argv)[count++] = cmdline + dst; } else if(!quoted && (c == '\'' || c == '"')) { @@ -191,8 +191,8 @@ static int handle_alias(int *argcp, const char ***argv) fflush(stderr); } - new_argv = realloc(new_argv, sizeof(char*) * - (count + *argcp + 1)); + new_argv = xrealloc(new_argv, sizeof(char*) * + (count + *argcp + 1)); /* insert after command name */ memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); new_argv[count+*argcp] = NULL; @@ -263,6 +263,7 @@ 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 }, diff --git a/http-fetch.c b/http-fetch.c index 7619b338f..6806f3678 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -696,10 +696,8 @@ xml_start_tag(void *userData, const char *name, const char **atts) strcat(ctx->name, "."); strcat(ctx->name, c); - if (ctx->cdata) { - free(ctx->cdata); - ctx->cdata = NULL; - } + free(ctx->cdata); + ctx->cdata = NULL; ctx->userFunc(ctx, 0); } @@ -726,8 +724,7 @@ static void xml_cdata(void *userData, const XML_Char *s, int len) { struct xml_ctx *ctx = (struct xml_ctx *)userData; - if (ctx->cdata) - free(ctx->cdata); + free(ctx->cdata); ctx->cdata = xmalloc(len + 1); strlcpy(ctx->cdata, s, len + 1); } @@ -765,9 +762,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->dentry_flags |= IS_DIR; } } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - if (ls->dentry_name) { - free(ls->dentry_name); - } + free(ls->dentry_name); ls->dentry_name = NULL; ls->dentry_flags = 0; } diff --git a/http-push.c b/http-push.c index 04cb238e9..7814666d8 100644 --- a/http-push.c +++ b/http-push.c @@ -1238,10 +1238,8 @@ xml_start_tag(void *userData, const char *name, const char **atts) strcat(ctx->name, "."); strcat(ctx->name, c); - if (ctx->cdata) { - free(ctx->cdata); - ctx->cdata = NULL; - } + free(ctx->cdata); + ctx->cdata = NULL; ctx->userFunc(ctx, 0); } @@ -1268,8 +1266,7 @@ static void xml_cdata(void *userData, const XML_Char *s, int len) { struct xml_ctx *ctx = (struct xml_ctx *)userData; - if (ctx->cdata) - free(ctx->cdata); + free(ctx->cdata); ctx->cdata = xmalloc(len + 1); strlcpy(ctx->cdata, s, len + 1); } @@ -1518,9 +1515,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->dentry_flags |= IS_DIR; } } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - if (ls->dentry_name) { - free(ls->dentry_name); - } + free(ls->dentry_name); ls->dentry_name = NULL; ls->dentry_flags = 0; } diff --git a/merge-recursive.c b/merge-recursive.c new file mode 100644 index 000000000..39a1eae89 --- /dev/null +++ b/merge-recursive.c @@ -0,0 +1,1354 @@ +/* + * Recursive Merge algorithm stolen from git-merge-recursive.py by + * Fredrik Kuivinen. + * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 + */ +#include <stdarg.h> +#include <string.h> +#include <assert.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include "cache.h" +#include "cache-tree.h" +#include "commit.h" +#include "blob.h" +#include "tree-walk.h" +#include "diff.h" +#include "diffcore.h" +#include "run-command.h" +#include "tag.h" +#include "unpack-trees.h" +#include "path-list.h" + +/* + * A virtual commit has + * - (const char *)commit->util set to the name, and + * - *(int *)commit->object.sha1 set to the virtual id. + */ + +static unsigned commit_list_count(const struct commit_list *l) +{ + unsigned c = 0; + for (; l; l = l->next ) + c++; + return c; +} + +static struct commit *make_virtual_commit(struct tree *tree, const char *comment) +{ + struct commit *commit = xcalloc(1, sizeof(struct commit)); + static unsigned virtual_id = 1; + commit->tree = tree; + commit->util = (void*)comment; + *(int*)commit->object.sha1 = virtual_id++; + /* avoid warnings */ + commit->object.parsed = 1; + return commit; +} + +/* + * Since we use get_tree_entry(), which does not put the read object into + * the object pool, we cannot rely on a == b. + */ +static int sha_eq(const unsigned char *a, const unsigned char *b) +{ + if (!a && !b) + return 2; + return a && b && hashcmp(a, b) == 0; +} + +/* + * Since we want to write the index eventually, we cannot reuse the index + * for these (temporary) data. + */ +struct stage_data +{ + struct + { + unsigned mode; + unsigned char sha[20]; + } stages[4]; + unsigned processed:1; +}; + +static struct path_list current_file_set = {NULL, 0, 0, 1}; +static struct path_list current_directory_set = {NULL, 0, 0, 1}; + +static int output_indent = 0; + +static void output(const char *fmt, ...) +{ + va_list args; + int i; + for (i = output_indent; i--;) + fputs(" ", stdout); + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); + fputc('\n', stdout); +} + +static void output_commit_title(struct commit *commit) +{ + int i; + for (i = output_indent; i--;) + fputs(" ", stdout); + if (commit->util) + printf("virtual %s\n", (char *)commit->util); + else { + printf("%s ", sha1_to_hex(commit->object.sha1)); + if (parse_commit(commit) != 0) + printf("(bad commit)\n"); + else { + const char *s; + int len; + for (s = commit->buffer; *s; s++) + if (*s == '\n' && s[1] == '\n') { + s += 2; + break; + } + for (len = 0; s[len] && '\n' != s[len]; len++) + ; /* do nothing */ + printf("%.*s\n", len, s); + } + } +} + +static const char *current_index_file = NULL; +static const char *original_index_file; +static const char *temporary_index_file; +static int cache_dirty = 0; + +static int flush_cache(void) +{ + /* flush temporary index */ + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int fd = hold_lock_file_for_update(lock, current_index_file, 1); + if (write_cache(fd, active_cache, active_nr) || + close(fd) || commit_lock_file(lock)) + die ("unable to write %s", current_index_file); + discard_cache(); + cache_dirty = 0; + return 0; +} + +static void setup_index(int temp) +{ + current_index_file = temp ? temporary_index_file: original_index_file; + if (cache_dirty) { + discard_cache(); + cache_dirty = 0; + } + unlink(temporary_index_file); + discard_cache(); +} + +static struct cache_entry *make_cache_entry(unsigned int mode, + const unsigned char *sha1, const char *path, int stage, int refresh) +{ + int size, len; + struct cache_entry *ce; + + if (!verify_path(path)) + return NULL; + + len = strlen(path); + size = cache_entry_size(len); + ce = xcalloc(1, size); + + hashcpy(ce->sha1, sha1); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(len, stage); + ce->ce_mode = create_ce_mode(mode); + + if (refresh) + return refresh_cache_entry(ce, 0); + + return ce; +} + +static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, + const char *path, int stage, int refresh, int options) +{ + struct cache_entry *ce; + if (!cache_dirty) + read_cache_from(current_index_file); + cache_dirty++; + ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); + if (!ce) + return error("cache_addinfo failed: %s", strerror(cache_errno)); + return add_cache_entry(ce, options); +} + +/* + * This is a global variable which is used in a number of places but + * only written to in the 'merge' function. + * + * index_only == 1 => Don't leave any non-stage 0 entries in the cache and + * don't update the working directory. + * 0 => Leave unmerged entries in the cache and update + * the working directory. + */ +static int index_only = 0; + +static int git_read_tree(struct tree *tree) +{ + int rc; + struct object_list *trees = NULL; + struct unpack_trees_options opts; + + if (cache_dirty) + die("read-tree with dirty cache"); + + memset(&opts, 0, sizeof(opts)); + object_list_append(&tree->object, &trees); + rc = unpack_trees(trees, &opts); + cache_tree_free(&active_cache_tree); + + if (rc == 0) + cache_dirty = 1; + + return rc; +} + +static int git_merge_trees(int index_only, + struct tree *common, + struct tree *head, + struct tree *merge) +{ + int rc; + struct object_list *trees = NULL; + struct unpack_trees_options opts; + + if (!cache_dirty) { + read_cache_from(current_index_file); + cache_dirty = 1; + } + + memset(&opts, 0, sizeof(opts)); + if (index_only) + opts.index_only = 1; + else + opts.update = 1; + opts.merge = 1; + opts.head_idx = 2; + opts.fn = threeway_merge; + + object_list_append(&common->object, &trees); + object_list_append(&head->object, &trees); + object_list_append(&merge->object, &trees); + + rc = unpack_trees(trees, &opts); + cache_tree_free(&active_cache_tree); + + cache_dirty = 1; + + return rc; +} + +static struct tree *git_write_tree(void) +{ + struct tree *result = NULL; + + if (cache_dirty) { + unsigned i; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) + return NULL; + } + } else + read_cache_from(current_index_file); + + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree) && + cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) + die("error building trees"); + + result = lookup_tree(active_cache_tree->sha1); + + flush_cache(); + cache_dirty = 0; + + return result; +} + +static int save_files_dirs(const unsigned char *sha1, + const char *base, int baselen, const char *path, + unsigned int mode, int stage) +{ + int len = strlen(path); + char *newpath = malloc(baselen + len + 1); + memcpy(newpath, base, baselen); + memcpy(newpath + baselen, path, len); + newpath[baselen + len] = '\0'; + + if (S_ISDIR(mode)) + path_list_insert(newpath, ¤t_directory_set); + else + path_list_insert(newpath, ¤t_file_set); + free(newpath); + + return READ_TREE_RECURSIVE; +} + +static int get_files_dirs(struct tree *tree) +{ + int n; + if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0) + return 0; + n = current_file_set.nr + current_directory_set.nr; + return n; +} + +/* + * Returns a index_entry instance which doesn't have to correspond to + * a real cache entry in Git's index. + */ +static struct stage_data *insert_stage_data(const char *path, + struct tree *o, struct tree *a, struct tree *b, + struct path_list *entries) +{ + struct path_list_item *item; + struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); + get_tree_entry(o->object.sha1, path, + e->stages[1].sha, &e->stages[1].mode); + get_tree_entry(a->object.sha1, path, + e->stages[2].sha, &e->stages[2].mode); + get_tree_entry(b->object.sha1, path, + e->stages[3].sha, &e->stages[3].mode); + item = path_list_insert(path, entries); + item->util = e; + return e; +} + +/* + * Create a dictionary mapping file names to stage_data objects. The + * dictionary contains one entry for every path with a non-zero stage entry. + */ +static struct path_list *get_unmerged(void) +{ + struct path_list *unmerged = xcalloc(1, sizeof(struct path_list)); + int i; + + unmerged->strdup_paths = 1; + if (!cache_dirty) { + read_cache_from(current_index_file); + cache_dirty++; + } + for (i = 0; i < active_nr; i++) { + struct path_list_item *item; + struct stage_data *e; + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + + item = path_list_lookup(ce->name, unmerged); + if (!item) { + item = path_list_insert(ce->name, unmerged); + item->util = xcalloc(1, sizeof(struct stage_data)); + } + e = item->util; + e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode); + hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); + } + + return unmerged; +} + +struct rename +{ + struct diff_filepair *pair; + struct stage_data *src_entry; + struct stage_data *dst_entry; + unsigned processed:1; +}; + +/* + * Get information of all renames which occured between 'o_tree' and + * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and + * 'b_tree') to be able to associate the correct cache entries with + * the rename information. 'tree' is always equal to either a_tree or b_tree. + */ +static struct path_list *get_renames(struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct path_list *entries) +{ + int i; + struct path_list *renames; + struct diff_options opts; + + renames = xcalloc(1, sizeof(struct path_list)); + diff_setup(&opts); + opts.recursive = 1; + opts.detect_rename = DIFF_DETECT_RENAME; + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + if (diff_setup_done(&opts) < 0) + die("diff setup failed"); + diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); + diffcore_std(&opts); + for (i = 0; i < diff_queued_diff.nr; ++i) { + struct path_list_item *item; + struct rename *re; + struct diff_filepair *pair = diff_queued_diff.queue[i]; + if (pair->status != 'R') { + diff_free_filepair(pair); + continue; + } + re = xmalloc(sizeof(*re)); + re->processed = 0; + re->pair = pair; + item = path_list_lookup(re->pair->one->path, entries); + if (!item) + re->src_entry = insert_stage_data(re->pair->one->path, + o_tree, a_tree, b_tree, entries); + else + re->src_entry = item->util; + + item = path_list_lookup(re->pair->two->path, entries); + if (!item) + re->dst_entry = insert_stage_data(re->pair->two->path, + o_tree, a_tree, b_tree, entries); + else + re->dst_entry = item->util; + item = path_list_insert(pair->one->path, renames); + item->util = re; + } + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_queued_diff.nr = 0; + diff_flush(&opts); + return renames; +} + +int update_stages(const char *path, struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, int clear) +{ + int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; + if (clear) + if (remove_file_from_cache(path)) + return -1; + if (o) + if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) + return -1; + if (a) + if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) + return -1; + if (b) + if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) + return -1; + return 0; +} + +static int remove_path(const char *name) +{ + int ret, len; + char *slash, *dirs; + + ret = unlink(name); + if (ret) + return ret; + len = strlen(name); + dirs = malloc(len+1); + memcpy(dirs, name, len); + dirs[len] = '\0'; + while ((slash = strrchr(name, '/'))) { + *slash = '\0'; + len = slash - name; + if (rmdir(name) != 0) + break; + } + free(dirs); + return ret; +} + +int remove_file(int clean, const char *path) +{ + int update_cache = index_only || clean; + int update_working_directory = !index_only; + + if (update_cache) { + if (!cache_dirty) + read_cache_from(current_index_file); + cache_dirty++; + if (remove_file_from_cache(path)) + return -1; + } + if (update_working_directory) + { + unlink(path); + if (errno != ENOENT || errno != EISDIR) + return -1; + remove_path(path); + } + return 0; +} + +static char *unique_path(const char *path, const char *branch) +{ + char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); + int suffix = 0; + struct stat st; + char *p = newpath + strlen(path); + strcpy(newpath, path); + *(p++) = '~'; + strcpy(p, branch); + for (; *p; ++p) + if ('/' == *p) + *p = '_'; + while (path_list_has_path(¤t_file_set, newpath) || + path_list_has_path(¤t_directory_set, newpath) || + lstat(newpath, &st) == 0) + sprintf(p, "_%d", suffix++); + + path_list_insert(newpath, ¤t_file_set); + return newpath; +} + +static int mkdir_p(const char *path, unsigned long mode) +{ + /* path points to cache entries, so strdup before messing with it */ + char *buf = strdup(path); + int result = safe_create_leading_directories(buf); + free(buf); + return result; +} + +static void flush_buffer(int fd, const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(fd, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("merge-recursive: %s", strerror(errno)); + } else if (!ret) { + die("merge-recursive: disk full?"); + } + size -= ret; + buf += ret; + } +} + +void update_file_flags(const unsigned char *sha, + unsigned mode, + const char *path, + int update_cache, + int update_wd) +{ + if (index_only) + update_wd = 0; + + if (update_wd) { + char type[20]; + void *buf; + unsigned long size; + + buf = read_sha1_file(sha, type, &size); + if (!buf) + die("cannot read object %s '%s'", sha1_to_hex(sha), path); + if (strcmp(type, blob_type) != 0) + die("blob expected for %s '%s'", sha1_to_hex(sha), path); + + if (S_ISREG(mode)) { + int fd; + if (mkdir_p(path, 0777)) + die("failed to create path %s: %s", path, strerror(errno)); + unlink(path); + if (mode & 0100) + mode = 0777; + else + mode = 0666; + fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); + if (fd < 0) + die("failed to open %s: %s", path, strerror(errno)); + flush_buffer(fd, buf, size); + close(fd); + } else if (S_ISLNK(mode)) { + char *lnk = malloc(size + 1); + memcpy(lnk, buf, size); + lnk[size] = '\0'; + mkdir_p(path, 0777); + unlink(lnk); + symlink(lnk, path); + } else + die("do not know what to do with %06o %s '%s'", + mode, sha1_to_hex(sha), path); + } + if (update_cache) + add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); +} + +void update_file(int clean, + const unsigned char *sha, + unsigned mode, + const char *path) +{ + update_file_flags(sha, mode, path, index_only || clean, !index_only); +} + +/* Low level file merging, update and removal */ + +struct merge_file_info +{ + unsigned char sha[20]; + unsigned mode; + unsigned clean:1, + merge:1; +}; + +static char *git_unpack_file(const unsigned char *sha1, char *path) +{ + void *buf; + char type[20]; + unsigned long size; + int fd; + + buf = read_sha1_file(sha1, type, &size); + if (!buf || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + + strcpy(path, ".merge_file_XXXXXX"); + fd = mkstemp(path); + if (fd < 0) + die("unable to create temp-file"); + flush_buffer(fd, buf, size); + close(fd); + return path; +} + +static struct merge_file_info merge_file(struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + const char *branch1, const char *branch2) +{ + struct merge_file_info result; + result.merge = 0; + result.clean = 1; + + if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { + result.clean = 0; + if (S_ISREG(a->mode)) { + result.mode = a->mode; + hashcpy(result.sha, a->sha1); + } else { + result.mode = b->mode; + hashcpy(result.sha, b->sha1); + } + } else { + if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) + result.merge = 1; + + result.mode = a->mode == o->mode ? b->mode: a->mode; + + if (sha_eq(a->sha1, o->sha1)) + hashcpy(result.sha, b->sha1); + else if (sha_eq(b->sha1, o->sha1)) + hashcpy(result.sha, a->sha1); + else if (S_ISREG(a->mode)) { + int code = 1, fd; + struct stat st; + char orig[PATH_MAX]; + char src1[PATH_MAX]; + char src2[PATH_MAX]; + const char *argv[] = { + "merge", "-L", NULL, "-L", NULL, "-L", NULL, + NULL, NULL, NULL, + NULL + }; + char *la, *lb, *lo; + + git_unpack_file(o->sha1, orig); + git_unpack_file(a->sha1, src1); + git_unpack_file(b->sha1, src2); + + argv[2] = la = strdup(mkpath("%s/%s", branch1, a->path)); + argv[6] = lb = strdup(mkpath("%s/%s", branch2, b->path)); + argv[4] = lo = strdup(mkpath("orig/%s", o->path)); + argv[7] = src1; + argv[8] = orig; + argv[9] = src2, + + code = run_command_v(10, argv); + + free(la); + free(lb); + free(lo); + if (code && code < -256) { + die("Failed to execute 'merge'. merge(1) is used as the " + "file-level merge tool. Is 'merge' in your path?"); + } + fd = open(src1, O_RDONLY); + if (fd < 0 || fstat(fd, &st) < 0 || + index_fd(result.sha, fd, &st, 1, + "blob")) + die("Unable to add %s to database", src1); + + unlink(orig); + unlink(src1); + unlink(src2); + + result.clean = WEXITSTATUS(code) == 0; + } else { + if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) + die("cannot merge modes?"); + + hashcpy(result.sha, a->sha1); + + if (!sha_eq(a->sha1, b->sha1)) + result.clean = 0; + } + } + + return result; +} + +static void conflict_rename_rename(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *del[2]; + int delp = 0; + const char *ren1_dst = ren1->pair->two->path; + const char *ren2_dst = ren2->pair->two->path; + const char *dst_name1 = ren1_dst; + const char *dst_name2 = ren2_dst; + if (path_list_has_path(¤t_directory_set, ren1_dst)) { + dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); + output("%s is a directory in %s adding as %s instead", + ren1_dst, branch2, dst_name1); + remove_file(0, ren1_dst); + } + if (path_list_has_path(¤t_directory_set, ren2_dst)) { + dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); + output("%s is a directory in %s adding as %s instead", + ren2_dst, branch1, dst_name2); + remove_file(0, ren2_dst); + } + update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); + update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); + while (delp--) + free(del[delp]); +} + +static void conflict_rename_dir(struct rename *ren1, + const char *branch1) +{ + char *new_path = unique_path(ren1->pair->two->path, branch1); + output("Renaming %s to %s instead", ren1->pair->one->path, new_path); + remove_file(0, ren1->pair->two->path); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); + free(new_path); +} + +static void conflict_rename_rename_2(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *new_path1 = unique_path(ren1->pair->two->path, branch1); + char *new_path2 = unique_path(ren2->pair->two->path, branch2); + output("Renaming %s to %s and %s to %s instead", + ren1->pair->one->path, new_path1, + ren2->pair->one->path, new_path2); + remove_file(0, ren1->pair->two->path); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); + update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); + free(new_path2); + free(new_path1); +} + +static int process_renames(struct path_list *a_renames, + struct path_list *b_renames, + const char *a_branch, + const char *b_branch) +{ + int clean_merge = 1, i, j; + struct path_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; + const struct rename *sre; + + for (i = 0; i < a_renames->nr; i++) { + sre = a_renames->items[i].util; + path_list_insert(sre->pair->two->path, &a_by_dst)->util + = sre->dst_entry; + } + for (i = 0; i < b_renames->nr; i++) { + sre = b_renames->items[i].util; + path_list_insert(sre->pair->two->path, &b_by_dst)->util + = sre->dst_entry; + } + + for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { + int compare; + char *src; + struct path_list *renames1, *renames2, *renames2Dst; + struct rename *ren1 = NULL, *ren2 = NULL; + const char *branch1, *branch2; + const char *ren1_src, *ren1_dst; + + if (i >= a_renames->nr) { + compare = 1; + ren2 = b_renames->items[j++].util; + } else if (j >= b_renames->nr) { + compare = -1; + ren1 = a_renames->items[i++].util; + } else { + compare = strcmp(a_renames->items[i].path, + b_renames->items[j].path); + if (compare <= 0) + ren1 = a_renames->items[i++].util; + if (compare >= 0) + ren2 = b_renames->items[j++].util; + } + + /* TODO: refactor, so that 1/2 are not needed */ + if (ren1) { + renames1 = a_renames; + renames2 = b_renames; + renames2Dst = &b_by_dst; + branch1 = a_branch; + branch2 = b_branch; + } else { + struct rename *tmp; + renames1 = b_renames; + renames2 = a_renames; + renames2Dst = &a_by_dst; + branch1 = b_branch; + branch2 = a_branch; + tmp = ren2; + ren2 = ren1; + ren1 = tmp; + } + src = ren1->pair->one->path; + + ren1->dst_entry->processed = 1; + ren1->src_entry->processed = 1; + + if (ren1->processed) + continue; + ren1->processed = 1; + + ren1_src = ren1->pair->one->path; + ren1_dst = ren1->pair->two->path; + + if (ren2) { + const char *ren2_src = ren2->pair->one->path; + const char *ren2_dst = ren2->pair->two->path; + /* Renamed in 1 and renamed in 2 */ + if (strcmp(ren1_src, ren2_src) != 0) + die("ren1.src != ren2.src"); + ren2->dst_entry->processed = 1; + ren2->processed = 1; + if (strcmp(ren1_dst, ren2_dst) != 0) { + clean_merge = 0; + output("CONFLICT (rename/rename): " + "Rename %s->%s in branch %s " + "rename %s->%s in %s", + src, ren1_dst, branch1, + src, ren2_dst, branch2); + conflict_rename_rename(ren1, branch1, ren2, branch2); + } else { + struct merge_file_info mfi; + remove_file(1, ren1_src); + mfi = merge_file(ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + branch1, + branch2); + if (mfi.merge || !mfi.clean) + output("Renaming %s->%s", src, ren1_dst); + + if (mfi.merge) + output("Auto-merging %s", ren1_dst); + + if (!mfi.clean) { + output("CONFLICT (content): merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + 1 /* clear */); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } else { + /* Renamed in 1, maybe changed in 2 */ + struct path_list_item *item; + /* we only use sha1 and mode of these */ + struct diff_filespec src_other, dst_other; + int try_merge, stage = a_renames == renames1 ? 3: 2; + + remove_file(1, ren1_src); + + hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); + src_other.mode = ren1->src_entry->stages[stage].mode; + hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha); + dst_other.mode = ren1->dst_entry->stages[stage].mode; + + try_merge = 0; + + if (path_list_has_path(¤t_directory_set, ren1_dst)) { + clean_merge = 0; + output("CONFLICT (rename/directory): Rename %s->%s in %s " + " directory %s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + conflict_rename_dir(ren1, branch1); + } else if (sha_eq(src_other.sha1, null_sha1)) { + clean_merge = 0; + output("CONFLICT (rename/delete): Rename %s->%s in %s " + "and deleted in %s", + ren1_src, ren1_dst, branch1, + branch2); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + } else if (!sha_eq(dst_other.sha1, null_sha1)) { + const char *new_path; + clean_merge = 0; + try_merge = 1; + output("CONFLICT (rename/add): Rename %s->%s in %s. " + "%s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + new_path = unique_path(ren1_dst, branch2); + output("Adding as %s instead", new_path); + update_file(0, dst_other.sha1, dst_other.mode, new_path); + } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) { + ren2 = item->util; + clean_merge = 0; + ren2->processed = 1; + output("CONFLICT (rename/rename): Rename %s->%s in %s. " + "Rename %s->%s in %s", + ren1_src, ren1_dst, branch1, + ren2->pair->one->path, ren2->pair->two->path, branch2); + conflict_rename_rename_2(ren1, branch1, ren2, branch2); + } else + try_merge = 1; + + if (try_merge) { + struct diff_filespec *o, *a, *b; + struct merge_file_info mfi; + src_other.path = (char *)ren1_src; + + o = ren1->pair->one; + if (a_renames == renames1) { + a = ren1->pair->two; + b = &src_other; + } else { + b = ren1->pair->two; + a = &src_other; + } + mfi = merge_file(o, a, b, + a_branch, b_branch); + + if (mfi.merge || !mfi.clean) + output("Renaming %s => %s", ren1_src, ren1_dst); + if (mfi.merge) + output("Auto-merging %s", ren1_dst); + if (!mfi.clean) { + output("CONFLICT (rename/modify): Merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + o, a, b, 1); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } + } + path_list_clear(&a_by_dst, 0); + path_list_clear(&b_by_dst, 0); + + if (cache_dirty) + flush_cache(); + return clean_merge; +} + +static unsigned char *has_sha(const unsigned char *sha) +{ + return is_null_sha1(sha) ? NULL: (unsigned char *)sha; +} + +/* Per entry merge function */ +static int process_entry(const char *path, struct stage_data *entry, + const char *branch1, + const char *branch2) +{ + /* + printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); + print_index_entry("\tpath: ", entry); + */ + int clean_merge = 1; + unsigned char *o_sha = has_sha(entry->stages[1].sha); + unsigned char *a_sha = has_sha(entry->stages[2].sha); + unsigned char *b_sha = has_sha(entry->stages[3].sha); + unsigned o_mode = entry->stages[1].mode; + unsigned a_mode = entry->stages[2].mode; + unsigned b_mode = entry->stages[3].mode; + + if (o_sha && (!a_sha || !b_sha)) { + /* Case A: Deleted in one */ + if ((!a_sha && !b_sha) || + (sha_eq(a_sha, o_sha) && !b_sha) || + (!a_sha && sha_eq(b_sha, o_sha))) { + /* Deleted in both or deleted in one and + * unchanged in the other */ + if (a_sha) + output("Removing %s", path); + remove_file(1, path); + } else { + /* Deleted in one and changed in the other */ + clean_merge = 0; + if (!a_sha) { + output("CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch1, + branch2, branch2, path); + update_file(0, b_sha, b_mode, path); + } else { + output("CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch2, + branch1, branch1, path); + update_file(0, a_sha, a_mode, path); + } + } + + } else if ((!o_sha && a_sha && !b_sha) || + (!o_sha && !a_sha && b_sha)) { + /* Case B: Added in one. */ + const char *add_branch; + const char *other_branch; + unsigned mode; + const unsigned char *sha; + const char *conf; + + if (a_sha) { + add_branch = branch1; + other_branch = branch2; + mode = a_mode; + sha = a_sha; + conf = "file/directory"; + } else { + add_branch = branch2; + other_branch = branch1; + mode = b_mode; + sha = b_sha; + conf = "directory/file"; + } + if (path_list_has_path(¤t_directory_set, path)) { + const char *new_path = unique_path(path, add_branch); + clean_merge = 0; + output("CONFLICT (%s): There is a directory with name %s in %s. " + "Adding %s as %s", + conf, path, other_branch, path, new_path); + remove_file(0, path); + update_file(0, sha, mode, new_path); + } else { + output("Adding %s", path); + update_file(1, sha, mode, path); + } + } else if (!o_sha && a_sha && b_sha) { + /* Case C: Added in both (check for same permissions). */ + if (sha_eq(a_sha, b_sha)) { + if (a_mode != b_mode) { + clean_merge = 0; + output("CONFLICT: File %s added identically in both branches, " + "but permissions conflict %06o->%06o", + path, a_mode, b_mode); + output("CONFLICT: adding with permission: %06o", a_mode); + update_file(0, a_sha, a_mode, path); + } else { + /* This case is handled by git-read-tree */ + assert(0 && "This case must be handled by git-read-tree"); + } + } else { + const char *new_path1, *new_path2; + clean_merge = 0; + new_path1 = unique_path(path, branch1); + new_path2 = unique_path(path, branch2); + output("CONFLICT (add/add): File %s added non-identically " + "in both branches. Adding as %s and %s instead.", + path, new_path1, new_path2); + remove_file(0, path); + update_file(0, a_sha, a_mode, new_path1); + update_file(0, b_sha, b_mode, new_path2); + } + + } else if (o_sha && a_sha && b_sha) { + /* case D: Modified in both, but differently. */ + struct merge_file_info mfi; + struct diff_filespec o, a, b; + + output("Auto-merging %s", path); + o.path = a.path = b.path = (char *)path; + hashcpy(o.sha1, o_sha); + o.mode = o_mode; + hashcpy(a.sha1, a_sha); + a.mode = a_mode; + hashcpy(b.sha1, b_sha); + b.mode = b_mode; + + mfi = merge_file(&o, &a, &b, + branch1, branch2); + + if (mfi.clean) + update_file(1, mfi.sha, mfi.mode, path); + else { + clean_merge = 0; + output("CONFLICT (content): Merge conflict in %s", path); + + if (index_only) + update_file(0, mfi.sha, mfi.mode, path); + else + update_file_flags(mfi.sha, mfi.mode, path, + 0 /* update_cache */, 1 /* update_working_directory */); + } + } else + die("Fatal merge failure, shouldn't happen."); + + if (cache_dirty) + flush_cache(); + + return clean_merge; +} + +static int merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1, + const char *branch2, + struct tree **result) +{ + int code, clean; + if (sha_eq(common->object.sha1, merge->object.sha1)) { + output("Already uptodate!"); + *result = head; + return 1; + } + + code = git_merge_trees(index_only, common, head, merge); + + if (code != 0) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + + *result = git_write_tree(); + + if (!*result) { + struct path_list *entries, *re_head, *re_merge; + int i; + path_list_clear(¤t_file_set, 1); + path_list_clear(¤t_directory_set, 1); + get_files_dirs(head); + get_files_dirs(merge); + + entries = get_unmerged(); + re_head = get_renames(head, common, head, merge, entries); + re_merge = get_renames(merge, common, head, merge, entries); + clean = process_renames(re_head, re_merge, + branch1, branch2); + for (i = 0; i < entries->nr; i++) { + const char *path = entries->items[i].path; + struct stage_data *e = entries->items[i].util; + if (e->processed) + continue; + if (!process_entry(path, e, branch1, branch2)) + clean = 0; + } + + path_list_clear(re_merge, 0); + path_list_clear(re_head, 0); + path_list_clear(entries, 1); + + if (clean || index_only) + *result = git_write_tree(); + else + *result = NULL; + } else { + clean = 1; + printf("merging of trees %s and %s resulted in %s\n", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1), + sha1_to_hex((*result)->object.sha1)); + } + + return clean; +} + +static struct commit_list *reverse_commit_list(struct commit_list *list) +{ + struct commit_list *next = NULL, *current, *backup; + for (current = list; current; current = backup) { + backup = current->next; + current->next = next; + next = current; + } + return next; +} + +/* + * Merge the commits h1 and h2, return the resulting virtual + * commit object and a flag indicating the cleaness of the merge. + */ +static +int merge(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + int call_depth /* =0 */, + struct commit *ancestor /* =None */, + struct commit **result) +{ + struct commit_list *ca = NULL, *iter; + struct commit *merged_common_ancestors; + struct tree *mrtree; + int clean; + + output("Merging:"); + output_commit_title(h1); + output_commit_title(h2); + + if (ancestor) + commit_list_insert(ancestor, &ca); + else + ca = reverse_commit_list(get_merge_bases(h1, h2, 1)); + + output("found %u common ancestor(s):", commit_list_count(ca)); + for (iter = ca; iter; iter = iter->next) + output_commit_title(iter->item); + + merged_common_ancestors = pop_commit(&ca); + if (merged_common_ancestors == NULL) { + /* if there is no common ancestor, make an empty tree */ + struct tree *tree = xcalloc(1, sizeof(struct tree)); + unsigned char hdr[40]; + int hdrlen; + + tree->object.parsed = 1; + tree->object.type = OBJ_TREE; + write_sha1_file_prepare(NULL, 0, tree_type, tree->object.sha1, + hdr, &hdrlen); + merged_common_ancestors = make_virtual_commit(tree, "ancestor"); + } + + for (iter = ca; iter; iter = iter->next) { + output_indent = call_depth + 1; + /* + * When the merge fails, the result contains files + * with conflict markers. The cleanness flag is + * ignored, it was never acutally used, as result of + * merge_trees has always overwritten it: the commited + * "conflicts" were already resolved. + */ + merge(merged_common_ancestors, iter->item, + "Temporary merge branch 1", + "Temporary merge branch 2", + call_depth + 1, + NULL, + &merged_common_ancestors); + output_indent = call_depth; + + if (!merged_common_ancestors) + die("merge returned no commit"); + } + + if (call_depth == 0) { + setup_index(0 /* $GIT_DIR/index */); + index_only = 0; + } else { + setup_index(1 /* temporary index */); + git_read_tree(h1->tree); + index_only = 1; + } + + clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, + branch1, branch2, &mrtree); + + if (!ancestor && (clean || index_only)) { + *result = make_virtual_commit(mrtree, "merged tree"); + commit_list_insert(h1, &(*result)->parents); + commit_list_insert(h2, &(*result)->parents->next); + } else + *result = NULL; + + return clean; +} + +static struct commit *get_ref(const char *ref) +{ + unsigned char sha1[20]; + struct object *object; + + if (get_sha1(ref, sha1)) + die("Could not resolve ref '%s'", ref); + object = deref_tag(parse_object(sha1), ref, strlen(ref)); + if (object->type != OBJ_COMMIT) + return NULL; + if (parse_commit((struct commit *)object)) + die("Could not parse commit '%s'", sha1_to_hex(object->sha1)); + return (struct commit *)object; +} + +int main(int argc, char *argv[]) +{ + static const char *bases[2]; + static unsigned bases_count = 0; + int i, clean; + const char *branch1, *branch2; + struct commit *result, *h1, *h2; + + original_index_file = getenv("GIT_INDEX_FILE"); + + if (!original_index_file) + original_index_file = strdup(git_path("index")); + + temporary_index_file = strdup(git_path("mrg-rcrsv-tmp-idx")); + + if (argc < 4) + die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]); + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--")) + break; + if (bases_count < sizeof(bases)/sizeof(*bases)) + bases[bases_count++] = argv[i]; + } + if (argc - i != 3) /* "--" "<head>" "<remote>" */ + die("Not handling anything other than two heads merge."); + + branch1 = argv[++i]; + branch2 = argv[++i]; + printf("Merging %s with %s\n", branch1, branch2); + + h1 = get_ref(branch1); + h2 = get_ref(branch2); + + if (bases_count == 1) { + struct commit *ancestor = get_ref(bases[0]); + clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result); + } else + clean = merge(h1, h2, branch1, branch2, 0, NULL, &result); + + if (cache_dirty) + flush_cache(); + + return clean ? 0: 1; +} + +/* +vim: sw=8 noet +*/ diff --git a/object-refs.c b/object-refs.c index b1b806585..b0034e4b2 100644 --- a/object-refs.c +++ b/object-refs.c @@ -30,7 +30,7 @@ static void grow_refs_hash(void) int new_hash_size = (refs_hash_size + 1000) * 3 / 2; struct object_refs **new_hash; - new_hash = calloc(new_hash_size, sizeof(struct object_refs *)); + new_hash = xcalloc(new_hash_size, sizeof(struct object_refs *)); for (i = 0; i < refs_hash_size; i++) { struct object_refs *ref = refs_hash[i]; if (!ref) @@ -73,7 +73,7 @@ static void grow_object_hash(void) int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size; struct object **new_hash; - new_hash = calloc(new_hash_size, sizeof(struct object *)); + new_hash = xcalloc(new_hash_size, sizeof(struct object *)); for (i = 0; i < obj_hash_size; i++) { struct object *obj = obj_hash[i]; if (!obj) diff --git a/path-list.c b/path-list.c index f15a10de3..b1ee72d1d 100644 --- a/path-list.c +++ b/path-list.c @@ -85,8 +85,7 @@ void path_list_clear(struct path_list *list, int free_items) for (i = 0; i < list->nr; i++) { if (list->strdup_paths) free(list->items[i].path); - if (list->items[i].util) - free(list->items[i].util); + free(list->items[i].util); } free(list->items); } diff --git a/read-cache.c b/read-cache.c index b6982eac4..20c9d494a 100644 --- a/read-cache.c +++ b/read-cache.c @@ -842,6 +842,23 @@ unmap: die("index file corrupt"); } +int discard_cache() +{ + int ret; + + active_nr = active_cache_changed = 0; + index_file_timestamp = 0; + cache_tree_free(&active_cache_tree); + if (cache_mmap == NULL) + return 0; + ret = munmap(cache_mmap, cache_mmap_size); + cache_mmap = NULL; + cache_mmap_size = 0; + + /* no need to throw away allocated active_cache */ + return ret; +} + #define WRITE_BUFFER_SIZE 8192 static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned long write_buffer_len; @@ -348,10 +348,8 @@ void unlock_ref(struct ref_lock *lock) if (lock->lk) rollback_lock_file(lock->lk); } - if (lock->ref_file) - free(lock->ref_file); - if (lock->log_file) - free(lock->log_file); + free(lock->ref_file); + free(lock->log_file); free(lock); } diff --git a/sha1_file.c b/sha1_file.c index 769a80984..46272b591 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -917,23 +917,19 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of enum object_type *type, unsigned long *sizep) { unsigned shift; - unsigned char *pack, c; + unsigned char c; unsigned long size; if (offset >= p->pack_size) die("object offset outside of pack file"); - - pack = (unsigned char *) p->pack_base + offset; - c = *pack++; - offset++; + c = *((unsigned char *)p->pack_base + offset++); *type = (c >> 4) & 7; size = c & 15; shift = 4; while (c & 0x80) { if (offset >= p->pack_size) die("object offset outside of pack file"); - c = *pack++; - offset++; + c = *((unsigned char *)p->pack_base + offset++); size += (c & 0x7f) << shift; shift += 7; } @@ -996,16 +992,10 @@ void packed_object_info_detail(struct pack_entry *e, } switch (kind) { case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); + strcpy(type, type_names[kind]); break; default: die("corrupted pack file %s containing object of kind %d", @@ -1036,16 +1026,10 @@ static int packed_object_info(struct pack_entry *entry, unuse_packed_git(p); return retval; case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); + strcpy(type, type_names[kind]); break; default: die("corrupted pack file %s containing object of kind %d", @@ -1057,23 +1041,49 @@ static int packed_object_info(struct pack_entry *entry, return 0; } -static void *unpack_delta_entry(unsigned char *base_sha1, +static void *unpack_compressed_entry(struct packed_git *p, + unsigned long offset, + unsigned long size) +{ + int st; + z_stream stream; + unsigned char *buffer; + + buffer = xmalloc(size + 1); + buffer[size] = 0; + memset(&stream, 0, sizeof(stream)); + stream.next_in = (unsigned char*)p->pack_base + offset; + stream.avail_in = p->pack_size - offset; + stream.next_out = buffer; + stream.avail_out = size; + + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if ((st != Z_STREAM_END) || stream.total_out != size) { + free(buffer); + return NULL; + } + + return buffer; +} + +static void *unpack_delta_entry(struct packed_git *p, + unsigned long offset, unsigned long delta_size, - unsigned long left, char *type, - unsigned long *sizep, - struct packed_git *p) + unsigned long *sizep) { struct pack_entry base_ent; - void *data, *delta_data, *result, *base; - unsigned long data_size, result_size, base_size; - z_stream stream; - int st; + void *delta_data, *result, *base; + unsigned long result_size, base_size; + unsigned char* base_sha1; - if (left < 20) + if ((offset + 20) >= p->pack_size) 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)) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); @@ -1082,23 +1092,7 @@ static void *unpack_delta_entry(unsigned char *base_sha1, die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); - data = base_sha1 + 20; - data_size = left - 20; - delta_data = xmalloc(delta_size); - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = data; - stream.avail_in = data_size; - stream.next_out = delta_data; - stream.avail_out = delta_size; - - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - inflateEnd(&stream); - if ((st != Z_STREAM_END) || stream.total_out != delta_size) - die("delta data unpack failed"); - + delta_data = unpack_compressed_entry(p, offset + 20, delta_size); result = patch_delta(base, base_size, delta_data, delta_size, &result_size); @@ -1110,33 +1104,6 @@ static void *unpack_delta_entry(unsigned char *base_sha1, return result; } -static void *unpack_non_delta_entry(unsigned char *data, - unsigned long size, - unsigned long left) -{ - int st; - z_stream stream; - unsigned char *buffer; - - buffer = xmalloc(size + 1); - buffer[size] = 0; - memset(&stream, 0, sizeof(stream)); - stream.next_in = data; - stream.avail_in = left; - stream.next_out = buffer; - stream.avail_out = size; - - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - inflateEnd(&stream); - if ((st != Z_STREAM_END) || stream.total_out != size) { - free(buffer); - return NULL; - } - - return buffer; -} - static void *unpack_entry(struct pack_entry *entry, char *type, unsigned long *sizep) { @@ -1157,36 +1124,23 @@ void *unpack_entry_gently(struct pack_entry *entry, char *type, unsigned long *sizep) { struct packed_git *p = entry->p; - unsigned long offset, size, left; - unsigned char *pack; + unsigned long offset, size; enum object_type kind; - void *retval; offset = unpack_object_header(p, entry->offset, &kind, &size); - pack = (unsigned char *) p->pack_base + offset; - left = p->pack_size - offset; switch (kind) { case OBJ_DELTA: - retval = unpack_delta_entry(pack, size, left, type, sizep, p); - return retval; + return unpack_delta_entry(p, offset, size, type, sizep); case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); - break; + strcpy(type, type_names[kind]); + *sizep = size; + return unpack_compressed_entry(p, offset, size); default: return NULL; } - *sizep = size; - retval = unpack_non_delta_entry(pack, size, left); - return retval; } int num_packed_objects(const struct packed_git *p) @@ -1786,7 +1740,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size) off += iret; if (off == size) { size *= 2; - buf = realloc(buf, size); + buf = xrealloc(buf, size); } } } while (iret > 0); diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh index 69aebe600..74f5c2a57 100755 --- a/t/t4116-apply-reverse.sh +++ b/t/t4116-apply-reverse.sh @@ -22,25 +22,64 @@ test_expect_success setup ' tr "[mon]" '\''[\0\1\2]'\'' <file1 >file2 && git commit -a -m second && + git tag second && - git diff --binary -R initial >patch + git diff --binary initial second >patch ' test_expect_success 'apply in forward' ' + T0=`git rev-parse "second^{tree}"` && + git reset --hard initial && git apply --index --binary patch && - git diff initial >diff && - diff -u /dev/null diff - + T1=`git write-tree` && + test "$T0" = "$T1" ' test_expect_success 'apply in reverse' ' + git reset --hard second && git apply --reverse --binary --index patch && git diff >diff && diff -u /dev/null diff ' +test_expect_success 'setup separate repository lacking postimage' ' + + git tar-tree initial initial | tar xf - && + ( + cd initial && git init-db && git add . + ) && + + git tar-tree second second | tar xf - && + ( + cd second && git init-db && git add . + ) + +' + +test_expect_success 'apply in forward without postimage' ' + + T0=`git rev-parse "second^{tree}"` && + ( + cd initial && + git apply --index --binary ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + +test_expect_success 'apply in reverse without postimage' ' + + T0=`git rev-parse "initial^{tree}"` && + ( + cd second && + git apply --index --binary --reverse ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + test_done diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh new file mode 100755 index 000000000..b4de075a3 --- /dev/null +++ b/t/t4117-apply-reject.sh @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply with rejects + +' + +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + do + echo $i + done >file1 && + cat file1 >saved.file1 && + git update-index --add file1 && + git commit -m initial && + + for i in 1 2 A B 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 D 21 + do + echo $i + done >file1 && + git diff >patch.1 && + cat file1 >clean && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >expected && + + mv file1 file2 && + git update-index --add --remove file1 file2 && + git diff -M HEAD >patch.2 && + + rm -f file1 file2 && + mv saved.file1 file1 && + git update-index --add --remove file1 file2 && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >file1 && + + cat file1 >saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply --verbose patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej && + + if git apply --reject patch.1 + then + echo "succeeds with --reject?" + exit 1 + fi + + diff -u file1 expected && + + cat file1.rej && + + if test -f file2.rej + then + echo "file2 should not have been touched" + exit 1 + fi +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'the same test with --verbose' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject --verbose patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'apply cleanly with --verbose' ' + + git cat-file -p HEAD:file1 >file1 && + rm -f file?.rej file2 && + + git apply --verbose patch.1 && + + diff -u file1 clean +' + +test_done diff --git a/xdiff-interface.c b/xdiff-interface.c index 6a82da73b..08602f522 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -69,9 +69,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) for (i = 0; i < nbuf; i++) { if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); priv->remainder_size += mb[i].size; @@ -83,9 +83,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) consume_one(priv, mb[i].ptr, mb[i].size); continue; } - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); consume_one(priv, priv->remainder, |